1
0

IntimaHelper.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. using System.Collections.Generic;
  5. using System.Runtime.InteropServices;
  6. using AI.Common;
  7. using AI.Common.Log;
  8. using AI.Common.Tools;
  9. using AI.DiagSystem;
  10. using AI.DiagSystem.Workers.TaskResults;
  11. using static AI.Common.InferenceNetworkUtils;
  12. namespace Reconstruction3DHelper
  13. {
  14. /// <summary>
  15. /// 内膜测量结果, 用于C++输出的接口
  16. /// </summary>
  17. [StructLayout(LayoutKind.Sequential)]
  18. public struct MeasureResults
  19. {
  20. //检测是否成功
  21. public bool isSuccess;
  22. //平均厚度
  23. public float AverageThickness;
  24. //最小厚度
  25. public float MinThickness;
  26. //最大厚度
  27. public float MaxThickness;
  28. //测量起点,单个
  29. public StructMyPoint startPoint;
  30. //测量终点,单个
  31. public StructMyPoint endPoint;
  32. }
  33. /// <summary>
  34. /// 预测的organ后处理信息
  35. /// </summary>
  36. public struct PostProcessInputInfo
  37. {
  38. private int[] _labelID;
  39. private int[] _singleContourLenList;
  40. private int _idx;
  41. private InferenceNetworkUtils.StructMyPoint[] _contoursPoints;
  42. public int[] LabelID
  43. {
  44. get { return _labelID; }
  45. set { _labelID = value; }
  46. }
  47. public int[] SingleContourLenList
  48. {
  49. get { return _singleContourLenList; }
  50. set { _singleContourLenList = value; }
  51. }
  52. public int Idx
  53. {
  54. get { return _idx; }
  55. set { _idx = value; }
  56. }
  57. public InferenceNetworkUtils.StructMyPoint[] ContoursPoints
  58. {
  59. get { return _contoursPoints; }
  60. set { _contoursPoints = value; }
  61. }
  62. }
  63. /// <summary>
  64. /// 错误代码
  65. /// </summary>
  66. public enum EnumErrorCode
  67. {
  68. None = 0,
  69. selectContours = 1,
  70. skeletonSega = 2,
  71. removeDiscreteValue = 3,
  72. extractSegment = 4,
  73. fitLineAndCalc = 5,
  74. calaThickness = 6,
  75. intimaProcess = 7,
  76. interfaceOfIntimeThickness = 8
  77. };
  78. /// <summary>
  79. /// 内膜自动测量
  80. /// </summary>
  81. public class IntimaHelper
  82. {
  83. #region field
  84. private AIDiagSystem _diagSystem;
  85. private bool _disposing = false;
  86. #endregion
  87. #region events
  88. /// <summary>
  89. /// 通知订阅者,推理过程中发生了错误
  90. /// </summary>
  91. public event EventHandler<ErrorEventArgs> NotifyError;
  92. /// <summary>
  93. /// 通知订阅者,推理过程中有log要记录
  94. /// </summary>
  95. public event EventHandler<LogEventArgs> NotifyLog;
  96. #endregion
  97. #region constructor
  98. public IntimaHelper(EnumPerformance performace = EnumPerformance.Low)
  99. {
  100. _diagSystem = new AIDiagSystem(performace, defaultInferWorkName: EnumInferWorkName.CarotidArteryPlaque);
  101. _diagSystem.IsCropped = true;
  102. // _diagSystem.EnableLesionSeg = true;
  103. // _diagSystem.EnableDescription = true;
  104. _diagSystem.NotifyError += OnErrorOccur;
  105. _diagSystem.NotifyLog += OnLogWrite;
  106. _diagSystem.EnableDebugLogWrite = true;
  107. }
  108. ~IntimaHelper()
  109. {
  110. DoDispose();
  111. }
  112. #endregion
  113. #region DllImport
  114. [DllImport(@"IntimediaMeasurement.dll", CallingConvention = CallingConvention.Cdecl)]
  115. [return: MarshalAs(UnmanagedType.I1)]
  116. public static extern bool InterfaceOfIntimeThickness(IntPtr imgData, int oriWidth, int oriHeight, int objNum, int[] labelID, int[] singleContourLenList,
  117. IntPtr allContourPoints, bool calcLower, bool calcUpper, ref MeasureResults thicknessAnt, ref MeasureResults thicknessPost);
  118. [DllImport(@"IntimediaMeasurement.dll", CallingConvention = CallingConvention.Cdecl)]
  119. [return: MarshalAs(UnmanagedType.I1)]
  120. public static extern void GetErrorCodeAndMsg(ref EnumErrorCode errorCode, StringBuilder error, int errorMaxLen);
  121. #endregion
  122. #region private
  123. private List<int> CalCenter(AI.Common.Point2D[] objContour)
  124. {
  125. int minX = objContour[0].X;
  126. int minY = objContour[0].Y;
  127. int maxX = 0;
  128. int maxY = 0;
  129. foreach (AI.Common.Point2D p in objContour)
  130. {
  131. if (p.X < minX) minX = p.X;
  132. if (p.Y < minY) minY = p.Y;
  133. if (p.X > maxX) maxX = p.X;
  134. if (p.Y > maxY) maxY = p.Y;
  135. }
  136. int centerX = (minX + maxX) / 2;
  137. int centerY = (minY + maxY) / 2;
  138. List<int> center = new List<int> { centerX, centerY };
  139. return center;
  140. }
  141. /// <summary>
  142. /// 用来过滤超出左右边界的point
  143. /// </summary>
  144. /// <param name="inputArray"></param>
  145. /// <param name="cropLeft"></param>
  146. /// <param name="cropRight"></param>
  147. /// <returns></returns>
  148. private void removePointOutofBBox(ref AI.Common.Point2D[] inputArray, int cropLeft, int cropRight)
  149. {
  150. AI.Common.Point2D[] filteredPoints = new AI.Common.Point2D[inputArray.Length];
  151. int filteredIndex = 0;
  152. for (int ni = 0; ni < inputArray.Length; ni++)
  153. {
  154. int xCoordinate = inputArray[ni].X;
  155. if (xCoordinate >= cropLeft && xCoordinate <= cropRight)
  156. {
  157. filteredPoints[filteredIndex] = inputArray[ni];
  158. filteredIndex++;
  159. }
  160. }
  161. AI.Common.Point2D[] actualFilteredPoints = new AI.Common.Point2D[filteredIndex];
  162. Array.Copy(filteredPoints, actualFilteredPoints, filteredIndex);
  163. inputArray = actualFilteredPoints;
  164. }
  165. /// <summary>
  166. /// 生成c++后处理所需要的格式
  167. /// </summary>
  168. /// <param name="diagResult"></param>
  169. /// <returns></returns>
  170. private PostProcessInputInfo ConvertResultToPostProcessInput(AIDiagResultPerImg diagResult, AI.Common.Rect rect)
  171. {
  172. int cropLeft = rect.Left;
  173. int cropRight = rect.Left + rect.Width;
  174. // 每个organ和下面的lesions 组成一个dict
  175. Dictionary<DetectedOrgan, List<DetectedLesion>> outlesions = new Dictionary<DetectedOrgan, List<DetectedLesion>>();
  176. for (int ni = 0; ni < diagResult.DiagResultsForEachOrgan.Length; ni++)
  177. {
  178. //分两种情况,情况 1,如果有两个或以上organ,横切的情况,模型结果输出是将organ和lesion都放一起了,就需要拆开
  179. if (diagResult.DiagResultsForEachOrgan[ni].OrganContours.Length >= 2 && diagResult.DiagResultsForEachOrgan[ni].Organ == EnumOrgans.CarotidArtery)
  180. {
  181. Dictionary<List<int>, DetectedLesion> lesionDict = new Dictionary<List<int>, DetectedLesion>();
  182. Dictionary<List<int>, DetectedOrgan> organDict = new Dictionary<List<int>, DetectedOrgan>();
  183. for (int i = 0; i < diagResult.DiagResultsForEachOrgan[ni].OrganContours.Length; i++)
  184. {
  185. removePointOutofBBox(ref diagResult.DiagResultsForEachOrgan[ni].OrganContours[i], cropLeft, cropRight);
  186. AI.Common.Point2D[] organContour = diagResult.DiagResultsForEachOrgan[ni].OrganContours[i];
  187. // 获取organ的center
  188. List<int> centerOrgan = CalCenter(organContour);
  189. AI.Common.Point2D[][] organContours = { organContour };
  190. DetectedOrgan organ = new DetectedOrgan(EnumScanParts.Neck, EnumOrgans.CarotidArtery, 1,
  191. diagResult.DiagResultsForEachOrgan[ni].OrganBoundBox, organContours);
  192. organDict[centerOrgan] = organ;
  193. }
  194. for (int j = 0; j < diagResult.DiagResultsForEachOrgan[ni].DetectedObjects.Length; j++)
  195. {
  196. if (diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Label == 1)
  197. {
  198. removePointOutofBBox(ref diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Contours[0], cropLeft, cropRight);
  199. List<int> centerLesion = new List<int>{ diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Left + diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Width / 2,
  200. diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Top + diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Height / 2};
  201. DetectedLesion lesion = new DetectedLesion(EnumScanParts.Neck, EnumOrgans.Neck, AI.DiagSystem.Workers.InferenceNetworks.EnumLesionType.FocalLesion,
  202. diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Label, diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Confidence,
  203. 0.0f, 0.0f, 0.0f, diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox, diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Contours);
  204. lesionDict[centerLesion] = lesion;
  205. }
  206. }
  207. // 初始化 outlesions 的每个 organ 的 value;
  208. foreach (List<int> organCenter in organDict.Keys)
  209. {
  210. outlesions[organDict[organCenter]] = new List<DetectedLesion>();
  211. }
  212. // 先循环lesion 查找中心点距离最近的organ,组成dict
  213. foreach (List<int> lesionCenter in lesionDict.Keys)
  214. {
  215. double _minDistance = 10000;
  216. List<int> _minDistanceOrganCenter = new List<int>();
  217. foreach (List<int> organCenter in organDict.Keys)
  218. {
  219. double _distance = Math.Sqrt(Math.Pow((lesionCenter[0] - organCenter[0]), 2.0) + Math.Pow((lesionCenter[1] - organCenter[1]), 2.0));
  220. if (_distance < _minDistance)
  221. {
  222. _minDistanceOrganCenter = organCenter;
  223. _minDistance = _distance;
  224. }
  225. }
  226. outlesions[organDict[_minDistanceOrganCenter]].Add(lesionDict[lesionCenter]);
  227. }
  228. }
  229. //情况 2,只有一个organ的情况就不需要拆开
  230. else if (diagResult.DiagResultsForEachOrgan[ni].OrganContours.Length == 1)
  231. {
  232. AIDiagResultPerOrgan _diagResultsForEachOrgan = diagResult.DiagResultsForEachOrgan[ni];
  233. if (_diagResultsForEachOrgan.Organ == EnumOrgans.CarotidArtery) //颈动脉
  234. {
  235. removePointOutofBBox(ref _diagResultsForEachOrgan.OrganContours[0], cropLeft, cropRight);
  236. DetectedOrgan organ = new DetectedOrgan(EnumScanParts.Neck, EnumOrgans.CarotidArtery, 1,
  237. _diagResultsForEachOrgan.OrganBoundBox, _diagResultsForEachOrgan.OrganContours);
  238. List<DetectedLesion> lesions = new List<DetectedLesion>();
  239. foreach (DetectedObject _detectedObject in _diagResultsForEachOrgan.DetectedObjects)
  240. {
  241. if (_detectedObject.Label == 1) // 1代表斑块
  242. {
  243. removePointOutofBBox(ref _detectedObject.Contours[0], cropLeft, cropRight);
  244. lesions.Add(new DetectedLesion(EnumScanParts.Neck, EnumOrgans.Neck, AI.DiagSystem.Workers.InferenceNetworks.EnumLesionType.FocalLesion,
  245. _detectedObject.Label, _detectedObject.Confidence, 0.0f, 0.0f, 0.0f, _detectedObject.BoundingBox, _detectedObject.Contours));
  246. }
  247. outlesions[organ] = lesions;
  248. }
  249. }
  250. }
  251. }
  252. // 对分配好的organ进行后处理,若斑块超出范围,则外扩轮廓
  253. foreach (DetectedOrgan _organ in outlesions.Keys)
  254. {
  255. List<DetectedLesion> _lesions = outlesions[_organ];
  256. }
  257. // 将上面的dict 转换成输入到postproce的格式
  258. int[] labelID = new int[20]; // 初始化10
  259. int[] singleContourLenList = new int[20];
  260. int allContoursLengths = 0;
  261. int idx = 0;
  262. foreach (DetectedOrgan singleOrganRes in outlesions.Keys)
  263. {
  264. labelID[idx] = singleOrganRes.Label;
  265. int singleOrganResContoursLength = singleOrganRes.Contours[0].Length;
  266. singleContourLenList[idx] = singleOrganResContoursLength;
  267. allContoursLengths += singleOrganResContoursLength;
  268. idx++;
  269. foreach (DetectedLesion singleLesionRes in outlesions[singleOrganRes])
  270. {
  271. labelID[idx] = singleLesionRes.Label;
  272. int singleLesionResContoursLength = singleLesionRes.Contours[0].Length;
  273. singleContourLenList[idx] = singleLesionResContoursLength;
  274. allContoursLengths += singleLesionResContoursLength;
  275. idx++;
  276. }
  277. }
  278. Array.Resize(ref labelID, idx);
  279. Array.Resize(ref singleContourLenList, idx);
  280. //将所有轮廓记录在contoursPoints中
  281. InferenceNetworkUtils.StructMyPoint[] contoursPoints = new InferenceNetworkUtils.StructMyPoint[allContoursLengths];
  282. int idx2 = 0;
  283. int startIndex = 0;
  284. foreach (DetectedOrgan singleOrganRes in outlesions.Keys)
  285. {
  286. var lenOrgan = singleContourLenList[idx2];
  287. for (int ni = 0; ni < lenOrgan; ni++)
  288. {
  289. contoursPoints[startIndex + ni].X = singleOrganRes.Contours[0][ni].X;
  290. contoursPoints[startIndex + ni].Y = singleOrganRes.Contours[0][ni].Y;
  291. }
  292. idx2 += 1;
  293. startIndex += lenOrgan;
  294. foreach (DetectedLesion singleLesionRes in outlesions[singleOrganRes])
  295. {
  296. var lenLesion = singleContourLenList[idx2];
  297. for (int nj = 0; nj < lenLesion; nj++)
  298. {
  299. contoursPoints[startIndex + nj].X = singleLesionRes.Contours[0][nj].X;
  300. contoursPoints[startIndex + nj].Y = singleLesionRes.Contours[0][nj].Y;
  301. }
  302. idx2 += 1;
  303. startIndex += lenLesion;
  304. }
  305. }
  306. PostProcessInputInfo postProcessInputInfo = new PostProcessInputInfo();
  307. postProcessInputInfo.LabelID = labelID;
  308. postProcessInputInfo.SingleContourLenList = singleContourLenList;
  309. postProcessInputInfo.Idx = idx;
  310. postProcessInputInfo.ContoursPoints = contoursPoints;
  311. return postProcessInputInfo;
  312. }
  313. /// <summary>
  314. /// 计算颈动脉内中膜厚度
  315. /// </summary>
  316. /// <param name="postProcessInputInfo"></param>
  317. /// <param name="rawImage"></param>
  318. /// <returns></returns>
  319. private bool InterfaceOfIntimeThickness(PostProcessInputInfo postProcessInputInfo, RawImage rawImage, bool calcLower, bool calcUpper, ref MeasureResults thicknessAnt, ref MeasureResults thicknessPost)
  320. {
  321. RawImage _rawImageOneChannel;
  322. // 转换成单通道
  323. if (rawImage.ColorType != EnumColorType.Gray8 && rawImage.ColorType != 0)
  324. {
  325. _rawImageOneChannel = rawImage.Clone(EnumColorType.Gray8);
  326. }
  327. else
  328. {
  329. _rawImageOneChannel = rawImage;
  330. }
  331. // 获取指针
  332. GCHandle hObject1 = GCHandle.Alloc(_rawImageOneChannel.DataBuffer, GCHandleType.Pinned);
  333. IntPtr pObject1 = hObject1.AddrOfPinnedObject();
  334. GCHandle hObject2 = GCHandle.Alloc(postProcessInputInfo.ContoursPoints, GCHandleType.Pinned);
  335. IntPtr pObject2 = hObject2.AddrOfPinnedObject();
  336. if (!InterfaceOfIntimeThickness(pObject1, rawImage.Width, rawImage.Height, postProcessInputInfo.Idx,
  337. postProcessInputInfo.LabelID, postProcessInputInfo.SingleContourLenList, pObject2,
  338. calcLower, calcUpper, ref thicknessAnt, ref thicknessPost))
  339. {
  340. int errorMaxLen = 256;
  341. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  342. EnumErrorCode errorCode = EnumErrorCode.None;
  343. GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  344. throw new Exception("Failed at decoding vinno image data buffers, error code: "
  345. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  346. }
  347. // 释放
  348. if (hObject1.IsAllocated)
  349. {
  350. hObject1.Free();
  351. }
  352. if (hObject2.IsAllocated)
  353. {
  354. hObject2.Free();
  355. }
  356. return true;
  357. }
  358. private void OnErrorOccur(object sender, ErrorEventArgs e)
  359. {
  360. if (!_disposing)
  361. {
  362. NotifyError?.Invoke(this, e);
  363. }
  364. }
  365. private void OnLogWrite(object sender, LogEventArgs e)
  366. {
  367. if (!_disposing)
  368. {
  369. NotifyLog?.Invoke(this, e);
  370. }
  371. }
  372. private void DoDispose()
  373. {
  374. if (!_disposing)
  375. {
  376. _diagSystem?.Dispose();
  377. }
  378. }
  379. #endregion
  380. #region public
  381. /// <summary>
  382. /// 根据AI选取的最优平面,通过AI模型预测颈动脉轮廓,再去计算内中膜厚度
  383. /// </summary>
  384. /// <returns></returns>
  385. public IntimaDetectResult DetectIntima(byte[] imageByte, int imageWidth, int imageHeight, float physicalPerPixel, bool enableAnt, bool enablePost)
  386. {
  387. try
  388. {
  389. if (imageByte.Length == 0)
  390. {
  391. LogHelper.ErrorLog($"DetectIntima, get intima image failed!");
  392. return new IntimaDetectResult();
  393. }
  394. //AI检测
  395. AI.Common.RawImage rawImgToEval = null;
  396. rawImgToEval = new RawImage(imageByte, imageWidth, imageHeight, AI.Common.EnumColorType.Gray8);
  397. AI.Common.Rect rect = new AI.Common.Rect(0, 0, 0, 0);
  398. UsImageRegionSegHelper.CropWithCvCore(rawImgToEval, out rect);
  399. var diagResult = _diagSystem.EvaluateOneImage(rawImgToEval);
  400. //根据AI结果计算内中膜厚度
  401. if (diagResult.DiagResultsForEachOrgan[0].Organ == EnumOrgans.CarotidArtery) //预测出颈动脉轮廓
  402. {
  403. // 将结果转换为后处理的输入
  404. var postProcessInputInfo = ConvertResultToPostProcessInput(diagResult, rect);
  405. // 计算内中膜厚度
  406. MeasureResults thicknessAnt = new MeasureResults();
  407. MeasureResults thicknessPost = new MeasureResults();
  408. var thicknessRe = InterfaceOfIntimeThickness(postProcessInputInfo, rawImgToEval, enablePost, enableAnt, ref thicknessAnt, ref thicknessPost);
  409. // 测量结果统计
  410. IntimaDetectResult intimaResult = new IntimaDetectResult();
  411. if (thicknessAnt.isSuccess)
  412. {
  413. intimaResult.AntIntima = new IntimaProperty();
  414. intimaResult.AntIntima.AverageThickness = thicknessAnt.AverageThickness * physicalPerPixel;
  415. intimaResult.AntIntima.MaxThickness = thicknessAnt.MaxThickness * physicalPerPixel;
  416. intimaResult.AntIntima.MinThickness = thicknessAnt.MinThickness * physicalPerPixel;
  417. intimaResult.AntIntima.PointLower = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessAnt.startPoint.X, thicknessAnt.startPoint.Y) };
  418. intimaResult.AntIntima.PointUpper = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessAnt.endPoint.X, thicknessAnt.endPoint.Y) };
  419. intimaResult.AntIntima.intimaRect = new IntimaRect
  420. {
  421. Left = thicknessAnt.startPoint.X,
  422. Top = thicknessAnt.startPoint.Y - 30,
  423. Width = thicknessAnt.endPoint.X - thicknessAnt.startPoint.X,
  424. Height = thicknessAnt.endPoint.Y - thicknessAnt.startPoint.Y + 60
  425. };
  426. intimaResult.AntIntima.isSuccess = true;
  427. }
  428. if (thicknessPost.isSuccess)
  429. {
  430. intimaResult.PostIntima = new IntimaProperty();
  431. intimaResult.PostIntima.AverageThickness = thicknessPost.AverageThickness * physicalPerPixel;
  432. intimaResult.PostIntima.MaxThickness = thicknessPost.MaxThickness * physicalPerPixel;
  433. intimaResult.PostIntima.MinThickness = thicknessPost.MinThickness * physicalPerPixel;
  434. intimaResult.PostIntima.PointLower = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessPost.startPoint.X, thicknessPost.startPoint.Y) };
  435. intimaResult.PostIntima.PointUpper = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessPost.endPoint.X, thicknessPost.endPoint.Y) };
  436. intimaResult.PostIntima.intimaRect = new IntimaRect
  437. {
  438. Left = thicknessPost.startPoint.X,
  439. Top = thicknessPost.startPoint.Y - 30,
  440. Width = thicknessPost.endPoint.X - thicknessPost.startPoint.X,
  441. Height = thicknessPost.endPoint.Y - thicknessPost.startPoint.Y + 60
  442. };
  443. intimaResult.PostIntima.isSuccess = true;
  444. }
  445. return intimaResult;
  446. }
  447. else
  448. {
  449. return new IntimaDetectResult();
  450. }
  451. }
  452. catch (Exception ex)
  453. {
  454. LogHelper.ErrorLog("IntimaHelper.DetectIntima Failed:" + ex);
  455. return new IntimaDetectResult();
  456. }
  457. }
  458. #endregion
  459. }
  460. }