GetModelVesselAndPlaque.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. using Emgu.CV;
  5. using Emgu.CV.Structure;
  6. using System.Drawing;
  7. using System.Linq;
  8. using Emgu.CV.Util;
  9. using WingAIDiagnosisService.Carotid.Utilities.CntkSeg;
  10. using WingServerCommon.Log;
  11. using WingAIDiagnosisService.Carotid.MathTools;
  12. using AI.DiagSystem;
  13. using AI.Common;
  14. using System.IO;
  15. using AI.Common.Log;
  16. namespace WingAIDiagnosisService.Carotid.Utilities
  17. {
  18. public class OneImageArteryContours
  19. {
  20. //图像序号
  21. public int SerialNumber { get; }
  22. //动脉轮廓
  23. public List<CntkDetectResult> ArteryContours { get; }
  24. public OneImageArteryContours(int serialNumber, List<CntkDetectResult> arteryContours)
  25. {
  26. SerialNumber = serialNumber;
  27. ArteryContours = arteryContours;
  28. }
  29. }
  30. public struct OnePlaqueContours
  31. {
  32. public int SerialNumber;
  33. public Rectangle ImageRect;
  34. public Point[] ArteryContour;
  35. public Point[] PlaqueContour;
  36. public double PlaqueArea;
  37. //狭窄率
  38. public float Ratio;
  39. //在原图中的斑块外界矩形
  40. public RotatedRect RealRect;
  41. //在原图中的斑块中心
  42. public Point RealCenterPoint;
  43. public float Credibility;
  44. }
  45. public struct VesselYEdge
  46. {
  47. public int AvgTop;
  48. public int AvgBottom;
  49. }
  50. public class ArteryImage
  51. {
  52. //图像序号
  53. public int SerialNumber { get; }
  54. //动脉轮廓
  55. public Point[] ArteryContour { get; }
  56. //动脉图像
  57. public Image<Gray, byte> Image { get; }
  58. //图像在原图中roi
  59. public Rectangle Rect { get; }
  60. //roi的中心
  61. public Point RectCenterPoint { get; }
  62. //把大图像轮廓点,转成roi图像轮廓点坐标
  63. private Point[] ChangeToRoiCoordinate(Point[] arteryContour, Rectangle rect)
  64. {
  65. var len = arteryContour.Length;
  66. var left = rect.Left;
  67. var top = rect.Top;
  68. var newArteryContour = new Point[len];
  69. for (var i = 0; i < len; i++)
  70. {
  71. newArteryContour[i] = new Point(arteryContour[i].X - left, arteryContour[i].Y - top);
  72. }
  73. return newArteryContour;
  74. }
  75. public ArteryImage(int serialNumber, Point[] arteryContour, Image<Gray, byte> image, Rectangle rect)
  76. {
  77. SerialNumber = serialNumber;
  78. ArteryContour = ChangeToRoiCoordinate(arteryContour, rect);
  79. Image = image;
  80. Rect = rect;
  81. RectCenterPoint = new Point(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2);
  82. }
  83. }
  84. public class GetModelVesselAndPlaque : IDisposable
  85. {
  86. #region private variable
  87. private AIDiagSystem _diagSystem;
  88. private static GetModelVesselAndPlaque _instance;
  89. private bool _isInitializationSuccess = false;
  90. private ModelSize _modelSize = new ModelSize();
  91. private List<OneImageArteryContours> _allArteryContours = new List<OneImageArteryContours>();
  92. private List<OnePlaqueContours> _allPlaqueContours = new List<OnePlaqueContours>();
  93. private static readonly object _locker = new object();
  94. private RawImage _image = new RawImage(EnumColorType.Gray8);
  95. #endregion
  96. #region public funcs
  97. /// <summary>
  98. /// 得到一个AAAA的实例
  99. /// </summary>
  100. public static GetModelVesselAndPlaque Instance
  101. {
  102. get => _instance ?? (_instance = new GetModelVesselAndPlaque());
  103. }
  104. /// <summary>
  105. /// 是否已初始化
  106. /// </summary>
  107. /// <returns></returns>
  108. public bool InitializationStatus()
  109. {
  110. return _isInitializationSuccess;
  111. }
  112. /// <summary>
  113. /// 初始化
  114. /// </summary>
  115. /// <param name="modelArtery"></param>
  116. /// <param name="modelPlaque"></param>
  117. /// <returns></returns>
  118. public bool Initialization(EnumPerformance performance, string modelFolder)
  119. {
  120. _diagSystem = new AIDiagSystem(performance, modelFolder, EnumInferWorkName.CarotidArteryPlaque, isCropped: true);
  121. _diagSystem.NotifyError += AIdiagSystem_NotifyError;
  122. _diagSystem.NotifyLog += AIdiagSystem_NotifyLog;
  123. _isInitializationSuccess = true;
  124. return true;
  125. }
  126. private void AIdiagSystem_NotifyError(object sender, ErrorEventArgs e)
  127. {
  128. Logger.WriteLineError("AIdiagSystem_NotifyError:" + e.GetException());
  129. }
  130. private void AIdiagSystem_NotifyLog(object sender, LogEventArgs e)
  131. {
  132. if (e != null && !string.IsNullOrEmpty(e.Msg))
  133. {
  134. switch (e.LogType)
  135. {
  136. case EnumLogType.InfoLog:
  137. Logger.WriteLineInfo($"AIdiagSystem_NotifyLog:{e.Msg}");
  138. break;
  139. case EnumLogType.ErrorLog:
  140. Logger.WriteLineError($"AIdiagSystem_NotifyLog:{e.Msg}");
  141. break;
  142. case EnumLogType.WarnLog:
  143. Logger.WriteLineWarn($"AIdiagSystem_NotifyLog:{e.Msg}");
  144. break;
  145. case EnumLogType.FatalLog:
  146. Logger.WriteLineError($"AIdiagSystem_NotifyLog:{e.Msg}");
  147. break;
  148. default:
  149. Logger.WriteLineInfo(e.Msg);
  150. break;
  151. }
  152. }
  153. }
  154. /// <summary>
  155. /// 获得所有斑块的轮廓
  156. /// </summary>
  157. /// <returns></returns>
  158. public List<OnePlaqueContours> GetAllPlaqueContours()
  159. {
  160. return _allPlaqueContours;
  161. }
  162. /// <summary>
  163. /// 获得有内膜的血管(颈动脉?)
  164. /// </summary>
  165. /// <returns></returns>
  166. public List<OneImageArteryContours> GetIntimaVessel()
  167. {
  168. try
  169. {
  170. lock (_locker)
  171. {
  172. var count = _allArteryContours.Count;
  173. if (count == 0)
  174. {
  175. return new List<OneImageArteryContours>();
  176. }
  177. var arteryContoursList1 = new List<OneImageArteryContours>();
  178. var minIndex = 0;
  179. var maxIndex = _modelSize.ModelLengthZ - 1;
  180. for (var i = 0; i < count; i++)
  181. {
  182. if (_allArteryContours[i].SerialNumber < minIndex) continue;
  183. if (_allArteryContours[i] == null)
  184. {
  185. continue;
  186. }
  187. arteryContoursList1.Add(_allArteryContours[i]);
  188. if (_allArteryContours[i].SerialNumber > maxIndex) break;
  189. }
  190. return arteryContoursList1;
  191. }
  192. }
  193. catch (Exception e)
  194. {
  195. Logger.WriteLineError($"GetModelVesselAndPlaque GetIntimaVessel have an error{e}");
  196. return new List<OneImageArteryContours>();
  197. }
  198. }
  199. /// <summary>
  200. /// Get top and bottom edge of vessel.
  201. /// </summary>
  202. /// <returns>Vessel's y edge.</returns>
  203. public VesselYEdge GetVesselYEdge()
  204. {
  205. int count = 0;
  206. int sumTopY = 0;
  207. int sumBottomY = 0;
  208. lock (_locker)
  209. {
  210. foreach (var countour in _allArteryContours)
  211. {
  212. if (countour.ArteryContours.Count > 0)
  213. {
  214. count++;
  215. sumTopY += countour.ArteryContours[0].Rect.Top;
  216. sumBottomY += countour.ArteryContours[0].Rect.Bottom;
  217. }
  218. }
  219. }
  220. int avgTopY = sumTopY / count;
  221. int avgBottomY = sumBottomY / count;
  222. return new VesselYEdge { AvgTop = avgTopY, AvgBottom = avgBottomY };
  223. }
  224. /// <summary>
  225. /// 获得Y形的起始位置?
  226. /// </summary>
  227. /// <returns></returns>
  228. public List<OneImageArteryContours> GetYBeginVessel()
  229. {
  230. lock (_locker)
  231. {
  232. var count = _allArteryContours.Count;
  233. if (count == 0)
  234. {
  235. return new List<OneImageArteryContours>();
  236. }
  237. var arteryContoursList = new List<OneImageArteryContours>();
  238. var maxIndex = _modelSize.ModelLengthZ / 4;
  239. for (var i = 0; i < count; ++i)
  240. {
  241. if (_allArteryContours[i].SerialNumber < maxIndex)
  242. {
  243. arteryContoursList.Add(_allArteryContours[i]);
  244. }
  245. else
  246. {
  247. break;
  248. }
  249. }
  250. return arteryContoursList;
  251. }
  252. }
  253. /// <summary>
  254. /// 获得Y形的终止位置?
  255. /// </summary>
  256. /// <returns></returns>
  257. public List<OneImageArteryContours> GetYEndVessel()
  258. {
  259. lock (_locker)
  260. {
  261. var count = _allArteryContours.Count;
  262. if (count == 0)
  263. {
  264. return new List<OneImageArteryContours>();
  265. }
  266. var arteryContoursList = new List<OneImageArteryContours>();
  267. var minIndex = (_modelSize.ModelLengthZ * 3) / 4;
  268. for (var i = 0; i < count; ++i)
  269. {
  270. if (_allArteryContours[i].SerialNumber > minIndex)
  271. {
  272. arteryContoursList.Add(_allArteryContours[i]);
  273. }
  274. }
  275. return arteryContoursList;
  276. }
  277. }
  278. /// <summary>
  279. /// 进行检测
  280. /// </summary>
  281. /// <param name="modelSource"></param>
  282. /// <param name="modelSize"></param>
  283. /// <returns></returns>
  284. public bool DoDetect(byte[][] modelSource, ModelSize modelSize)
  285. {
  286. _modelSize = modelSize;
  287. try
  288. {
  289. var vesselList = new List<OneImageArteryContours>();
  290. var plaqueList = new List<OnePlaqueContours>();
  291. // 每隔3帧算一次
  292. int modelImgCount = _modelSize.ModelLengthZ;
  293. int interval = 3;
  294. for (int ni = 0; ni < modelImgCount; ni += interval)
  295. {
  296. // 复制出待测图像
  297. _image.CopyFrom(modelSource[ni], _modelSize.ModelLengthX, _modelSize.ModelLengthY, 0);
  298. // 输入待测图像进行检测
  299. var diagResult = _diagSystem.EvaluateOneImage(_image);
  300. if (diagResult == null)
  301. {
  302. continue;
  303. }
  304. // 将当前图像上检测到的颈动脉血管找出来
  305. List<CntkDetectResult> detectedArtery = new List<CntkDetectResult>();
  306. foreach (var organ in diagResult.DiagResultsForEachOrgan)
  307. {
  308. // 只取每一个颈动脉OrganContours里的第一条轮廓
  309. // 注意:如有多个颈动脉,则会有多个organ=EnumOrgans.CarotidArtery的结果,每个organ里还是只有一条轮廓
  310. // (目前一个目标有多条轮廓只适用于圆环形心肌这种情况,这时心肌需要用内外两条线才能完整表示一个目标)
  311. if (organ.Organ == EnumOrgans.CarotidArtery && organ.OrganContours?.Length == 1)
  312. {
  313. // 目前AIDiagSystem中未返回organ的置信度,因此这里暂时全设为0.8 (该值不影响后续的流程)
  314. // 斑块和颈动脉 好像都是CntkLabelType.LabelOne
  315. var oneArtery = GenCntkDetectResult(0.8f, CntkLabelType.LabelOne, organ.OrganBoundBox,
  316. organ.OrganContours[0]);
  317. // 将当前轮廓添加到detectedArtery里
  318. detectedArtery.Add(oneArtery);
  319. // 将当前血管上找到的斑块 转成所需的格式 放到plaqueList里
  320. foreach (var detectedObject in organ.DetectedObjects)
  321. {
  322. if (detectedObject.Contours.Length > 0)
  323. {
  324. var onePlaque = GenCntkDetectResult(detectedObject.Confidence, CntkLabelType.LabelOne,
  325. detectedObject.BoundingBox, detectedObject.Contours[0]);
  326. OnePlaqueContours onePlaqueContours = new OnePlaqueContours();
  327. onePlaqueContours.ArteryContour = oneArtery.Contour;
  328. onePlaqueContours.ImageRect = GetExtendRect(oneArtery.Rect, new Size(_image.Width, _image.Height));
  329. onePlaqueContours.PlaqueContour = onePlaque.Contour;
  330. onePlaqueContours.Credibility = onePlaque.Credibility;
  331. onePlaqueContours.SerialNumber = ni;
  332. onePlaqueContours.PlaqueArea = GetArea(onePlaque.Contour);
  333. plaqueList.Add(onePlaqueContours);
  334. }
  335. }
  336. }
  337. }
  338. // 将有动脉的那些帧的序号和对应的动脉轮廓,存到vesselList里
  339. if (detectedArtery.Count > 0)
  340. {
  341. vesselList.Add(new OneImageArteryContours(ni, detectedArtery));
  342. }
  343. }
  344. // 去除从异常位置的颈动脉血管上找到的斑块
  345. var selectedPlaqueList = RemoveAbnormalImage(vesselList, plaqueList);
  346. // 更新_allArteryContours 和 _allPlaqueContours
  347. lock (_locker)
  348. {
  349. _allArteryContours = vesselList;
  350. if (_allArteryContours.Count == 0)
  351. {
  352. Logger.WriteLineWarn($"GetModelVesselAndPlaque DetectArtery get zero artery contours.");
  353. }
  354. _allPlaqueContours = selectedPlaqueList;
  355. }
  356. return true;
  357. }
  358. catch (Exception e)
  359. {
  360. Logger.WriteLineError($"GetModelVesselAndPlaque DoDetect have an error{e}");
  361. return false;
  362. }
  363. }
  364. /// <summary>
  365. /// 销毁
  366. /// </summary>
  367. public void Dispose()
  368. {
  369. _diagSystem.Dispose();
  370. _diagSystem = null;
  371. _instance = null;
  372. }
  373. #endregion
  374. #region private funcs
  375. private CntkDetectResult GenCntkDetectResult(float confidence, CntkLabelType label, Rect boundingBox, Point2D[] contour)
  376. {
  377. CntkDetectResult result = new CntkDetectResult();
  378. result.Credibility = confidence;
  379. result.LabelType = label;
  380. result.Rect = new Rectangle(boundingBox.Left, boundingBox.Top,
  381. boundingBox.Width, boundingBox.Height);
  382. int contourLen = contour.Length;
  383. Point[] contourDst = new Point[contourLen];
  384. for (int ki = 0; ki < contourLen; ki++)
  385. {
  386. contourDst[ki].X = contour[ki].X;
  387. contourDst[ki].Y = contour[ki].Y;
  388. }
  389. result.Contour = contourDst;
  390. // 轮廓面积
  391. result.ContourArea = (float)CvInvoke.ContourArea(new VectorOfPoint(contourDst));
  392. return result;
  393. }
  394. private Rectangle GetExtendRect(Rectangle rect1, Size imageSize)
  395. {
  396. var change = 20;
  397. var left = Math.Max(0, rect1.Left - change);
  398. var top = Math.Max(0, rect1.Top - change);
  399. var endW = Math.Min(left + rect1.Width + 2 * change, imageSize.Width - 1);
  400. var endH = Math.Min(top + rect1.Height + 2 * change, imageSize.Height - 1);
  401. return new Rectangle(left, top, endW - left, endH - top);
  402. }
  403. private double GetArea(Point[] points)
  404. {
  405. var vector = new VectorOfPoint(points);
  406. return CvInvoke.ContourArea(vector);
  407. }
  408. private List<OnePlaqueContours> RemoveAbnormalImage(List<OneImageArteryContours> vesselList, List<OnePlaqueContours> plaqueList)
  409. {
  410. if (plaqueList.Count <= 0)
  411. {
  412. return plaqueList;
  413. }
  414. // 数量太少的不好拟合直线,直接返回
  415. var count = vesselList.Count;
  416. if (count < 10)
  417. {
  418. return plaqueList;
  419. }
  420. // 将所有血管的中心点取出来
  421. List<Point> points = new List<Point>();
  422. for (var i = 0; i < count; ++i)
  423. {
  424. // 一幅图上有多个血管,则取多个血管外边框的最左最右最上最下点来计算中心
  425. int left = 0, top = 0, right = 0, bottom = 0;
  426. var arterys = vesselList[i].ArteryContours;
  427. for (int ni = 0; ni < arterys.Count; ni++)
  428. {
  429. if (ni == 0)
  430. {
  431. left = arterys[ni].Rect.Left;
  432. top = arterys[ni].Rect.Top;
  433. right = arterys[ni].Rect.Right;
  434. bottom = arterys[ni].Rect.Bottom;
  435. }
  436. else
  437. {
  438. left = Math.Min(left, arterys[ni].Rect.Left);
  439. top = Math.Min(top, arterys[ni].Rect.Top);
  440. right = Math.Max(right, arterys[ni].Rect.Right);
  441. bottom = Math.Max(bottom, arterys[ni].Rect.Bottom);
  442. }
  443. }
  444. var x = (left + right) / 2;
  445. var y = (top + bottom) / 2;
  446. points.Add(new Point(x, y));
  447. }
  448. // 将这些点拟合成一条线
  449. var line = MathTools2D.CalculateFittedLine(points);
  450. var pointDiff = new LineFittingError[count];
  451. //计算每个点的差值
  452. double sumDiff = 0;
  453. for (var i = 0; i < count; ++i)
  454. {
  455. //var newX = line[0] * points[i].X + line[2];
  456. double diff = MathTools2D.GetDistaceBetweenPointAndLine(line, points[i]);
  457. sumDiff += diff;
  458. pointDiff[i] = new LineFittingError()
  459. {
  460. SerialNumber = i,
  461. OriginalPoint = points[i],
  462. DiffValue = diff
  463. };
  464. }
  465. //计算差值的,平均值和标准差
  466. var averageDiff = sumDiff / count;
  467. double sumStd = 0;
  468. for (var i = 0; i < count; ++i)
  469. {
  470. var diff = pointDiff[i].DiffValue;
  471. sumStd += (diff - averageDiff) * (diff - averageDiff);
  472. }
  473. sumStd = Math.Sqrt(sumStd / count);
  474. var minDiff = averageDiff - 3 * sumStd;
  475. var maxDiff = averageDiff + 3 * sumStd;
  476. //删除差值大于平均值加减3倍的标准差的点
  477. List<int> removeFlag = new List<int>();
  478. for (var i = 0; i < count; ++i)
  479. {
  480. var diff = pointDiff[i].DiffValue;
  481. if (diff < minDiff || diff > maxDiff)
  482. {
  483. int serialNumber = vesselList[i].SerialNumber;
  484. if (!removeFlag.Contains(serialNumber))
  485. {
  486. removeFlag.Add(serialNumber);
  487. }
  488. pointDiff[i].DiffValue = -100000;
  489. }
  490. }
  491. //去掉5%的数据
  492. var count1 = count / 20;
  493. var diffList = pointDiff.ToList().OrderByDescending(o => o.DiffValue);
  494. var diffList1 = diffList.Skip(count1).ToList();
  495. var maxDiffValue = diffList1[0].DiffValue;
  496. for (var i = 0; i < count; ++i)
  497. {
  498. if (pointDiff[i].DiffValue > maxDiffValue)
  499. {
  500. int serialNumber = vesselList[i].SerialNumber;
  501. if (!removeFlag.Contains(serialNumber))
  502. {
  503. removeFlag.Add(serialNumber);
  504. }
  505. }
  506. }
  507. int plaqueCount = plaqueList.Count;
  508. for (var i = plaqueCount - 1; i >= 0; i--)
  509. {
  510. var serialNumber = plaqueList[i].SerialNumber;
  511. if (removeFlag.Contains(serialNumber))
  512. {
  513. plaqueList.RemoveAt(i);
  514. }
  515. }
  516. return plaqueList;
  517. }
  518. #endregion
  519. }
  520. }