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
}
}