using System; using System.Collections.Generic; using System.IO; using System.Linq; using Newtonsoft.Json.Linq; using SkiaSharp; using Vinno.vCloud.Common.Vid2; using Vinno.vCloud.Common.Vid2.Visuals; using WingInterfaceLibrary.Enum; using WingServerCommon.Log; namespace WingAIDiagnosisService.Manage { public abstract class BaseDiagnosis { public DiagnosisOrganEnum Organ; public List RecordDiagnosisResult { get; set; } = new(); private readonly string _tempFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DiagnosisTemp"); private static readonly VidTag DicomTagPixelSpacing = new VidTag("0028", "0030"); /// /// 0018, 602E Physical Delta Y /// private static readonly VidTag DicomTagPhysicalDeltaY = new VidTag("0018", "602E"); /// /// 0018,602C Physical Delta X /// private static readonly VidTag DicomTagPhysicalDeltaX = new VidTag("0018", "602C"); /// /// 0018, 6024 Physical Units X /// private static readonly VidTag DicomTagPhysicalUnitsX = new VidTag("0018", "6024"); /// /// 0018, 6026 Physical Units Y /// private static readonly VidTag DicomTagPhysicalUnitsY = new VidTag("0018", "6026"); public BaseDiagnosis(List recordResult, DiagnosisOrganEnum organ) { RecordDiagnosisResult = recordResult; Organ = organ; CheckDiagResultIndex(); } /// 筛选有效的AI诊断记录 /// /// public virtual void CheckDiagResultIndex() { var validResult = new List(); foreach (var diagResult in RecordDiagnosisResult) { var organResultList = new List(); foreach (var organResult in diagResult.DiagResultsForEachOrgan) { var valid = CheckResultValid(organResult); if (valid != null) { organResultList.Add(valid); } } if (organResultList.Any()) { diagResult.DiagResultsForEachOrgan = organResultList; validResult.Add(diagResult); } } RecordDiagnosisResult = validResult.OrderByDescending(x => x.PriorityScore).ToList();//根据评分倒序 } /// 筛选报告可用诊断结果 /// /// public abstract List GetReportResults(); /// 判断并返回有效的诊断描述 /// /// public abstract AIDiagnosisResultPerOrgan CheckResultValid(AIDiagnosisResultPerOrgan message); /// 是否有病灶 /// /// public bool HasSick() { foreach (var data in RecordDiagnosisResult) { var labels = data.GetLabels(); if (labels.Any(x => x > 0)) { return true; } } return false; } /// 判断AI诊断结果 /// public abstract DiagnosisConclusion GetAIStatus(); /// /// 是否恶性病灶 /// /// /// protected abstract bool IsMalignant(AIDetectedObject detectedObject); /// 计算最大尺寸 /// /// protected double GetMaxLesionSize(AIDetectedObject detectedObjectInfo) { if (detectedObjectInfo.Descriptions == null || !detectedObjectInfo.Descriptions.Any()) { return 0; } var descriptionList = detectedObjectInfo.Descriptions.Where(d => d?.Type == DiagnosisDescription.LesionSize); if (descriptionList == null || !descriptionList.Any()) { return 0; } double currArea = 0; foreach (var lesion in detectedObjectInfo.Descriptions) { if (lesion.Type == DiagnosisDescription.LesionSize && !string.IsNullOrWhiteSpace(lesion.Value)) { var lessionSizeInfo = Newtonsoft.Json.JsonConvert.DeserializeObject(lesion.Value); var area = lessionSizeInfo.HorizontalLengthInPixel * lessionSizeInfo.VerticalLengthInPixel; if (area > currArea) { currArea = area; } } } return currArea; } protected void InitialAIImage() { foreach (var remedical in RecordDiagnosisResult) { var result = GetAIImageInfo(remedical); remedical.Pixel = result.Item1; remedical.AILocalImagePath = result.Item2; } } protected Tuple GetAIImageInfo(DiagnosisPerImageModel diagResult) { var vinnoImageData = new VinnoImageData(diagResult.LocalVidPath, OperationMode.Open); var vinnoImage = vinnoImageData.GetImage(diagResult.Index); double pixelLength; if (diagResult.IsVinnoVid) { pixelLength = GetLengthPerPxiel(vinnoImage); } else { pixelLength = GetPixelSpacingForVidExamData(vinnoImageData.ExtendedData); } var newBytes = GetNewImageBuffer(vinnoImage.ImageData, diagResult, vinnoImage.Width, vinnoImage.Height); var aiLocalFile = Path.Combine(_tempFolder, $"{Guid.NewGuid():N}.jpg"); File.WriteAllBytes(aiLocalFile, newBytes); vinnoImageData.Dispose(); return new Tuple(pixelLength, aiLocalFile); } private byte[] GetNewImageBuffer(byte[] imageData, DiagnosisPerImageModel diagResult, int imageWidth, int imageHeight, float widthPixelScale = 1, float heightPixelScale = 1) { var newImageBuffer = imageData; try { if (diagResult.DiagResultsForEachOrgan == null || diagResult.DiagResultsForEachOrgan.All(x => x.DetectedObjects.All(x => x.Label == 0))) { return newImageBuffer; } var skImageInfo = new SKImageInfo(imageWidth, imageHeight, SKImageInfo.PlatformColorType, SKAlphaType.Premul); using (var surface = SKSurface.Create(skImageInfo)) { var canvas = surface.Canvas; using (var bmp = SKBitmap.Decode(imageData)) { canvas.DrawBitmap(bmp, SKRect.Create(imageWidth, imageHeight)); var sizeRate = bmp.Height / 160.0; var fontSize = Convert.ToInt32(Math.Round(sizeRate * 6)); var currentDetectInfos = new List>(); List kPoints = new List(); using (var rectangelPaint = new SKPaint { StrokeWidth = (float)sizeRate / 1.5f, Color = SKColors.Orange, Style = SKPaintStyle.Fill }) { foreach (var record in diagResult.DiagResultsForEachOrgan) { if (record != null) { foreach (var detectedInfo in record.DetectedObjects) { if (detectedInfo.Label > 0 && detectedInfo.BoundingBox != null && detectedInfo.Descriptions != null && detectedInfo.Descriptions.Count() > 0) { var isMalignant = IsMalignant(detectedInfo); var color = isMalignant ? SKColors.Orange : SKColors.Lime; rectangelPaint.Color = color; var rect = detectedInfo.BoundingBox; var lesion = detectedInfo.Descriptions.FirstOrDefault(a => a.Type == DiagnosisDescription.LesionSize); if (lesion != null && !string.IsNullOrWhiteSpace(lesion.Value)) { var lessionSizeInfo = Newtonsoft.Json.JsonConvert.DeserializeObject(lesion.Value); if (lessionSizeInfo.HorizontalLengthInPixel > 0 && lessionSizeInfo.VerticalLengthInPixel > 0) { List sKPoints = new List(); int index = 0; var kPoint = new SKPoint(-1, -1); foreach (var point in detectedInfo.Contours) { if (index % 10 == 0) { var poin = new SKPoint(point.X * widthPixelScale, point.Y * heightPixelScale); sKPoints.Add(poin); if ((kPoint.X < 0 && kPoint.Y < 0) || kPoint.X > poin.X) { kPoint = poin; } } index++; } kPoints.Add(kPoint); canvas.DrawPoints(SKPointMode.Points, sKPoints.ToArray(), rectangelPaint); List sKPointLine = new List(); foreach (var item in detectedInfo.Descriptions) { if (item.Type == DiagnosisDescription.LesionSize && !string.IsNullOrWhiteSpace(item.Value)) { JObject jobj = JObject.Parse(item.Value); foreach (var jobPoint in jobj) { if (!jobPoint.Key.Contains("Pixel")) { int x = Convert.ToInt32(jobPoint.Value.First.First); int y = Convert.ToInt32(jobPoint.Value.First.Next.First); sKPointLine.Add(new SKPoint(x, y)); } } } } //横纵径虚线显示 for (int j = 0; j < 2; j++) { float LineX = sKPointLine[2 * j].X * widthPixelScale - sKPointLine[2 * j + 1].X * widthPixelScale; float LineY = sKPointLine[2 * j].Y * heightPixelScale - sKPointLine[2 * j + 1].Y * heightPixelScale; double LineLength = Math.Sqrt(Math.Pow(LineX, 2) + Math.Pow(LineY, 2)); float LineLengthX = (float)(LineX / LineLength); float LineLengthY = (float)(LineY / LineLength); List sKLinePoint = new List(); for (int i = 0; i < LineLength; i = i + 10) { sKLinePoint.Add(new SKPoint(sKPointLine[2 * j + 1].X * widthPixelScale + LineLengthX * i, sKPointLine[2 * j + 1].Y * heightPixelScale + LineLengthY * i)); } var linePaint = new SKPaint { StrokeWidth = (float)sizeRate / 2.5f, Color = color, Style = SKPaintStyle.Fill }; canvas.DrawPoints(SKPointMode.Points, sKLinePoint.ToArray(), linePaint); canvas.DrawLine(new SKPoint(sKPointLine[2 * j].X * widthPixelScale - 5, sKPointLine[2 * j].Y * heightPixelScale), new SKPoint(sKPointLine[2 * j].X * (float)widthPixelScale + 5, sKPointLine[2 * j].Y * (float)heightPixelScale), linePaint); canvas.DrawLine(new SKPoint(sKPointLine[2 * j].X * widthPixelScale, sKPointLine[2 * j].Y * heightPixelScale - 5), new SKPoint(sKPointLine[2 * j].X * (float)widthPixelScale, sKPointLine[2 * j].Y * (float)heightPixelScale + 5), linePaint); canvas.DrawLine(new SKPoint(sKPointLine[2 * j + 1].X * widthPixelScale - 5, sKPointLine[2 * j + 1].Y * heightPixelScale), new SKPoint(sKPointLine[2 * j + 1].X * widthPixelScale + 5, sKPointLine[2 * j + 1].Y * heightPixelScale), linePaint); canvas.DrawLine(new SKPoint(sKPointLine[2 * j + 1].X * widthPixelScale, sKPointLine[2 * j + 1].Y * heightPixelScale - 5), new SKPoint(sKPointLine[2 * j + 1].X * widthPixelScale, sKPointLine[2 * j + 1].Y * heightPixelScale + 5), linePaint); } currentDetectInfos.Add(new Tuple(detectedInfo, isMalignant)); } } } } } } } //Draw Index using (var indexPaint = new SKPaint { Color = SKColors.Orange, StrokeWidth = (float)sizeRate, TextSize = fontSize, IsLinearText = true, IsAntialias = true }) { var count = 1; foreach (var info in currentDetectInfos) { var detectedInfo = info.Item1; var color = info.Item2 ? SKColors.Orange : SKColors.Lime; if (detectedInfo.Label > 0 && detectedInfo.BoundingBox != null && detectedInfo.Descriptions != null && detectedInfo.Descriptions.Count() > 0) { indexPaint.Color = color; var tuple = new SKPoint(kPoints[count - 1].X - 25, kPoints[count - 1].Y) /*GetIndexPosition(currentDetectInfos, detectedInfo.BondingBox, bmp.Width, sizeRate)*/; canvas.DrawText(count.ToString(), tuple, indexPaint); count++; } } if (count > 1) { newImageBuffer = surface.Snapshot().Encode().ToArray(); } } } } } catch (Exception ex) { Logger.WriteLineError("AI image draw rectangle fail," + ex); } return newImageBuffer; } private double GetLengthPerPxiel(VinnoImage image) { var lengthPerPixel = 0.1; try { var visual = image.Visuals.FirstOrDefault(x => x is Vinno2DVisual) as Vinno2DVisual; if (visual != null) { var logicalCoordinates = visual.LogicalCoordinates; if (logicalCoordinates.Count > 0 && logicalCoordinates.ContainsKey(VinnoVisualAreaType.Tissue)) { var logicInfo = logicalCoordinates[VinnoVisualAreaType.Tissue]; var region = logicInfo.Region; var unit = logicInfo.XUnit; var depth = region.Height; var height = image.Height; if (height > 0) { lengthPerPixel = depth / height; } } } } catch (Exception ex) { Logger.WriteLineError($"Get pxiel length failed: {ex}"); } return lengthPerPixel; } private double GetPixelSpacingForVidExamData(byte[] extendedDatabytes) { var extendedData = VidExtendedData.FromBytes(extendedDatabytes); if (extendedData != null) { PixelSpacing pixelSpacing = new PixelSpacing(); var pixelSpacingValue = GetVidValueByTag(DicomTagPixelSpacing, extendedData); if (pixelSpacingValue != null) { var pixelSpacingArray = pixelSpacingValue.GetValue().ToString().Split('\\'); if (pixelSpacingArray.Length > 1) { if (double.TryParse(pixelSpacingArray[0], out double pixelSpacingX)) { pixelSpacing.PhysicalDeltaX = pixelSpacingX; } if (double.TryParse(pixelSpacingArray[1], out double pixelSpacingY)) { pixelSpacing.PhysicalDeltaY = pixelSpacingY; } } } else { pixelSpacing.PhysicalDeltaX = GetPhysicalDeltaX(extendedData); pixelSpacing.PhysicalDeltaY = GetPhysicalDeltaY(extendedData); } if (pixelSpacing.PhysicalDeltaX > 0) return pixelSpacing.PhysicalDeltaX / 10.0; if (pixelSpacing.PhysicalDeltaY > 0) return pixelSpacing.PhysicalDeltaY / 10.0; } return 0.1; } private IVidValue GetVidValueByTag(VidTag vidTag, VidExtendedData extendedData) { IVidValue vidValue = null; var vidTagkey = extendedData.Data?.Keys.FirstOrDefault(v => v.Group == vidTag.Group && v.Element == vidTag.Element); if (vidTagkey != null) { var flag = extendedData.Data?.TryGetValue(vidTagkey, out vidValue); if (flag.Value) { return vidValue; } } return null; } private double GetPhysicalDeltaX(VidExtendedData extendedData) { IVidValue physicalDeltaXvalue = GetVidValueByTag(DicomTagPhysicalDeltaX, extendedData); if (physicalDeltaXvalue != null) { IVidValue physicalUnitsXvalue = GetVidValueByTag(DicomTagPhysicalUnitsX, extendedData); if (physicalUnitsXvalue != null) { var unitsvalue = Convert.ToInt16(physicalUnitsXvalue.GetValue().ToString()); /// unit=3 is cm if (unitsvalue == 3) { var physicalDeltaX = Convert.ToDouble(physicalDeltaXvalue.GetValue().ToString()) * 10; if (physicalDeltaX > 0) return physicalDeltaX; } } } return 1.0; } private double GetPhysicalDeltaY(VidExtendedData extendedData) { IVidValue physicalDeltaYvalue = GetVidValueByTag(DicomTagPhysicalDeltaY, extendedData); if (physicalDeltaYvalue != null) { IVidValue physicalUnitsYvalue = GetVidValueByTag(DicomTagPhysicalUnitsY, extendedData); if (physicalUnitsYvalue != null) { var unitsvalue = Convert.ToInt16(physicalUnitsYvalue.GetValue().ToString()); /// unit=3 is cm if (unitsvalue == 3) { var physicalDeltaX = Convert.ToDouble(physicalDeltaYvalue.GetValue().ToString()) * 10; return physicalDeltaX; } } } return 1.0; } } }