123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- 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
- /// <summary>
- /// 通知订阅者,推理过程中发生了错误
- /// </summary>
- public event EventHandler<ErrorEventArgs> NotifyError;
- /// <summary>
- /// 通知订阅者,推理过程中有log要记录
- /// </summary>
- public event EventHandler<LogEventArgs> 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
- /// <summary>
- /// 根据AI结果,自动选取最优的血管内膜切面,返回图像上的三个3D坐标点
- /// </summary>
- /// <param name="modelResults"></param>
- /// <param name="modelSize"></param>
- /// <param name="imageWidth"></param>
- /// <param name="imageHeight"></param>
- /// <returns></returns>
- public List<Point3DF> GetClipPoints(Dictionary<int, AIDiagResultPerImg> 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<Point3DF> pointList = new List<Point3DF> { intimaSlicePoints.Point1, intimaSlicePoints.Point2, intimaSlicePoints.Point3 };
- return pointList;
- }
- public Dictionary<int, AIDiagResultPerImg> 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<int, RawImage>();
- 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<RawImage> { 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
- /// <summary>
- /// 根据三维点集投影到Y=0平面上的投影线的信息,求过这条投影线的平面上的三个空间点的位置
- /// </summary>
- /// <param name="projectionLine"></param>
- /// <returns></returns>
- 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;
- }
- }
- /// <summary>
- /// 获得血管所在切面上的三个点的空间坐标
- /// </summary>
- /// <param name="modelResults"></param>
- /// <returns></returns>
- private CoplanarPoints GetVesselSlicePoints(Dictionary<int, AIDiagResultPerImg> modelResults, int modelX, int modelY, int modelZ)
- {
- if (modelResults == null)
- {
- throw new ArgumentNullException("modelResults");
- }
- // 从modelResults中找到所有颈动脉血管的中心
- List<Point3D> vesselCenters = new List<Point3D>();
- 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
- }
- }
|