using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Drawing; using AI.Common; using AutoEFInferenceCalcLib; using System.Windows.Threading; using ImageShowUtilsLib; using System.Collections.Concurrent; using Newtonsoft.Json; namespace AutoEFCalculationDemo { /// /// 界面大小变化 /// public class MyTransform { public double Scale { get; set; } public double OffsetX { get; set; } public double OffsetY { get; set; } public MyTransform(double scale, double offsetX, double offsetY) { Scale = scale; OffsetX = offsetX; OffsetY = offsetY; } public System.Windows.Point Transform(System.Windows.Point point) { double x = OffsetX + Scale * point.X; double y = OffsetY + Scale * point.Y; return new System.Windows.Point(x, y); } public System.Windows.Rect Transform(System.Windows.Rect rect) { double left = OffsetX + Scale * rect.Left; double top = OffsetY + Scale * rect.Top; double width = Scale * rect.Width; double height = Scale * rect.Height; return new System.Windows.Rect(left, top, width, height); } } internal class Roi { internal AI.Common.Rect BoundingBox { get; set; } internal System.Windows.Media.Brush Color { get; set; } internal Point2D[] Contour { get; set; } internal string LabelName { get; set; } internal Roi(Point2D[] contour, System.Windows.Media.Brush color, string labelName) { Contour = contour; LabelName = labelName; Color = color; } } public class Organ { public string OrganName { get; set; } public AI.Common.Rect BoundingBox { get; set; } public Point2D[][] Contour { get; set; } public System.Windows.Media.Brush Color => System.Windows.Media.Brushes.Green; public Organ(string organName, AI.Common.Rect boundBox, Point2D[][] contour) { OrganName = organName; BoundingBox = boundBox; Contour = contour; } } /// /// ImageInfo.xaml 的交互逻辑 /// public partial class ImageInfo : UserControl { #region private private volatile bool _enableAI; private volatile bool _enableShowObjectContour; private volatile bool _enableShowMeasureLine; private volatile bool _enableShowMeasureCurve; private volatile bool _enableShowTimeSeriesResult; private Bitmap _image; private MyTransform _transform; private List _rois; private CardiacCurveInfos _cardiacCurveInfos; private MeasureCurveInfo _measureCurve = new MeasureCurveInfo(10000, 1000, 0, 200, 40.0,new List {"AutoEF.Volume", "AutoEF.CardiacCycle" }); private ConcurrentDictionary _imageInfo = new ConcurrentDictionary(); private Dictionary _hasShow = new Dictionary(); #endregion #region properties private void CanvasChangeSize(object sender, SizeChangedEventArgs e) { RoiCanvas.Width = GridOrigImg.ActualWidth; RoiCanvas.Height = GridOrigImg.ActualHeight; UpdateTransforms(); UpdateRois(); UpdateShowMeasureLine(); UpdateTimeSeriesResultPanelItems(); } #endregion #region public func public ImageInfo() { InitializeComponent(); } public void SetImageShowsParam(bool enableAI, bool showObject) { _enableAI = enableAI; _enableShowObjectContour = showObject; if(_rois != null) { UpdateRois(); } if(_cardiacCurveInfos != null) { UpdateShowMeasureLine(); } } public void SetShowMeasureLine(bool showMeasureLine) { _enableShowMeasureLine = showMeasureLine; if( _rois != null) { UpdateRois(); } if(_cardiacCurveInfos != null) { UpdateShowMeasureLine(); } } public void SetShowTimeSeriesResult(bool showTimeSeriesResult) { _enableShowTimeSeriesResult = showTimeSeriesResult; if( _rois != null) { UpdateRois(); } if (_cardiacCurveInfos != null) { UpdateShowMeasureLine(); } //if ( _cardiacCurveInfos != null) //{ // UpdateTimeSeriesResultPanelItems(); //} //if( _cardiacCurveInfos != null) //{ // UpdateShowMeasureCurve(); //} } public void SetShowMeasureCurve(bool showMeasureCurve) { _enableShowMeasureCurve = showMeasureCurve; if( _rois != null) { UpdateRois(); } if( _cardiacCurveInfos != null) { UpdateShowMeasureLine(); } } public void UpdateImage(double frameTime, Bitmap image) { _image = image.Clone(new System.Drawing.Rectangle(0,0,image.Width, image.Height), System.Drawing.Imaging.PixelFormat.Format24bppRgb); _imageInfo.TryAdd(frameTime, _image); //更新缩放比例 UpdateTransforms(); //更新图像显示 var bitmapImage = RawImageShowUtils.BitmapToBitmapImage(_image); Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { ImageShow.Source = bitmapImage; })); } public void UpdateDiagResult(string dataID, double currentTimeStamp, CardiacCurveInfos cardiacCurveInfos = null) { List rois = new List(); LVVolumeCalcResult detectResult =cardiacCurveInfos.ResultPerImage.Last().Value; if(detectResult.Contour != null) { string labelName = "左心室内轮廓"; System.Windows.Media.Brush brush = System.Windows.Media.Brushes.DodgerBlue; Roi roi = new Roi(detectResult.Contour, brush, labelName); rois.Add(roi); } //删掉旧的Roi和Organ RemoveMaesureLine(); RemoveRois(); RemoveMeasureCurve(); //RemoveTimeSeriesResult(); //设置 _rois = rois; _cardiacCurveInfos = cardiacCurveInfos; //更新画布上显示的Roi UpdateRois(); UpdateShowMeasureLine(); UpdateShowMeasureCurve(dataID, currentTimeStamp); UpdateTimeSeriesResultPanelItems(); } public void ClearDiagResult() { //删掉已有的Roi RemoveRois(); RemoveMaesureLine() ; RemoveMeasureCurve(); RemoveTimeSeriesResult(); //设置 _rois = null; _cardiacCurveInfos = null; _measureCurve.ClaerResult(); _hasShow.Clear(); _imageInfo.Clear(); } #endregion #region Draw /// /// 在画布上画出轮廓 /// /// /// private void DrawRoiInCanvas(Roi roi, string roiIndex) { if(_transform == null) { return; } RemoveRoiInCanvas(roiIndex); //如果不启用AI,则直接退出‘ if(!_enableAI) { return; } //直接画轮廓 if(_enableShowObjectContour) { if(roi.Contour?.Length <= 0) { return; } PointCollection contourPoint = new PointCollection(); for(int i = 0; i < roi.Contour.Length; i++) { var point = new System.Windows.Point(roi.Contour[i].X, roi.Contour[i].Y); var transPoint = _transform.Transform(point); contourPoint.Add(transPoint); } //画出识别到的目标的结构 System.Windows.Shapes.Polyline contourDraw = new System.Windows.Shapes.Polyline(); contourDraw.Points = contourPoint; contourDraw.StrokeThickness = 2; contourDraw.Stroke = roi.Color; RoiCanvas.Children.Add(contourDraw); string roiName = "Roi_Contour" + roiIndex; RoiCanvas.RegisterName(roiName, contourDraw); //画出心肌末端的连线 System.Windows.Shapes.Line endLine = new System.Windows.Shapes.Line(); endLine.X1 = contourPoint[0].X; endLine.Y1 = contourPoint[0].Y; endLine.X2 = contourPoint[contourPoint.Count - 1].X; endLine.Y2 = contourPoint[contourPoint.Count - 1].Y; endLine.StrokeThickness = 2; endLine.Stroke = roi.Color; RoiCanvas.Children.Add(endLine); string lineName = "EndLine" + roiIndex; RoiCanvas.RegisterName(lineName, endLine); //TODO:显示识别部位的名称 } } private void DrawMeasureLines(CardiacCurveInfos curveInfos) { if(_transform == null) { return;} //已有则删除 RemoveMaesureLine(); //若不启用AI则退出 if(!_enableAI) { return; } LVVolumeCalcResult resultPerImage = curveInfos.ResultPerImage.Last().Value; if (resultPerImage == LVVolumeCalcResult.Empty) { return; } //画测量线 if (_enableShowMeasureLine) { //显示切片点 PointCollection slicePoints = new PointCollection(); var slicePointOri = resultPerImage.MeasureMarks.SlicePoint; var controlPointOri = resultPerImage.MeasureMarks.ControlPoints; for (int i = 0; i < slicePointOri.Length; i++) { var slicePoint = new System.Windows.Point(slicePointOri[i].X, slicePointOri[i].Y); slicePoints.Add(slicePoint); } //显示切片连线 for(int i = 0; i < slicePointOri.Length / 2; i++) { var transPoint1 = _transform.Transform(slicePoints[2 * i]); var transPoint2 = _transform.Transform(slicePoints[2 * i + 1]); System.Windows.Shapes.Line sliceLine = new System.Windows.Shapes.Line(); sliceLine.X1 = transPoint1.X; sliceLine.Y1 = transPoint1.Y; sliceLine.X2 = transPoint2.X; sliceLine.Y2 = transPoint2.Y; sliceLine.StrokeThickness = 1; sliceLine.Stroke = System.Windows.Media.Brushes.Coral; RoiCanvas.Children.Add(sliceLine); string lineName = "MeasureLine_" + i.ToString(); RoiCanvas.RegisterName(lineName, sliceLine); } //显示控制点 for(int i = 0; i < controlPointOri.Length; i++) { var controlPoint = new System.Windows.Point(controlPointOri[i].X, controlPointOri[i].Y); var transPoint = _transform.Transform(controlPoint); System.Windows.Shapes.Path circleDraw = new System.Windows.Shapes.Path(); EllipseGeometry ellipseGeometry = new EllipseGeometry(); ellipseGeometry.Center = transPoint; ellipseGeometry.RadiusX = 3; ellipseGeometry.RadiusY = 3; circleDraw.Data = ellipseGeometry; circleDraw.Fill = System.Windows.Media.Brushes.DarkRed; RoiCanvas.Children.Add(circleDraw); string circleName = "ControlPoint_" + i.ToString(); RoiCanvas.RegisterName(circleName, circleDraw); } //显示左心室中轴线 var apexPoint = new System.Windows.Point(resultPerImage.MeasureMarks.ApexPoint.X, resultPerImage.MeasureMarks.ApexPoint.Y); var endPoints = resultPerImage.MeasureMarks.EndPoints; System.Windows.Point centerPoint = new System.Windows.Point(); centerPoint.X = (endPoints[0].X + endPoints[1].X) / 2; centerPoint.Y = (endPoints[0].Y + endPoints[1].Y) / 2; var transApexPoint = _transform.Transform(apexPoint); var transCenterPoint = _transform.Transform(centerPoint); System.Windows.Shapes.Line axisLine = new System.Windows.Shapes.Line(); axisLine.X1 = transApexPoint.X; axisLine.Y1 = transApexPoint.Y; axisLine.X2 = transCenterPoint.X; axisLine.Y2 = transCenterPoint.Y; axisLine.StrokeThickness = 1; axisLine.Stroke = System.Windows.Media.Brushes.Coral; RoiCanvas.Children.Add(axisLine); string axisLineName = "AxisLine"; RoiCanvas.RegisterName(axisLineName, axisLine); } } #endregion #region remove /// /// 删除Roi轮廓 /// /// private void RemoveRoiInCanvas(string roiIndex) { //删除轮廓 string contourName = "Roi_Contour" + roiIndex; System.Windows.Shapes.Polyline contourExist = RoiCanvas.FindName(contourName) as System.Windows.Shapes.Polyline; if (contourExist != null) { RoiCanvas.Children.Remove(contourExist); RoiCanvas.UnregisterName(contourName); } string lineName = "EndLine" + roiIndex; System.Windows.Shapes.Line lineExist = RoiCanvas.FindName(lineName) as System.Windows.Shapes.Line; if(lineExist != null) { RoiCanvas.Children.Remove(lineExist); RoiCanvas.UnregisterName(lineName); } } private void RemoveMaesure() { //删除测量线 for (int i = 0; i < _cardiacCurveInfos.ResultPerImage.Last().Value.MeasureMarks.SlicePoint.Length / 2; i++) { string measureLineName = "MeasureLine_" + i.ToString(); System.Windows.Shapes.Line lineExist = RoiCanvas.FindName(measureLineName) as System.Windows.Shapes.Line; if (lineExist != null) { RoiCanvas.Children.Remove(lineExist); RoiCanvas.UnregisterName(measureLineName); } } //删除控制点 for(int i = 0; i < _cardiacCurveInfos.ResultPerImage.Last().Value.MeasureMarks.ControlPoints.Length; i++) { string circleName = "ControlPoint_" + i.ToString(); System.Windows.Shapes.Path circleExist = RoiCanvas.FindName(circleName) as System.Windows.Shapes.Path; if (circleExist != null) { RoiCanvas.Children.Remove(circleExist); RoiCanvas.UnregisterName(circleName); } } //删除中轴线 string axisLineName = "AxisLine"; System.Windows.Shapes.Line axisLineExist = RoiCanvas.FindName(axisLineName) as System.Windows.Shapes.Line; if (axisLineExist != null) { RoiCanvas.Children.Remove(axisLineExist); RoiCanvas.UnregisterName(axisLineName); } } private void RemoveRois() { Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { if(_rois != null) { for(int i = 0; i < _rois.Count;i++) { RemoveRoiInCanvas(i.ToString()); } } })); } private void RemoveMaesureLine() { Dispatcher.Invoke((Action)(() => { if(_cardiacCurveInfos != null) { RemoveMaesure(); } })); } private void RemoveMeasureCurve() { Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { PanelMeasureCurves.Children.Clear(); })); } private void RemoveTimeSeriesResult() { Dispatcher.Invoke(() => { PanelTimeSeriesBasedResults.Children.Clear(); }); } #endregion #region private private void UpdateTransforms() { if(_image != null) { var imgWidth = _image.Width; var imgHeight = _image.Height; var canvasWidth = GridOrigImg.ActualWidth; var canvasHeight = GridOrigImg.ActualHeight; var scaleX = canvasWidth / imgWidth; var scaleY = canvasHeight / imgHeight; var scale = scaleX < scaleY ? scaleX : scaleY; double offsetX, offsetY; if (Math.Abs(scale - scaleX) < 0.0001) { offsetY = 0.5 * (scaleY - scale) * imgHeight; offsetX = 0; } else { offsetX = 0.5 * (scaleX - scale) * imgWidth; offsetY = 0; } Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { //更新画布尺寸,使其刚好只覆盖图像区域 RoiCanvas.Width = scale * _image.Width; RoiCanvas.Height = scale * _image.Height; Canvas.SetLeft(RoiCanvas, offsetX); Canvas.SetTop(RoiCanvas, offsetY); OrigImgScaleTransform.CenterX = 0; OrigImgScaleTransform.CenterY = 0; OrigImgScaleTransform.ScaleX = scale; OrigImgScaleTransform.ScaleY = scale; })); _transform = new MyTransform(scale, 0, 0); } } private void UpdateRois() { Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { if(_rois != null) { for(int i = 0; i < _rois.Count; i++) { DrawRoiInCanvas(_rois[i], i.ToString()); } } })); } private void UpdateShowMeasureLine() { Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { if(_cardiacCurveInfos != null) { DrawMeasureLines(_cardiacCurveInfos); } })); } private void UpdateShowMeasureCurve(string dataID, double currentTimeStamp) { Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { var curve = new CurveInfo(_measureCurve); PanelMeasureCurves.Children.Add(curve); List> cardiacCurveInfos = new List>(); cardiacCurveInfos.Add(_cardiacCurveInfos.ResultPerImage); CardiacCycleInfos[] cardiacCycles = _cardiacCurveInfos.CardiacCycles; _measureCurve.UpdateCurveResult(dataID, currentTimeStamp, cardiacCurveInfos, cardiacCycles); })); } private void UpdateTimeSeriesResultPanelItems() { List> _readyShowSingleFrame = new List>(); bool hasTimeSeriesNoShow = false; if (_cardiacCurveInfos != null) { foreach (var cycleInfo in _cardiacCurveInfos.CardiacCycles) { var edesResult = new Dictionary(); var edInfo = cycleInfo.EDTimeStamp; var esInfo = cycleInfo.ESTimeStamp; if(cycleInfo.EDVolume == 0 || cycleInfo.ESVolume == 0) { continue; } if (!_cardiacCurveInfos.ResultPerImage.ContainsKey(cycleInfo.EDTimeStamp) || !_cardiacCurveInfos.ResultPerImage.ContainsKey(cycleInfo.ESTimeStamp)) { continue; } edesResult.Add(edInfo, _cardiacCurveInfos.ResultPerImage[cycleInfo.EDTimeStamp]); edesResult.Add(esInfo, _cardiacCurveInfos.ResultPerImage[cycleInfo.ESTimeStamp]); if(!_hasShow.ContainsKey(edInfo) && !_hasShow.ContainsKey(esInfo)) { hasTimeSeriesNoShow = true; _readyShowSingleFrame.Add(edesResult); _hasShow.Add(edInfo, _cardiacCurveInfos.ResultPerImage[cycleInfo.EDTimeStamp]); _hasShow.Add(esInfo, _cardiacCurveInfos.ResultPerImage[cycleInfo.ESTimeStamp]); } } if(_readyShowSingleFrame.Count > 0 && hasTimeSeriesNoShow) { RemoveTimeSeriesResult(); foreach(var timeSeries in _readyShowSingleFrame) { //取出当前图像对应的结果,得分等,生成对应的UserControl添加到Children中去 string resultName = "AutoEFCalculation"; Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => { PanelTimeSeriesBasedResults.Children.Add(GetTextBlock(resultName + ":")); })); var keyList = timeSeries.Keys.ToList(); var edVolumeName = "EDVloume"; var esVolumeName = "ESVloume"; var efValueName = "EFValue"; var edVolume = timeSeries[keyList[0]].Volume; var esVolume = timeSeries[keyList[1]].Volume; var efValue = (edVolume - esVolume) / edVolume; Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => { PanelTimeSeriesBasedResults.Children.Add(GetTextBlock(edVolumeName + ":" + edVolume.ToString("f2"))); PanelTimeSeriesBasedResults.Children.Add(GetTextBlock(esVolumeName + ":" + esVolume.ToString("f2"))); PanelTimeSeriesBasedResults.Children.Add(GetTextBlock(efValueName + ":" + (efValue * 100).ToString("f2") + "%")); })); var edImage = _imageInfo[keyList[0]]; var esImage = _imageInfo[keyList[1]]; Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => { PanelTimeSeriesBasedResults.Children.Add(GetTextBlock("ED Frame" + "(" + keyList[0].ToString("f2") + "):")); PanelTimeSeriesBasedResults.Children.Add(GetTextBlock("ED ControlPoints Number:" + timeSeries[keyList[0]].MeasureMarks.ControlPoints.Length.ToString())); PanelTimeSeriesBasedResults.Children.Add(new RecognizedImages(edImage, timeSeries[keyList[0]], _enableShowObjectContour, _enableShowMeasureLine)); PanelTimeSeriesBasedResults.Children.Add(GetTextBlock("ES Frame" + "(" + keyList[1].ToString("f2") + "):")); PanelTimeSeriesBasedResults.Children.Add(GetTextBlock("ES ControlPoints Number:" + timeSeries[keyList[1]].MeasureMarks.ControlPoints.Length.ToString())); PanelTimeSeriesBasedResults.Children.Add(new RecognizedImages(esImage, timeSeries[keyList[1]], _enableShowObjectContour, _enableShowMeasureLine)); })); } } } //把存储的超出时间的图像和数据删掉 var imageKey = _imageInfo.Keys.ToList(); int imagCount = imageKey.Count; for( int i = imagCount - 1; i >= 0; i-- ) { var key = imageKey[i]; if( key < _cardiacCurveInfos.ResultPerImage.Keys.First()) { _imageInfo.TryRemove(key, out _); } } var showKey = _hasShow.Keys.ToList(); int showCount = showKey.Count; for ( int i = showCount - 1; i >= 0; i-- ) { var key = showKey[i]; if(key < _cardiacCurveInfos.ResultPerImage.Keys.First() ) { _hasShow.Remove(key); } } } private TextBlock GetTextBlock(string text) { var textBlock = new TextBlock(); textBlock.Text = text; textBlock.Margin = new Thickness(4,4,4,4); return textBlock; } #endregion } }