PlaqueDetectHelper.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Runtime.InteropServices;
  5. using AI.Common;
  6. using AI.Common.Log;
  7. using AI.DiagSystem;
  8. using AI.Reconstruction;
  9. namespace Reconstruction3DHelper
  10. {
  11. public class PlaqueDetectHelper
  12. {
  13. #region field
  14. private AIDiagSystem _diagSystem;
  15. private bool _disposing = false;
  16. #endregion
  17. #region events
  18. /// <summary>
  19. /// 通知订阅者,推理过程中发生了错误
  20. /// </summary>
  21. public event EventHandler<ErrorEventArgs> NotifyError;
  22. /// <summary>
  23. /// 通知订阅者,推理过程中有log要记录
  24. /// </summary>
  25. public event EventHandler<LogEventArgs> NotifyLog;
  26. #endregion
  27. #region dllImport
  28. [DllImport(@"PlaqueProcessingCpp.dll", CallingConvention = CallingConvention.Cdecl)]
  29. [return: MarshalAs(UnmanagedType.I1)]
  30. public static extern bool SaveSliceImage(AI.Reconstruction.AIReconstructor.StructImageInfo srcImageInfo, int name);
  31. #endregion
  32. #region constructor
  33. public PlaqueDetectHelper(EnumPerformance performace = EnumPerformance.Low)
  34. {
  35. _diagSystem = new AIDiagSystem(performace);
  36. _diagSystem.IsCropped = false;
  37. _diagSystem.EnableLesionSeg = true;
  38. _diagSystem.EnableDescription = true;
  39. _diagSystem.NotifyError += OnErrorOccur;
  40. _diagSystem.NotifyLog += OnLogWrite;
  41. _diagSystem.EnableDebugLogWrite = true;
  42. }
  43. ~PlaqueDetectHelper()
  44. {
  45. DoDispose();
  46. }
  47. #endregion
  48. #region public
  49. /// <summary>
  50. /// 根据AI结果,自动选取最优的血管内膜切面,返回图像上的三个3D坐标点
  51. /// </summary>
  52. /// <param name="modelResults"></param>
  53. /// <param name="modelSize"></param>
  54. /// <param name="imageWidth"></param>
  55. /// <param name="imageHeight"></param>
  56. /// <returns></returns>
  57. public List<Point3DF> GetClipPoints(Dictionary<int, AIDiagResultPerImg> modelResults, ModelSize modelSize)
  58. {
  59. var intimaSlicePoints = GetVesselSlicePoints(modelResults, modelSize.ModelLengthX, modelSize.ModelLengthY, modelSize.ModelLengthZ);
  60. if (intimaSlicePoints == null)
  61. {
  62. LogHelper.ErrorLog($"DetectIntima, get three coplanar points failed!");
  63. return null;
  64. }
  65. //获得切面图像用于检测内膜
  66. List<Point3DF> pointList = new List<Point3DF> { intimaSlicePoints.Point1, intimaSlicePoints.Point2, intimaSlicePoints.Point3 };
  67. return pointList;
  68. }
  69. public Dictionary<int, AIDiagResultPerImg> DetectPlaquesInVolumeData(byte[] volumeDataBuffer, int width, int height,
  70. int imageCount,EnumColorType colorType)
  71. {
  72. try
  73. {
  74. var diagId = _diagSystem.StartEvalutationOfMultipleImageBatches(imageCount);
  75. int bytesPerPixel = RawImage.GetBytesPerPixel(colorType);
  76. int bytesPerImage = width * height * bytesPerPixel;
  77. byte[] imageDataBuffer = new byte[bytesPerImage];
  78. //var RawImages = new Dictionary<int, RawImage>();
  79. for (int ni = 0; ni < imageCount; ni++)
  80. {
  81. Array.Copy(volumeDataBuffer, ni * bytesPerImage, imageDataBuffer, 0, bytesPerImage);
  82. RawImage image = new RawImage(imageDataBuffer, width, height, colorType);
  83. // RawImages.Add(ni, image);
  84. //单通道转为3通道
  85. //RawImage imageBgr = image.Clone(EnumColorType.Bgr);
  86. //图像保存至本地,用于测试
  87. //var decodedPointer = Marshal.UnsafeAddrOfPinnedArrayElement(imageDataBuffer, 0);
  88. //AI.Reconstruction.AIReconstructor.StructImageInfo srcImageInfo = new AI.Reconstruction.AIReconstructor.StructImageInfo(width, height, colorType, decodedPointer);
  89. //SaveSliceImage(srcImageInfo, ni);
  90. //var diagResultOne = _diagSystem.EvaluateOneImage(image);
  91. _diagSystem.PushOneBatchOfImagesAsync(diagId, new List<RawImage> { image });
  92. }
  93. var diagResult = _diagSystem.GetEvaluationsOfPushedMultipleImageBatches(diagId);
  94. return diagResult;
  95. }
  96. catch (Exception e)
  97. {
  98. LogHelper.ErrorLog("PlaqueDetectHelper, DetectPlaquesInVolumeData error," + e.Message + "," + e.StackTrace);
  99. throw;
  100. }
  101. }
  102. public void Dispose()
  103. {
  104. DoDispose();
  105. GC.SuppressFinalize(this);
  106. }
  107. #endregion
  108. #region private
  109. /// <summary>
  110. /// 根据三维点集投影到Y=0平面上的投影线的信息,求过这条投影线的平面上的三个空间点的位置
  111. /// </summary>
  112. /// <param name="projectionLine"></param>
  113. /// <returns></returns>
  114. private bool GetCoplanarPointsWithProjectionLine(double[] projectionLine, int modelX, int modelY, int modelZ, out CoplanarPoints points)
  115. {
  116. points = CoplanarPoints.Empty;
  117. try
  118. {
  119. Point3DF point1 = Point3DF.Empty;
  120. Point3DF point2 = Point3DF.Empty;
  121. Point3DF point3 = Point3DF.Empty;
  122. if (projectionLine[1] == 0)
  123. {
  124. var x = -projectionLine[2];
  125. x = Math.Min(Math.Max(0, x), modelX - 1);
  126. point1 = new Point3DF((float)x, 0, 0);
  127. point2 = new Point3DF((float)x, 0, modelZ - 1);
  128. point3 = new Point3DF((float)x, modelY - 1, 0);
  129. }
  130. else
  131. {
  132. var k = projectionLine[0];
  133. var b = projectionLine[2];
  134. // 计算直线与z=modelZ-1,y=0直线的交点
  135. var tempX1 = (modelZ - 1 - b) / k; // 此处原为tempX1 = modelZ - 1 - b / k; 按照直线求交点的公式是错误的
  136. if (tempX1 < 0)
  137. {
  138. var tempZ = (int)Math.Round(b);
  139. tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1);
  140. point1 = new Point3DF(0, 0, tempZ);
  141. point2 = new Point3DF(0, modelY - 1, tempZ);
  142. }
  143. else if (tempX1 < modelX - 1)
  144. {
  145. var tempX = (int)Math.Round(tempX1);
  146. tempX = Math.Min(Math.Max(0, tempX), modelX - 1);
  147. point1 = new Point3DF(tempX, 0, modelZ - 1);
  148. point2 = new Point3DF(tempX, modelY - 1, modelZ - 1);
  149. }
  150. else
  151. {
  152. var tempZ = (int)Math.Round(k * (modelX - 1) + b);
  153. tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1);
  154. point1 = new Point3DF(modelX - 1, 0, tempZ);
  155. point2 = new Point3DF(modelX - 1, modelY - 1, tempZ);
  156. }
  157. var tempX2 = -b / k;
  158. if (tempX2 < 0)
  159. {
  160. var tempZ = (int)Math.Round(b);
  161. tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1);
  162. point3 = new Point3DF(0, 0, tempZ);
  163. }
  164. else if (tempX2 < modelX - 1)
  165. {
  166. var tempX = (int)Math.Round(tempX2);
  167. tempX = Math.Min(Math.Max(0, tempX), modelX - 1);
  168. point3 = new Point3DF(tempX, 0, 0);
  169. }
  170. else
  171. {
  172. var tempZ = (int)Math.Round((modelX - 1) * k + b);
  173. tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1);
  174. point3 = new Point3DF(modelX - 1, 0, tempZ);
  175. }
  176. }
  177. points = new CoplanarPoints(point1, point2, point3);
  178. return true;
  179. }
  180. catch (Exception excep)
  181. {
  182. LogHelper.ErrorLog("ContourHelper.FitLine Failed:" + excep);
  183. return false;
  184. }
  185. }
  186. /// <summary>
  187. /// 获得血管所在切面上的三个点的空间坐标
  188. /// </summary>
  189. /// <param name="modelResults"></param>
  190. /// <returns></returns>
  191. private CoplanarPoints GetVesselSlicePoints(Dictionary<int, AIDiagResultPerImg> modelResults, int modelX, int modelY, int modelZ)
  192. {
  193. if (modelResults == null)
  194. {
  195. throw new ArgumentNullException("modelResults");
  196. }
  197. // 从modelResults中找到所有颈动脉血管的中心
  198. List<Point3D> vesselCenters = new List<Point3D>();
  199. for (int ni = 0; ni < modelResults.Count; ni++)
  200. {
  201. // 可能一幅图上会有两个颈动脉,所以这里要找到所有的颈动脉
  202. var vessels = Array.FindAll(modelResults[ni].DiagResultsForEachOrgan, x => x.Organ == EnumOrgans.CarotidArtery);
  203. if (vessels != null)
  204. {
  205. foreach (var vessel in vessels)
  206. {
  207. var x = vessel.OrganBoundBox.Left + vessel.OrganBoundBox.Width / 2;
  208. var y = vessel.OrganBoundBox.Top + vessel.OrganBoundBox.Height / 2;
  209. // 因为这里模型已经被采样成xyz方向均匀分布的立方体,所以z直接为ni即可
  210. var z = ni;
  211. vesselCenters.Add(new Point3D(x, y, z));
  212. }
  213. }
  214. }
  215. // 点数不够,直接返回
  216. if (vesselCenters.Count < 3)
  217. {
  218. return CoplanarPoints.Empty;
  219. }
  220. // 计算每个中心点在y=0平面上的投影(因为默认扫查时是颈动脉横切面,且是沿着颈动脉的长轴方向连续扫查)
  221. // 因此求y=0平面上的投影,能得到颈动脉的长轴切面
  222. var projectPoints = new Point2D[vesselCenters.Count];
  223. for (int ni = 0; ni < vesselCenters.Count; ni++)
  224. {
  225. projectPoints[ni].X = vesselCenters[ni].X;
  226. projectPoints[ni].Y = vesselCenters[ni].Z;
  227. }
  228. // 计算所有投影点的拟合直线
  229. if (!ContourHelper.FitLines(projectPoints, out var line, true))
  230. {
  231. return CoplanarPoints.Empty;
  232. }
  233. // 计算三个过拟合直线,且和y轴平行的平面上的点
  234. if (!GetCoplanarPointsWithProjectionLine(line, modelX, modelY, modelZ, out var points))
  235. {
  236. return CoplanarPoints.Empty;
  237. }
  238. return points;
  239. }
  240. private void OnErrorOccur(object sender, ErrorEventArgs e)
  241. {
  242. if (!_disposing)
  243. {
  244. NotifyError?.Invoke(this, e);
  245. }
  246. }
  247. private void OnLogWrite(object sender, LogEventArgs e)
  248. {
  249. if (!_disposing)
  250. {
  251. NotifyLog?.Invoke(this, e);
  252. }
  253. }
  254. private void DoDispose()
  255. {
  256. if (!_disposing)
  257. {
  258. _diagSystem?.Dispose();
  259. }
  260. }
  261. #endregion
  262. }
  263. }