using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using AI.Common; using AI.Common.Log; using AI.DiagSystem; using AI.Reconstruction; namespace Reconstruction3DHelper { public class PlaqueDetectHelper { #region field private AIDiagSystem _diagSystem; private bool _disposing = false; #endregion #region events /// /// 通知订阅者,推理过程中发生了错误 /// public event EventHandler NotifyError; /// /// 通知订阅者,推理过程中有log要记录 /// public event EventHandler NotifyLog; #endregion #region dllImport [DllImport(@"PlaqueProcessingCpp.dll", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool SaveSliceImage(AI.Reconstruction.AIReconstructor.StructImageInfo srcImageInfo, int name); #endregion #region constructor public PlaqueDetectHelper(EnumPerformance performace = EnumPerformance.Low) { _diagSystem = new AIDiagSystem(performace); _diagSystem.IsCropped = false; _diagSystem.EnableLesionSeg = true; _diagSystem.EnableDescription = true; _diagSystem.NotifyError += OnErrorOccur; _diagSystem.NotifyLog += OnLogWrite; _diagSystem.EnableDebugLogWrite = true; } ~PlaqueDetectHelper() { DoDispose(); } #endregion #region public /// /// 根据AI结果,自动选取最优的血管内膜切面,返回图像上的三个3D坐标点 /// /// /// /// /// /// public List GetClipPoints(Dictionary modelResults, ModelSize modelSize) { var intimaSlicePoints = GetVesselSlicePoints(modelResults, modelSize.ModelLengthX, modelSize.ModelLengthY, modelSize.ModelLengthZ); if (intimaSlicePoints == null) { LogHelper.ErrorLog($"DetectIntima, get three coplanar points failed!"); return null; } //获得切面图像用于检测内膜 List pointList = new List { intimaSlicePoints.Point1, intimaSlicePoints.Point2, intimaSlicePoints.Point3 }; return pointList; } public Dictionary DetectPlaquesInVolumeData(byte[] volumeDataBuffer, int width, int height, int imageCount,EnumColorType colorType) { try { var diagId = _diagSystem.StartEvalutationOfMultipleImageBatches(imageCount); int bytesPerPixel = RawImage.GetBytesPerPixel(colorType); int bytesPerImage = width * height * bytesPerPixel; byte[] imageDataBuffer = new byte[bytesPerImage]; //var RawImages = new Dictionary(); for (int ni = 0; ni < imageCount; ni++) { Array.Copy(volumeDataBuffer, ni * bytesPerImage, imageDataBuffer, 0, bytesPerImage); RawImage image = new RawImage(imageDataBuffer, width, height, colorType); // RawImages.Add(ni, image); //单通道转为3通道 //RawImage imageBgr = image.Clone(EnumColorType.Bgr); //图像保存至本地,用于测试 //var decodedPointer = Marshal.UnsafeAddrOfPinnedArrayElement(imageDataBuffer, 0); //AI.Reconstruction.AIReconstructor.StructImageInfo srcImageInfo = new AI.Reconstruction.AIReconstructor.StructImageInfo(width, height, colorType, decodedPointer); //SaveSliceImage(srcImageInfo, ni); //var diagResultOne = _diagSystem.EvaluateOneImage(image); _diagSystem.PushOneBatchOfImagesAsync(diagId, new List { image }); } var diagResult = _diagSystem.GetEvaluationsOfPushedMultipleImageBatches(diagId); return diagResult; } catch (Exception e) { LogHelper.ErrorLog("PlaqueDetectHelper, DetectPlaquesInVolumeData error," + e.Message + "," + e.StackTrace); throw; } } public void Dispose() { DoDispose(); GC.SuppressFinalize(this); } #endregion #region private /// /// 根据三维点集投影到Y=0平面上的投影线的信息,求过这条投影线的平面上的三个空间点的位置 /// /// /// private bool GetCoplanarPointsWithProjectionLine(double[] projectionLine, int modelX, int modelY, int modelZ, out CoplanarPoints points) { points = CoplanarPoints.Empty; try { Point3DF point1 = Point3DF.Empty; Point3DF point2 = Point3DF.Empty; Point3DF point3 = Point3DF.Empty; if (projectionLine[1] == 0) { var x = -projectionLine[2]; x = Math.Min(Math.Max(0, x), modelX - 1); point1 = new Point3DF((float)x, 0, 0); point2 = new Point3DF((float)x, 0, modelZ - 1); point3 = new Point3DF((float)x, modelY - 1, 0); } else { var k = projectionLine[0]; var b = projectionLine[2]; // 计算直线与z=modelZ-1,y=0直线的交点 var tempX1 = (modelZ - 1 - b) / k; // 此处原为tempX1 = modelZ - 1 - b / k; 按照直线求交点的公式是错误的 if (tempX1 < 0) { var tempZ = (int)Math.Round(b); tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1); point1 = new Point3DF(0, 0, tempZ); point2 = new Point3DF(0, modelY - 1, tempZ); } else if (tempX1 < modelX - 1) { var tempX = (int)Math.Round(tempX1); tempX = Math.Min(Math.Max(0, tempX), modelX - 1); point1 = new Point3DF(tempX, 0, modelZ - 1); point2 = new Point3DF(tempX, modelY - 1, modelZ - 1); } else { var tempZ = (int)Math.Round(k * (modelX - 1) + b); tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1); point1 = new Point3DF(modelX - 1, 0, tempZ); point2 = new Point3DF(modelX - 1, modelY - 1, tempZ); } var tempX2 = -b / k; if (tempX2 < 0) { var tempZ = (int)Math.Round(b); tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1); point3 = new Point3DF(0, 0, tempZ); } else if (tempX2 < modelX - 1) { var tempX = (int)Math.Round(tempX2); tempX = Math.Min(Math.Max(0, tempX), modelX - 1); point3 = new Point3DF(tempX, 0, 0); } else { var tempZ = (int)Math.Round((modelX - 1) * k + b); tempZ = Math.Min(Math.Max(0, tempZ), modelZ - 1); point3 = new Point3DF(modelX - 1, 0, tempZ); } } points = new CoplanarPoints(point1, point2, point3); return true; } catch (Exception excep) { LogHelper.ErrorLog("ContourHelper.FitLine Failed:" + excep); return false; } } /// /// 获得血管所在切面上的三个点的空间坐标 /// /// /// private CoplanarPoints GetVesselSlicePoints(Dictionary modelResults, int modelX, int modelY, int modelZ) { if (modelResults == null) { throw new ArgumentNullException("modelResults"); } // 从modelResults中找到所有颈动脉血管的中心 List vesselCenters = new List(); for (int ni = 0; ni < modelResults.Count; ni++) { // 可能一幅图上会有两个颈动脉,所以这里要找到所有的颈动脉 var vessels = Array.FindAll(modelResults[ni].DiagResultsForEachOrgan, x => x.Organ == EnumOrgans.CarotidArtery); if (vessels != null) { foreach (var vessel in vessels) { var x = vessel.OrganBoundBox.Left + vessel.OrganBoundBox.Width / 2; var y = vessel.OrganBoundBox.Top + vessel.OrganBoundBox.Height / 2; // 因为这里模型已经被采样成xyz方向均匀分布的立方体,所以z直接为ni即可 var z = ni; vesselCenters.Add(new Point3D(x, y, z)); } } } // 点数不够,直接返回 if (vesselCenters.Count < 3) { return CoplanarPoints.Empty; } // 计算每个中心点在y=0平面上的投影(因为默认扫查时是颈动脉横切面,且是沿着颈动脉的长轴方向连续扫查) // 因此求y=0平面上的投影,能得到颈动脉的长轴切面 var projectPoints = new Point2D[vesselCenters.Count]; for (int ni = 0; ni < vesselCenters.Count; ni++) { projectPoints[ni].X = vesselCenters[ni].X; projectPoints[ni].Y = vesselCenters[ni].Z; } // 计算所有投影点的拟合直线 if (!ContourHelper.FitLines(projectPoints, out var line, true)) { return CoplanarPoints.Empty; } // 计算三个过拟合直线,且和y轴平行的平面上的点 if (!GetCoplanarPointsWithProjectionLine(line, modelX, modelY, modelZ, out var points)) { return CoplanarPoints.Empty; } return points; } 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 } }