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