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 { /// /// 内膜测量结果, 用于C++输出的接口 /// [StructLayout(LayoutKind.Sequential)] public struct MeasureResults { //检测是否成功 public bool isSuccess; //平均厚度 public float AverageThickness; //最小厚度 public float MinThickness; //最大厚度 public float MaxThickness; //测量起点,单个 public StructMyPoint startPoint; //测量终点,单个 public StructMyPoint endPoint; } /// /// 预测的organ后处理信息 /// 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; } } } /// /// 错误代码 /// public enum EnumErrorCode { None = 0, selectContours = 1, skeletonSega = 2, removeDiscreteValue = 3, extractSegment = 4, fitLineAndCalc = 5, calaThickness = 6, intimaProcess = 7, interfaceOfIntimeThickness = 8 }; /// /// 内膜自动测量 /// public class IntimaHelper { #region field private AIDiagSystem _diagSystem; private bool _disposing = false; #endregion #region events /// /// 通知订阅者,推理过程中发生了错误 /// public event EventHandler NotifyError; /// /// 通知订阅者,推理过程中有log要记录 /// public event EventHandler 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 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 center = new List { centerX, centerY }; return center; } /// /// 用来过滤超出左右边界的point /// /// /// /// /// 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; } /// /// 生成c++后处理所需要的格式 /// /// /// private PostProcessInputInfo ConvertResultToPostProcessInput(AIDiagResultPerImg diagResult, AI.Common.Rect rect) { int cropLeft = rect.Left; int cropRight = rect.Left + rect.Width; // 每个organ和下面的lesions 组成一个dict Dictionary> outlesions = new Dictionary>(); 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, DetectedLesion> lesionDict = new Dictionary, DetectedLesion>(); Dictionary, DetectedOrgan> organDict = new Dictionary, 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 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 centerLesion = new List{ 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 organCenter in organDict.Keys) { outlesions[organDict[organCenter]] = new List(); } // 先循环lesion 查找中心点距离最近的organ,组成dict foreach (List lesionCenter in lesionDict.Keys) { double _minDistance = 10000; List _minDistanceOrganCenter = new List(); foreach (List 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 lesions = new List(); 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 _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; } /// /// 计算颈动脉内中膜厚度 /// /// /// /// 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 /// /// 根据AI选取的最优平面,通过AI模型预测颈动脉轮廓,再去计算内中膜厚度 /// /// 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 { new System.Drawing.Point(thicknessAnt.startPoint.X, thicknessAnt.startPoint.Y) }; intimaResult.AntIntima.PointUpper = new List { 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 { new System.Drawing.Point(thicknessPost.startPoint.X, thicknessPost.startPoint.Y) }; intimaResult.PostIntima.PointUpper = new List { 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 } }