GetModelVesselAndPlaque.cs 21 KB

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