GetModelVesselAndPlaque.cs 21 KB

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