123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- using System;
- using System.IO;
- using System.Text;
- using System.Collections.Generic;
- using System.Runtime.InteropServices;
- using AI.Common;
- using AI.Common.Log;
- using AI.Common.Tools;
- using AI.DiagSystem;
- using AI.DiagSystem.Workers.TaskResults;
- using static AI.Common.InferenceNetworkUtils;
- namespace Reconstruction3DHelper
- {
- /// <summary>
- /// 内膜测量结果, 用于C++输出的接口
- /// </summary>
- [StructLayout(LayoutKind.Sequential)]
- public struct MeasureResults
- {
- //检测是否成功
- public bool isSuccess;
- //平均厚度
- public float AverageThickness;
- //最小厚度
- public float MinThickness;
- //最大厚度
- public float MaxThickness;
- //测量起点,单个
- public StructMyPoint startPoint;
- //测量终点,单个
- public StructMyPoint endPoint;
- }
- /// <summary>
- /// 预测的organ后处理信息
- /// </summary>
- public struct PostProcessInputInfo
- {
- private int[] _labelID;
- private int[] _singleContourLenList;
- private int _idx;
- private InferenceNetworkUtils.StructMyPoint[] _contoursPoints;
- public int[] LabelID
- {
- get { return _labelID; }
- set { _labelID = value; }
- }
- public int[] SingleContourLenList
- {
- get { return _singleContourLenList; }
- set { _singleContourLenList = value; }
- }
- public int Idx
- {
- get { return _idx; }
- set { _idx = value; }
- }
- public InferenceNetworkUtils.StructMyPoint[] ContoursPoints
- {
- get { return _contoursPoints; }
- set { _contoursPoints = value; }
- }
- }
- /// <summary>
- /// 错误代码
- /// </summary>
- public enum EnumErrorCode
- {
- None = 0,
- selectContours = 1,
- skeletonSega = 2,
- removeDiscreteValue = 3,
- extractSegment = 4,
- fitLineAndCalc = 5,
- calaThickness = 6,
- intimaProcess = 7,
- interfaceOfIntimeThickness = 8
- };
- /// <summary>
- /// 内膜自动测量
- /// </summary>
- public class IntimaHelper
- {
- #region field
- private AIDiagSystem _diagSystem;
- private bool _disposing = false;
- #endregion
- #region events
- /// <summary>
- /// 通知订阅者,推理过程中发生了错误
- /// </summary>
- public event EventHandler<ErrorEventArgs> NotifyError;
- /// <summary>
- /// 通知订阅者,推理过程中有log要记录
- /// </summary>
- public event EventHandler<LogEventArgs> NotifyLog;
- #endregion
- #region constructor
- public IntimaHelper(EnumPerformance performace = EnumPerformance.Low)
- {
- _diagSystem = new AIDiagSystem(performace, defaultInferWorkName: EnumInferWorkName.CarotidArteryPlaque);
- _diagSystem.IsCropped = true;
- // _diagSystem.EnableLesionSeg = true;
- // _diagSystem.EnableDescription = true;
- _diagSystem.NotifyError += OnErrorOccur;
- _diagSystem.NotifyLog += OnLogWrite;
- _diagSystem.EnableDebugLogWrite = true;
- }
- ~IntimaHelper()
- {
- DoDispose();
- }
- #endregion
- #region DllImport
- [DllImport(@"IntimediaMeasurement.dll", CallingConvention = CallingConvention.Cdecl)]
- [return: MarshalAs(UnmanagedType.I1)]
- public static extern bool InterfaceOfIntimeThickness(IntPtr imgData, int oriWidth, int oriHeight, int objNum, int[] labelID, int[] singleContourLenList,
- IntPtr allContourPoints, bool calcLower, bool calcUpper, ref MeasureResults thicknessAnt, ref MeasureResults thicknessPost);
- [DllImport(@"IntimediaMeasurement.dll", CallingConvention = CallingConvention.Cdecl)]
- [return: MarshalAs(UnmanagedType.I1)]
- public static extern void GetErrorCodeAndMsg(ref EnumErrorCode errorCode, StringBuilder error, int errorMaxLen);
- #endregion
- #region private
- private List<int> CalCenter(AI.Common.Point2D[] objContour)
- {
- int minX = objContour[0].X;
- int minY = objContour[0].Y;
- int maxX = 0;
- int maxY = 0;
- foreach (AI.Common.Point2D p in objContour)
- {
- if (p.X < minX) minX = p.X;
- if (p.Y < minY) minY = p.Y;
- if (p.X > maxX) maxX = p.X;
- if (p.Y > maxY) maxY = p.Y;
- }
- int centerX = (minX + maxX) / 2;
- int centerY = (minY + maxY) / 2;
- List<int> center = new List<int> { centerX, centerY };
- return center;
- }
- /// <summary>
- /// 用来过滤超出左右边界的point
- /// </summary>
- /// <param name="inputArray"></param>
- /// <param name="cropLeft"></param>
- /// <param name="cropRight"></param>
- /// <returns></returns>
- private void removePointOutofBBox(ref AI.Common.Point2D[] inputArray, int cropLeft, int cropRight)
- {
- AI.Common.Point2D[] filteredPoints = new AI.Common.Point2D[inputArray.Length];
- int filteredIndex = 0;
- for (int ni = 0; ni < inputArray.Length; ni++)
- {
- int xCoordinate = inputArray[ni].X;
- if (xCoordinate >= cropLeft && xCoordinate <= cropRight)
- {
- filteredPoints[filteredIndex] = inputArray[ni];
- filteredIndex++;
- }
- }
- AI.Common.Point2D[] actualFilteredPoints = new AI.Common.Point2D[filteredIndex];
- Array.Copy(filteredPoints, actualFilteredPoints, filteredIndex);
- inputArray = actualFilteredPoints;
- }
- /// <summary>
- /// 生成c++后处理所需要的格式
- /// </summary>
- /// <param name="diagResult"></param>
- /// <returns></returns>
- private PostProcessInputInfo ConvertResultToPostProcessInput(AIDiagResultPerImg diagResult, AI.Common.Rect rect)
- {
- int cropLeft = rect.Left;
- int cropRight = rect.Left + rect.Width;
- // 每个organ和下面的lesions 组成一个dict
- Dictionary<DetectedOrgan, List<DetectedLesion>> outlesions = new Dictionary<DetectedOrgan, List<DetectedLesion>>();
- for (int ni = 0; ni < diagResult.DiagResultsForEachOrgan.Length; ni++)
- {
- //分两种情况,情况 1,如果有两个或以上organ,横切的情况,模型结果输出是将organ和lesion都放一起了,就需要拆开
- if (diagResult.DiagResultsForEachOrgan[ni].OrganContours.Length >= 2 && diagResult.DiagResultsForEachOrgan[ni].Organ == EnumOrgans.CarotidArtery)
- {
- Dictionary<List<int>, DetectedLesion> lesionDict = new Dictionary<List<int>, DetectedLesion>();
- Dictionary<List<int>, DetectedOrgan> organDict = new Dictionary<List<int>, DetectedOrgan>();
- for (int i = 0; i < diagResult.DiagResultsForEachOrgan[ni].OrganContours.Length; i++)
- {
- removePointOutofBBox(ref diagResult.DiagResultsForEachOrgan[ni].OrganContours[i], cropLeft, cropRight);
- AI.Common.Point2D[] organContour = diagResult.DiagResultsForEachOrgan[ni].OrganContours[i];
- // 获取organ的center
- List<int> centerOrgan = CalCenter(organContour);
- AI.Common.Point2D[][] organContours = { organContour };
- DetectedOrgan organ = new DetectedOrgan(EnumScanParts.Neck, EnumOrgans.CarotidArtery, 1,
- diagResult.DiagResultsForEachOrgan[ni].OrganBoundBox, organContours);
- organDict[centerOrgan] = organ;
- }
- for (int j = 0; j < diagResult.DiagResultsForEachOrgan[ni].DetectedObjects.Length; j++)
- {
- if (diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Label == 1)
- {
- removePointOutofBBox(ref diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Contours[0], cropLeft, cropRight);
- List<int> centerLesion = new List<int>{ diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Left + diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Width / 2,
- diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Top + diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox.Height / 2};
- DetectedLesion lesion = new DetectedLesion(EnumScanParts.Neck, EnumOrgans.Neck, AI.DiagSystem.Workers.InferenceNetworks.EnumLesionType.FocalLesion,
- diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Label, diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Confidence,
- 0.0f, 0.0f, 0.0f, diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].BoundingBox, diagResult.DiagResultsForEachOrgan[ni].DetectedObjects[j].Contours);
- lesionDict[centerLesion] = lesion;
- }
- }
- // 初始化 outlesions 的每个 organ 的 value;
- foreach (List<int> organCenter in organDict.Keys)
- {
- outlesions[organDict[organCenter]] = new List<DetectedLesion>();
- }
- // 先循环lesion 查找中心点距离最近的organ,组成dict
- foreach (List<int> lesionCenter in lesionDict.Keys)
- {
- double _minDistance = 10000;
- List<int> _minDistanceOrganCenter = new List<int>();
- foreach (List<int> organCenter in organDict.Keys)
- {
- double _distance = Math.Sqrt(Math.Pow((lesionCenter[0] - organCenter[0]), 2.0) + Math.Pow((lesionCenter[1] - organCenter[1]), 2.0));
- if (_distance < _minDistance)
- {
- _minDistanceOrganCenter = organCenter;
- _minDistance = _distance;
- }
- }
- outlesions[organDict[_minDistanceOrganCenter]].Add(lesionDict[lesionCenter]);
- }
- }
- //情况 2,只有一个organ的情况就不需要拆开
- else if (diagResult.DiagResultsForEachOrgan[ni].OrganContours.Length == 1)
- {
- AIDiagResultPerOrgan _diagResultsForEachOrgan = diagResult.DiagResultsForEachOrgan[ni];
- if (_diagResultsForEachOrgan.Organ == EnumOrgans.CarotidArtery) //颈动脉
- {
- removePointOutofBBox(ref _diagResultsForEachOrgan.OrganContours[0], cropLeft, cropRight);
- DetectedOrgan organ = new DetectedOrgan(EnumScanParts.Neck, EnumOrgans.CarotidArtery, 1,
- _diagResultsForEachOrgan.OrganBoundBox, _diagResultsForEachOrgan.OrganContours);
- List<DetectedLesion> lesions = new List<DetectedLesion>();
- foreach (DetectedObject _detectedObject in _diagResultsForEachOrgan.DetectedObjects)
- {
- if (_detectedObject.Label == 1) // 1代表斑块
- {
- removePointOutofBBox(ref _detectedObject.Contours[0], cropLeft, cropRight);
- lesions.Add(new DetectedLesion(EnumScanParts.Neck, EnumOrgans.Neck, AI.DiagSystem.Workers.InferenceNetworks.EnumLesionType.FocalLesion,
- _detectedObject.Label, _detectedObject.Confidence, 0.0f, 0.0f, 0.0f, _detectedObject.BoundingBox, _detectedObject.Contours));
- }
- outlesions[organ] = lesions;
- }
- }
- }
- }
- // 对分配好的organ进行后处理,若斑块超出范围,则外扩轮廓
- foreach (DetectedOrgan _organ in outlesions.Keys)
- {
- List<DetectedLesion> _lesions = outlesions[_organ];
- }
- // 将上面的dict 转换成输入到postproce的格式
- int[] labelID = new int[20]; // 初始化10
- int[] singleContourLenList = new int[20];
- int allContoursLengths = 0;
- int idx = 0;
- foreach (DetectedOrgan singleOrganRes in outlesions.Keys)
- {
- labelID[idx] = singleOrganRes.Label;
- int singleOrganResContoursLength = singleOrganRes.Contours[0].Length;
- singleContourLenList[idx] = singleOrganResContoursLength;
- allContoursLengths += singleOrganResContoursLength;
- idx++;
- foreach (DetectedLesion singleLesionRes in outlesions[singleOrganRes])
- {
- labelID[idx] = singleLesionRes.Label;
- int singleLesionResContoursLength = singleLesionRes.Contours[0].Length;
- singleContourLenList[idx] = singleLesionResContoursLength;
- allContoursLengths += singleLesionResContoursLength;
- idx++;
- }
- }
- Array.Resize(ref labelID, idx);
- Array.Resize(ref singleContourLenList, idx);
- //将所有轮廓记录在contoursPoints中
- InferenceNetworkUtils.StructMyPoint[] contoursPoints = new InferenceNetworkUtils.StructMyPoint[allContoursLengths];
- int idx2 = 0;
- int startIndex = 0;
- foreach (DetectedOrgan singleOrganRes in outlesions.Keys)
- {
- var lenOrgan = singleContourLenList[idx2];
- for (int ni = 0; ni < lenOrgan; ni++)
- {
- contoursPoints[startIndex + ni].X = singleOrganRes.Contours[0][ni].X;
- contoursPoints[startIndex + ni].Y = singleOrganRes.Contours[0][ni].Y;
- }
- idx2 += 1;
- startIndex += lenOrgan;
- foreach (DetectedLesion singleLesionRes in outlesions[singleOrganRes])
- {
- var lenLesion = singleContourLenList[idx2];
- for (int nj = 0; nj < lenLesion; nj++)
- {
- contoursPoints[startIndex + nj].X = singleLesionRes.Contours[0][nj].X;
- contoursPoints[startIndex + nj].Y = singleLesionRes.Contours[0][nj].Y;
- }
- idx2 += 1;
- startIndex += lenLesion;
- }
- }
- PostProcessInputInfo postProcessInputInfo = new PostProcessInputInfo();
- postProcessInputInfo.LabelID = labelID;
- postProcessInputInfo.SingleContourLenList = singleContourLenList;
- postProcessInputInfo.Idx = idx;
- postProcessInputInfo.ContoursPoints = contoursPoints;
- return postProcessInputInfo;
- }
- /// <summary>
- /// 计算颈动脉内中膜厚度
- /// </summary>
- /// <param name="postProcessInputInfo"></param>
- /// <param name="rawImage"></param>
- /// <returns></returns>
- private bool InterfaceOfIntimeThickness(PostProcessInputInfo postProcessInputInfo, RawImage rawImage, bool calcLower, bool calcUpper, ref MeasureResults thicknessAnt, ref MeasureResults thicknessPost)
- {
- RawImage _rawImageOneChannel;
- // 转换成单通道
- if (rawImage.ColorType != EnumColorType.Gray8 && rawImage.ColorType != 0)
- {
- _rawImageOneChannel = rawImage.Clone(EnumColorType.Gray8);
- }
- else
- {
- _rawImageOneChannel = rawImage;
- }
- // 获取指针
- GCHandle hObject1 = GCHandle.Alloc(_rawImageOneChannel.DataBuffer, GCHandleType.Pinned);
- IntPtr pObject1 = hObject1.AddrOfPinnedObject();
- GCHandle hObject2 = GCHandle.Alloc(postProcessInputInfo.ContoursPoints, GCHandleType.Pinned);
- IntPtr pObject2 = hObject2.AddrOfPinnedObject();
- if (!InterfaceOfIntimeThickness(pObject1, rawImage.Width, rawImage.Height, postProcessInputInfo.Idx,
- postProcessInputInfo.LabelID, postProcessInputInfo.SingleContourLenList, pObject2,
- calcLower, calcUpper, ref thicknessAnt, ref thicknessPost))
- {
- int errorMaxLen = 256;
- StringBuilder errorMsg = new StringBuilder(errorMaxLen);
- EnumErrorCode errorCode = EnumErrorCode.None;
- GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
- throw new Exception("Failed at decoding vinno image data buffers, error code: "
- + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
- }
- // 释放
- if (hObject1.IsAllocated)
- {
- hObject1.Free();
- }
- if (hObject2.IsAllocated)
- {
- hObject2.Free();
- }
- return true;
- }
- private void OnErrorOccur(object sender, ErrorEventArgs e)
- {
- if (!_disposing)
- {
- NotifyError?.Invoke(this, e);
- }
- }
- private void OnLogWrite(object sender, LogEventArgs e)
- {
- if (!_disposing)
- {
- NotifyLog?.Invoke(this, e);
- }
- }
- private void DoDispose()
- {
- if (!_disposing)
- {
- _diagSystem?.Dispose();
- }
- }
- #endregion
- #region public
- /// <summary>
- /// 根据AI选取的最优平面,通过AI模型预测颈动脉轮廓,再去计算内中膜厚度
- /// </summary>
- /// <returns></returns>
- public IntimaDetectResult DetectIntima(byte[] imageByte, int imageWidth, int imageHeight, float physicalPerPixel, bool enableAnt, bool enablePost)
- {
- try
- {
- if (imageByte.Length == 0)
- {
- LogHelper.ErrorLog($"DetectIntima, get intima image failed!");
- return new IntimaDetectResult();
- }
-
- //AI检测
- AI.Common.RawImage rawImgToEval = null;
- rawImgToEval = new RawImage(imageByte, imageWidth, imageHeight, AI.Common.EnumColorType.Gray8);
- AI.Common.Rect rect = new AI.Common.Rect(0, 0, 0, 0);
- UsImageRegionSegHelper.CropWithCvCore(rawImgToEval, out rect);
- var diagResult = _diagSystem.EvaluateOneImage(rawImgToEval);
-
- //根据AI结果计算内中膜厚度
- if (diagResult.DiagResultsForEachOrgan[0].Organ == EnumOrgans.CarotidArtery) //预测出颈动脉轮廓
- {
- // 将结果转换为后处理的输入
- var postProcessInputInfo = ConvertResultToPostProcessInput(diagResult, rect);
- // 计算内中膜厚度
- MeasureResults thicknessAnt = new MeasureResults();
- MeasureResults thicknessPost = new MeasureResults();
- var thicknessRe = InterfaceOfIntimeThickness(postProcessInputInfo, rawImgToEval, enablePost, enableAnt, ref thicknessAnt, ref thicknessPost);
- // 测量结果统计
- IntimaDetectResult intimaResult = new IntimaDetectResult();
- if (thicknessAnt.isSuccess)
- {
- intimaResult.AntIntima = new IntimaProperty();
- intimaResult.AntIntima.AverageThickness = thicknessAnt.AverageThickness * physicalPerPixel;
- intimaResult.AntIntima.MaxThickness = thicknessAnt.MaxThickness * physicalPerPixel;
- intimaResult.AntIntima.MinThickness = thicknessAnt.MinThickness * physicalPerPixel;
- intimaResult.AntIntima.PointLower = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessAnt.startPoint.X, thicknessAnt.startPoint.Y) };
- intimaResult.AntIntima.PointUpper = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessAnt.endPoint.X, thicknessAnt.endPoint.Y) };
- intimaResult.AntIntima.intimaRect = new IntimaRect
- {
- Left = thicknessAnt.startPoint.X,
- Top = thicknessAnt.startPoint.Y - 30,
- Width = thicknessAnt.endPoint.X - thicknessAnt.startPoint.X,
- Height = thicknessAnt.endPoint.Y - thicknessAnt.startPoint.Y + 60
- };
- intimaResult.AntIntima.isSuccess = true;
- }
- if (thicknessPost.isSuccess)
- {
- intimaResult.PostIntima = new IntimaProperty();
- intimaResult.PostIntima.AverageThickness = thicknessPost.AverageThickness * physicalPerPixel;
- intimaResult.PostIntima.MaxThickness = thicknessPost.MaxThickness * physicalPerPixel;
- intimaResult.PostIntima.MinThickness = thicknessPost.MinThickness * physicalPerPixel;
- intimaResult.PostIntima.PointLower = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessPost.startPoint.X, thicknessPost.startPoint.Y) };
- intimaResult.PostIntima.PointUpper = new List<System.Drawing.Point> { new System.Drawing.Point(thicknessPost.endPoint.X, thicknessPost.endPoint.Y) };
- intimaResult.PostIntima.intimaRect = new IntimaRect
- {
- Left = thicknessPost.startPoint.X,
- Top = thicknessPost.startPoint.Y - 30,
- Width = thicknessPost.endPoint.X - thicknessPost.startPoint.X,
- Height = thicknessPost.endPoint.Y - thicknessPost.startPoint.Y + 60
- };
- intimaResult.PostIntima.isSuccess = true;
- }
- return intimaResult;
- }
- else
- {
- return new IntimaDetectResult();
- }
- }
- catch (Exception ex)
- {
- LogHelper.ErrorLog("IntimaHelper.DetectIntima Failed:" + ex);
- return new IntimaDetectResult();
- }
- }
- #endregion
- }
- }
|