AutoEFCalculation.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. using AI.Common;
  2. using AI.Common.Log;
  3. using AI.Common.Tools;
  4. using System;
  5. using System.Collections.Concurrent;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Runtime.InteropServices;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Timers;
  13. namespace AutoEFInferenceCalcLib
  14. {
  15. /// <summary>
  16. /// AutoEF 的计算
  17. /// </summary>
  18. public class AutoEFCalculation
  19. {
  20. #region field
  21. private int _numCpu = 0;
  22. private int _layerCount = 20;
  23. private int _edControlLayersCount = 12;
  24. private int _esControlLayersCount = 10;
  25. private static InferenceCore _inferenceCore = new InferenceCore();
  26. private List<IInferenceNetwork> _inferNets = new List<IInferenceNetwork>();
  27. private bool _initialized = false;
  28. private ConcurrentDictionary<string, CardiacCycleRecognition> _calculations = new ConcurrentDictionary<string, CardiacCycleRecognition>();
  29. //超过一定时间阈值没有再塞图,则删除该计算,以便减少内存占用和避免内存溢出的风险
  30. private double _noNewImagePushedTimeThreshold = 1200;
  31. private bool _closing = false;
  32. private ConcurrentQueue<ImageInfo> _imageInfoQueue = new ConcurrentQueue<ImageInfo>();
  33. //将处理线程唤醒的一个时间,设置初始值为true,表示有信号,
  34. //有信号的时候,_threadAwakeEvent.WaitOne不会阻塞,没有信号的时候则会阻塞
  35. //调用_threadAwakeEvent.Set,将对象设置为有信号状态
  36. //调用_threadAwakeEvent.Reset,将对象重置为无信号状态
  37. private readonly ManualResetEvent _threadAwakeEvent = new ManualResetEvent(true);
  38. private readonly object _processThreadLocker = new object();
  39. private List<Thread> _threads = new List<Thread>();
  40. #endregion
  41. #region properties
  42. public int NumCpu { get => _numCpu; }
  43. public int LayerCount { get => _layerCount; }
  44. public int EDControlLayersCount { get => _edControlLayersCount; }
  45. public int ESControlLayersCount { get => _esControlLayersCount; }
  46. #endregion
  47. #region cpp dll中的数据结构
  48. /// <summary>
  49. /// cpp中点的结构
  50. /// </summary>
  51. [StructLayout(LayoutKind.Sequential)]
  52. internal struct StructPointCpp
  53. {
  54. public int x;
  55. public int y;
  56. }
  57. /// <summary>
  58. /// cpp中的轮廓外接框结构
  59. /// </summary>
  60. [StructLayout(LayoutKind.Sequential)]
  61. internal struct StructRect
  62. {
  63. public int left;
  64. public int top;
  65. public int width;
  66. public int height;
  67. }
  68. [StructLayout(LayoutKind.Sequential)]
  69. internal struct StructEndPoints
  70. {
  71. public StructPointCpp leftPoint;
  72. public StructPointCpp rightPoint;
  73. }
  74. /// <summary>
  75. /// 单帧图像计算的左心室相关
  76. /// </summary>
  77. [StructLayout(LayoutKind.Sequential)]
  78. internal struct StructCppLVVolume
  79. {
  80. public double volume;
  81. public int innerContLen;
  82. public StructPointCpp apexPoint;
  83. public StructEndPoints endPoints;
  84. }
  85. #endregion
  86. #region import dll
  87. [DllImport(@"AutoEFPostProcess.dll", CallingConvention = CallingConvention.Cdecl)]
  88. [return: MarshalAs(UnmanagedType.I1)]
  89. internal static extern bool LVVolumeCalculate(int oriWidth, int oriHeight, float cmPerPixel, int layerCount,
  90. int edControlLayersCount, int esControlLayerCount, int[] controlLayers, IntPtr pDetectedObject,
  91. int contoureLen, IntPtr pRect, IntPtr slicePoints, IntPtr innerContour, IntPtr controlPoints, ref StructCppLVVolume pLvVolume);
  92. #endregion
  93. #region event
  94. /// <summary>
  95. /// 通知订阅者,推理过程中发现了错误
  96. /// </summary>
  97. public event EventHandler<ErrorEventArgs> NotifyError;
  98. /// <summary>
  99. /// 通知订阅者,推理过程中产生了log要记录
  100. /// </summary>
  101. public event EventHandler<LogEventArgs> NotifyLog;
  102. #endregion
  103. #region public func
  104. /// <summary>
  105. /// 构造函数
  106. /// </summary>
  107. /// <param name="numCpu"></param>
  108. /// <param name="netDir"></param>
  109. /// <param name="layerCount"></param>
  110. /// <param name="edControlLayersCount"></param>
  111. /// <param name="esControlLayerCount"></param>
  112. /// <exception cref="Exception"></exception>
  113. public AutoEFCalculation(int numCpu = 1, string netDir = "", int layerCount = 20, int edControlLayersCount = 12,
  114. int esControlLayerCount = 10)
  115. {
  116. try
  117. {
  118. if (numCpu <= 0)
  119. {
  120. throw new ArgumentOutOfRangeException("numCpu", "numCpu should be greater than 0.");
  121. }
  122. _numCpu = Math.Max(1, numCpu);
  123. _layerCount = layerCount;
  124. _edControlLayersCount = EDControlLayersCount;
  125. _esControlLayersCount = ESControlLayersCount;
  126. //加载网络
  127. var inferNet = new InferNetOnnxAutoEFCalculation();
  128. if (!inferNet.ModelLoaded)
  129. {
  130. byte[] trainedNetwork = InferenceNetworkUtils.ReadNetworkDataFromFile(netDir,
  131. inferNet.NetworkName, inferNet.HashCode);
  132. //设置inferenceCore的参数
  133. inferNet.LoadNetwork(_inferenceCore, EnumDeviceType.CPU, trainedNetwork);
  134. }
  135. _inferNets.Add(inferNet);
  136. for (int i = 0; i < _numCpu; i++)
  137. {
  138. var netClone = _inferNets[0].Clone(EnumCloneMethod.Clone);
  139. _inferNets.Add(netClone);
  140. }
  141. _initialized = true;
  142. }
  143. catch (Exception excep)
  144. {
  145. #if ENABLE_AUTOEF_DEBUG
  146. LogHelper.EnableLogWrite = true;
  147. LogHelper.ErrorLog("AutoEF.AutoEFCalculation.Error:", excep.Message);
  148. #endif
  149. throw new Exception("Failed at Loading AutoEFCalculation:" + excep);
  150. }
  151. }
  152. /// <summary>
  153. /// 开始一次新的计算
  154. /// 在整个计算过程中的图像,cmPerPixel不应该发生变化
  155. /// </summary>
  156. /// <param name="totalTime"></param>
  157. /// <param name="cmPerPixel"></param>
  158. /// <param name="intervalTime"></param>
  159. /// <returns></returns>
  160. /// <exception cref="Exception"></exception>
  161. public string StartCalculation(double totalTime, float cmPerPixel, double intervalTime = 0)
  162. {
  163. try
  164. {
  165. if (!_initialized)
  166. {
  167. throw new Exception("Uninitialized before call StartCalculation!");
  168. }
  169. if (!(totalTime > 0))
  170. {
  171. throw new ArgumentOutOfRangeException("The value of totalTime must be greater than 0!");
  172. }
  173. if (!(cmPerPixel > 0))
  174. {
  175. throw new ArgumentOutOfRangeException("The value of cmPerPixel must be greater than 0!");
  176. }
  177. if (intervalTime > 500)
  178. {
  179. throw new ArgumentOutOfRangeException("The value of intervalTime must be less than 0.5s!");
  180. }
  181. //判断之前的计算是否已经过了一定的时间,过了就清理掉,防止占用内存
  182. if (_calculations.Count != 0)
  183. {
  184. var keys = _calculations.Keys.ToList();
  185. foreach (var key in keys)
  186. {
  187. TimeSpan timeDiff = DateTime.Now - _calculations[key].LastPushedImageSucceedTime;
  188. if (timeDiff.TotalSeconds > _noNewImagePushedTimeThreshold)
  189. {
  190. _calculations.TryRemove(key, out _);
  191. }
  192. }
  193. }
  194. //生成随机码,作为calculationId
  195. string calculationId = Guid.NewGuid().ToString();
  196. //创建一个calculation并添加到_calculations里
  197. CardiacCycleRecognition calculation = new CardiacCycleRecognition(calculationId, totalTime, cmPerPixel, intervalTime);
  198. //注:创建时立即订阅事件,以便从calculation中能将事件传出
  199. calculation.NotifyLog += OnLogWrite;
  200. calculation.NotifyError += OnErrorOccur;
  201. _calculations.TryAdd(calculationId, calculation);
  202. //如果预设的时间间隔大于0.2s时,由于实际预测帧数较少, 导致预测的曲线相较于实际的相差较大,记一下WarnLog,提醒使用者
  203. if (intervalTime >= 200)
  204. {
  205. #if ENABLE_AUTOEF_DEBUG
  206. LogHelper.EnableLogWrite = true;
  207. LogHelper.WarnLog("AutoEF.StartCalculation.Warn:", "calculationId:" + calculationId,
  208. "intervalTime,should be less than or equal to 0.2s.");
  209. #endif
  210. }
  211. return calculationId;
  212. }
  213. catch (Exception excep)
  214. {
  215. #if ENABLE_AUTOEF_DEBUG
  216. LogHelper.EnableLogWrite = true;
  217. LogHelper.ErrorLog("Failed at loading StartCalculation:" + excep.Message);
  218. #endif
  219. throw new Exception("Failed at loading StartCalculation:" + excep);
  220. }
  221. }
  222. /// <summary>
  223. /// 塞入一幅图,计算完成后返回结果,实时计算使用
  224. /// </summary>
  225. /// <param name="calculationId"></param>
  226. /// <param name="rawImage"></param>
  227. /// <param name="realFrameTime"></param>
  228. /// <returns></returns>
  229. /// <exception cref="ArgumentOutOfRangeException"></exception>
  230. /// <exception cref="Exception"></exception>
  231. public CardiacCurveInfos PushOneImageSync(string calculationId, RawImage rawImage, double realFrameTime)
  232. {
  233. //判断输入图像的类型,并统一转成BGR
  234. if (rawImage.ColorType == EnumColorType.GrayF32 || rawImage.ColorType == EnumColorType.Gray16)
  235. {
  236. throw new ArgumentOutOfRangeException("The ColorType of GrayF32 or Gray16 are not supported for PushOneImageSync by now!");
  237. }
  238. RawImage imgRGB = rawImage.Clone(EnumColorType.Bgr);
  239. //判断此次calculation是否开启
  240. if (!_calculations.ContainsKey(calculationId))
  241. {
  242. throw new Exception("calculation:" + calculationId + "shuould be started first.");
  243. }
  244. //判断当前计算类型有没有改变
  245. //初始化时type类型是None,第一次推图后才会变成真实代表的类型, 所以要排除为None的情况
  246. var type = _calculations[calculationId].Type;
  247. if (type != CalcType.None && type != CalcType.OnLine)
  248. {
  249. throw new Exception("calculation:" + calculationId + "cannot call PushOneImageSync and PushOneImageAsync at the same time");
  250. }
  251. try
  252. {
  253. //塞图到队列里计算
  254. PushOneImage(calculationId, imgRGB, realFrameTime, CalcType.OnLine);
  255. //判断结果是否计算完
  256. if (_calculations[calculationId].StartTime == realFrameTime)
  257. {
  258. //等待该图的计算结果
  259. while (!_calculations[calculationId].ProcessFinished(realFrameTime))
  260. {
  261. Thread.Sleep(5);
  262. }
  263. }
  264. else
  265. {
  266. //等待该图的计算结果
  267. while (!_calculations[calculationId].ProcessFinished(realFrameTime))
  268. {
  269. Thread.Sleep(1);
  270. }
  271. }
  272. return _calculations[calculationId].GetResult();
  273. }
  274. catch (Exception excep)
  275. {
  276. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  277. return _calculations[calculationId].GetResult();
  278. }
  279. }
  280. /// <summary>
  281. /// 塞入一副图像,不需要等待计算结果,冻结时使用
  282. /// </summary>
  283. /// <param name="caculationId"></param>
  284. /// <param name="rawImage"></param>
  285. /// <param name="realFrameTime"></param>
  286. public void PushOneImageAsync(string caculationId, RawImage rawImage, double realFrameTime)
  287. {
  288. try
  289. {
  290. if (rawImage.ColorType == EnumColorType.GrayF32 || rawImage.ColorType == EnumColorType.Gray16)
  291. {
  292. throw new Exception("The colorType of GrayF16 and GrayF32 are not supported for PushOneImageAsync by now.");
  293. }
  294. RawImage imgBGR = rawImage.Clone(EnumColorType.Bgr);
  295. //先判断此次calculation是否开启
  296. if (!_calculations.ContainsKey(caculationId))
  297. {
  298. throw new Exception("calculation_" + caculationId + "should be started first.");
  299. }
  300. //判断type是否改变
  301. //初始化时type设为None,第一次推图后才会变成真实所代表的类型,所以要排除type类型为None的情况
  302. var type = _calculations[caculationId].Type;
  303. if (type != CalcType.None && type != CalcType.OffLine)
  304. {
  305. throw new Exception("calculation_" + caculationId + "cannot call PushOneImageAsync and PushOneImageSync at the same time.");
  306. }
  307. //塞图到队列中计算
  308. PushOneImage(caculationId, imgBGR, realFrameTime, CalcType.OffLine);
  309. }
  310. catch (Exception excep)
  311. {
  312. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  313. #if ENABLE_AUTOEF_DEBUG
  314. LogHelper.EnableLogWrite = true;
  315. LogHelper.ErrorLog("AutoEF.PushOneImageAsync.Error:", excep.Message);
  316. #endif
  317. }
  318. }
  319. /// <summary>
  320. /// 获取结果(单次要处理的视频全部使用PushOneImageAsync塞图)
  321. /// </summary>
  322. /// <param name="calculationId"></param>
  323. /// <returns></returns>
  324. public CardiacCurveInfos GetResult(string calculationId)
  325. {
  326. try
  327. {
  328. //等待最后一帧结果计算完毕
  329. var frameTime = _calculations[calculationId].LastPushedFrameTime;
  330. while (!_calculations[calculationId].ProcessFinished(frameTime))
  331. {
  332. Thread.Sleep(1);
  333. }
  334. //获取计算结果
  335. return _calculations[calculationId].GetResult();
  336. }
  337. catch (Exception excep)
  338. {
  339. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  340. #if ENABLE_AUTOEF_DEBUG
  341. LogHelper.EnableLogWrite = true;
  342. LogHelper.ErrorLog("GetResult.Error:", excep.Message);
  343. #endif
  344. return _calculations[calculationId].GetResult();
  345. }
  346. }
  347. /// <summary>
  348. /// 销毁
  349. /// </summary>
  350. public void Dispose()
  351. {
  352. DoDispose();
  353. GC.SuppressFinalize(this);
  354. }
  355. /// <summary>
  356. /// 析构函数
  357. /// </summary>
  358. ~AutoEFCalculation()
  359. {
  360. DoDispose();
  361. }
  362. #endregion
  363. #region private
  364. private void PushOneImage(string calculationId, RawImage rawImage, double realFrameTime, CalcType calcType)
  365. {
  366. //推图,如果成功则塞入队列开始计算,如果没有成功,则不做计算直接返回(返回的时候需要给当前时间赋一个空的检测结果),
  367. //注意:_closing的时候不能继续塞图了, 要把所有未处理的数据都清空,如果这时候输入另外的数据,则会导致又重新开始计算了
  368. if (!_closing && _calculations[calculationId].PushOneImage(realFrameTime, calcType))
  369. {
  370. //塞入的图像的信息
  371. ImageInfo imageInfo = new ImageInfo
  372. {
  373. CalculationId = _calculations[calculationId].Id,
  374. Time = realFrameTime,
  375. RawImage = rawImage,
  376. CmPerPixel = _calculations[calculationId].CmPerPixel,
  377. };
  378. _imageInfoQueue.Enqueue(imageInfo);
  379. //设置_threadAwakeEvent状态为有信号,代表有新的输入,线程可以继续跑了
  380. _threadAwakeEvent.Set();
  381. int threadCount = Math.Min(_numCpu, _imageInfoQueue.Count);
  382. for (int i = 0; i < threadCount; i++)
  383. {
  384. lock (_processThreadLocker)
  385. {
  386. if (_threads.Count < i + 1)
  387. {
  388. var thread = new Thread(DoImageProcessing);
  389. thread.IsBackground = true;
  390. thread.Name = "AutoEF_ImgProcessing_" + i.ToString();
  391. _threads.Add(thread);
  392. thread.Start(i);
  393. }
  394. else
  395. {
  396. var thread = _threads[i];
  397. if (thread == null || !thread.IsAlive)
  398. {
  399. thread = new Thread(DoImageProcessing);
  400. thread.IsBackground = true;
  401. thread.Name = "AutoEF_ImgProcessing_" + i.ToString();
  402. thread.Start(i);
  403. _threads[i] = thread;
  404. }
  405. }
  406. }
  407. }
  408. }
  409. else
  410. {
  411. _calculations[calculationId].UpdateImageResult(realFrameTime, new LVVolumeCalcResult(), ResultType.None);
  412. }
  413. }
  414. /// <summary>
  415. /// 单帧图像推理
  416. /// </summary>
  417. /// <param name="threadIndex"></param>
  418. private void DoImageProcessing(object threadIndex)
  419. {
  420. if (threadIndex == null)
  421. {
  422. return;
  423. }
  424. while (!_closing)
  425. {
  426. if (_imageInfoQueue.Count > 0)
  427. {
  428. if (_imageInfoQueue.TryDequeue(out var sigleImageInfo))
  429. {
  430. try
  431. {
  432. var frameTime = sigleImageInfo.Time;
  433. var rawImage = sigleImageInfo.RawImage;
  434. var cmPerPixel = sigleImageInfo.CmPerPixel;
  435. var calculationId = sigleImageInfo.CalculationId;
  436. //1.满足预设的时间间隔才实际推理,否则基于时间序列给出结果
  437. //当前帧时间与上一次计算的时间间隔小于预设的时间间隔
  438. if (frameTime - _calculations[calculationId].LastRealCalcFrameTimeStamp < _calculations[calculationId].IntervalTime &&
  439. frameTime != _calculations[calculationId].StartTime)
  440. {
  441. _calculations[calculationId].UpdateImageResult(frameTime, new LVVolumeCalcResult(), ResultType.FrameSequenceBasedPredict);
  442. continue;
  443. }
  444. //2.当本次计算最新Push的图像的时间戳与将要计算的时间戳大于一定的时间阈值(即计算滞后于Push一定的时间)
  445. //则不计算,否则计算的结果将越来越滞后,导致计算失败
  446. //一般输入帧率是20-30帧每秒,所以暂取0.15s,即如果滞后3帧,则不计算
  447. if (frameTime - _calculations[calculationId].LastPushedFrameTime > Math.Max(_calculations[calculationId].IntervalTime, 150))
  448. {
  449. _calculations[calculationId].UpdateImageResult(frameTime, new LVVolumeCalcResult(), ResultType.FrameSequenceBasedPredict);
  450. continue;
  451. }
  452. //3.正常情况下
  453. _calculations[calculationId].LastRealCalcFrameTimeStamp = frameTime;
  454. LVVolumeCalcResult resultPerImage = ProcessOneImage(rawImage, cmPerPixel, (int)threadIndex);
  455. _calculations[calculationId].UpdateImageResult(frameTime, resultPerImage, ResultType.SingleFrameBasedPredict);
  456. }
  457. catch (Exception excep)
  458. {
  459. NotifyError.Invoke(this, new ErrorEventArgs(excep));
  460. }
  461. }
  462. }
  463. else
  464. {
  465. //将_threadAwakeEvent设为无信号,表示现在没有新的数据了,但又不想让线程结束(避免频繁创建和销毁线程带来的开销)
  466. //所以让Waitone一直等卡在这里,直到又新的数据输入,waitOne才继续
  467. _threadAwakeEvent.Reset();
  468. _threadAwakeEvent.WaitOne();
  469. }
  470. }
  471. }
  472. /// <summary>
  473. /// 推理单帧图像结果
  474. /// </summary>
  475. /// <param name="rawImage"></param>
  476. /// <param name="cmPerPixel"></param>
  477. /// <param name="threadIndex"></param>
  478. /// <returns></returns>
  479. private LVVolumeCalcResult ProcessOneImage(RawImage rawImage, float cmPerPixel, int threadIndex = 0)
  480. {
  481. //对一张图像进行推理
  482. InferenceNetworkInputImage inferInput = new InferenceNetworkInputImage(rawImage, new Rect(0, 0, rawImage.Width, rawImage.Height));
  483. IDetectedObject[] segResult = _inferNets[threadIndex].Process(inferInput);
  484. LVVolumeCalcResult resultPerImage = new LVVolumeCalcResult();
  485. if (segResult.Length != 1)
  486. {
  487. return LVVolumeCalcResult.Empty;
  488. }
  489. resultPerImage = LVVolumeProcess(rawImage, cmPerPixel, segResult);
  490. return resultPerImage;
  491. }
  492. /// <summary>
  493. /// 左心室容积推理
  494. /// </summary>
  495. /// <param name="rawImage"></param>
  496. /// <param name="cmPerPixel"></param>
  497. /// <param name="segResult"></param>
  498. /// <returns></returns>
  499. private LVVolumeCalcResult LVVolumeProcess(RawImage rawImage, float cmPerPixel, IDetectedObject[] segResult)
  500. {
  501. int apexPointSize = 1;
  502. int endPointSize = 2;
  503. int slicePointSize = _layerCount * 2; ;
  504. int controlNum = _edControlLayersCount > _esControlLayersCount ? _edControlLayersCount : _esControlLayersCount;
  505. //转换分割结果
  506. int pointCount = segResult[0].Contours[0].Length;
  507. StructPointCpp[] structPoints = new StructPointCpp[pointCount];
  508. for (int i = 0; i < pointCount; i++)
  509. {
  510. structPoints[i].x = segResult[0].Contours[0][i].X;
  511. structPoints[i].y = segResult[0].Contours[0][i].Y;
  512. }
  513. StructRect cppRect = new StructRect();
  514. cppRect.left = segResult[0].BoundingBox.Left;
  515. cppRect.top = segResult[0].BoundingBox.Top;
  516. cppRect.width = segResult[0].BoundingBox.Width;
  517. cppRect.height = segResult[0].BoundingBox.Height;
  518. //初始化需要返回的结果
  519. StructCppLVVolume structCppLVVolume = new StructCppLVVolume();
  520. structCppLVVolume.volume = 0;
  521. structCppLVVolume.innerContLen = 0;
  522. structCppLVVolume.apexPoint = new StructPointCpp();
  523. structCppLVVolume.endPoints = new StructEndPoints();
  524. StructPointCpp[] slicePoints = new StructPointCpp[slicePointSize];
  525. StructPointCpp[] innerContour = new StructPointCpp[rawImage.Width * rawImage.Height];
  526. StructPointCpp[] controlPoints = new StructPointCpp[controlNum * 2];
  527. int[] controlCount = new int[1];
  528. controlCount[0] = 0;
  529. //获取轮廓指针
  530. GCHandle hDetectedObjectArray = GCHandle.Alloc(structPoints, GCHandleType.Pinned);
  531. IntPtr pDetectedObject = hDetectedObjectArray.AddrOfPinnedObject();
  532. //获取外接矩形的指针
  533. GCHandle hObjectRect = GCHandle.Alloc(cppRect, GCHandleType.Pinned);
  534. IntPtr pObjectRect = hObjectRect.AddrOfPinnedObject();
  535. GCHandle hSlicePoint = GCHandle.Alloc(slicePoints, GCHandleType.Pinned);
  536. IntPtr pSlicePoints = hSlicePoint.AddrOfPinnedObject();
  537. GCHandle hInnerPoints = GCHandle.Alloc(innerContour, GCHandleType.Pinned);
  538. IntPtr pInnerPoints = hInnerPoints.AddrOfPinnedObject();
  539. GCHandle hControlPoints = GCHandle.Alloc(controlPoints, GCHandleType.Pinned);
  540. IntPtr pConrolPoints = hControlPoints.AddrOfPinnedObject();
  541. bool ret = false;
  542. ret = LVVolumeCalculate(rawImage.Width, rawImage.Height, cmPerPixel, _layerCount, _edControlLayersCount,
  543. _esControlLayersCount, controlCount, pDetectedObject, pointCount, pObjectRect,
  544. pSlicePoints, pInnerPoints, pConrolPoints, ref structCppLVVolume);
  545. //释放
  546. if (hDetectedObjectArray.IsAllocated)
  547. {
  548. hDetectedObjectArray.Free();
  549. }
  550. if (hObjectRect.IsAllocated)
  551. {
  552. hObjectRect.Free();
  553. }
  554. if (hSlicePoint.IsAllocated)
  555. {
  556. hSlicePoint.Free();
  557. }
  558. if (hInnerPoints.IsAllocated)
  559. {
  560. hInnerPoints.Free();
  561. }
  562. if (hControlPoints.IsAllocated)
  563. {
  564. hControlPoints.Free();
  565. }
  566. //当返回false或计算的内轮廓的长度为0时,返回空值
  567. if (!ret)
  568. {
  569. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.WarnLog, "Failed at Loading LVVolumeCalculate."));
  570. return LVVolumeCalcResult.Empty;
  571. }
  572. if (structCppLVVolume.innerContLen == 0 || controlCount[0] == 0)
  573. {
  574. return LVVolumeCalcResult.Empty;
  575. }
  576. //转换cpp计算的结果
  577. Point2D[] endPoints = new Point2D[endPointSize];
  578. for (int i = 0; i < endPointSize; i++)
  579. {
  580. endPoints[0] = new Point2D(structCppLVVolume.endPoints.leftPoint.x, structCppLVVolume.endPoints.leftPoint.y);
  581. endPoints[1] = new Point2D(structCppLVVolume.endPoints.rightPoint.x, structCppLVVolume.endPoints.rightPoint.y);
  582. }
  583. Point2D[] slicePointsTran = new Point2D[slicePointSize];
  584. for (int i = 0; i < slicePointSize; i++)
  585. {
  586. slicePointsTran[i] = new Point2D(slicePoints[i].x, slicePoints[i].y);
  587. }
  588. Point2D[] innerContourPoint = new Point2D[structCppLVVolume.innerContLen];
  589. for (int i = 0; i < structCppLVVolume.innerContLen; i++)
  590. {
  591. innerContourPoint[i] = new Point2D(innerContour[i].x, innerContour[i].y);
  592. }
  593. Point2D[] controlPoint = new Point2D[controlCount[0]];
  594. for (int i = 0; i < controlCount[0]; i++)
  595. {
  596. controlPoint[i] = new Point2D(controlPoints[i].x, controlPoints[i].y);
  597. }
  598. LVVolumeMeasureMarks lVVolumeMeasure = new LVVolumeMeasureMarks(new Point2D(structCppLVVolume.apexPoint.x,
  599. structCppLVVolume.apexPoint.y), endPoints, slicePointsTran, controlPoint);
  600. LVVolumeCalcResult lVVolumeCalcResult = new LVVolumeCalcResult(innerContourPoint, structCppLVVolume.volume,
  601. segResult[0].Confidence, lVVolumeMeasure);
  602. return lVVolumeCalcResult;
  603. }
  604. /// <summary>
  605. /// 主动销毁
  606. /// </summary>
  607. private void DoDispose()
  608. {
  609. _closing = true;
  610. //因为_threadAwakeEvent一直在waitone,所以需要先Set一下,使其不阻塞
  611. _threadAwakeEvent.Set();
  612. //等待所有线程结束
  613. while (true)
  614. {
  615. bool runing = false;
  616. lock (_processThreadLocker)
  617. {
  618. for (int i = 0; i < _threads.Count; i++)
  619. {
  620. var thread = _threads[i];
  621. if (thread != null && thread.IsAlive)
  622. {
  623. runing = true;
  624. break;
  625. }
  626. }
  627. }
  628. if (runing)
  629. {
  630. Thread.Sleep(10);
  631. }
  632. else
  633. {
  634. break;
  635. }
  636. }
  637. //所有的calculation停止
  638. foreach (var calculation in _calculations.Values)
  639. {
  640. calculation.NotifyLog -= OnLogWrite;
  641. calculation.NotifyError -= OnErrorOccur;
  642. calculation.Dispose();
  643. }
  644. //所有待处理数据清空
  645. while (_imageInfoQueue.Count > 0)
  646. {
  647. _imageInfoQueue.TryDequeue(out var _);
  648. }
  649. //逐个释放inferNet
  650. for (int i = 0; i < _inferNets.Count; i++)
  651. {
  652. _inferNets[i]?.Dispose();
  653. _inferNets[i] = null;
  654. }
  655. _initialized = false;
  656. _closing = false;
  657. }
  658. /// <summary>
  659. /// 有log要记
  660. /// </summary>
  661. /// <param name="sender"></param>
  662. /// <param name="e"></param>
  663. private void OnLogWrite(object sender, LogEventArgs e)
  664. {
  665. NotifyLog?.Invoke(this, e);
  666. }
  667. /// <summary>
  668. /// 推理过程中发生了错误
  669. /// </summary>
  670. /// <param name="sender"></param>
  671. /// <param name="e"></param>
  672. private void OnErrorOccur(object sender, ErrorEventArgs e)
  673. {
  674. NotifyError?.Invoke(this, e);
  675. }
  676. #endregion
  677. }
  678. }