Browse Source

update(measure): 初步接入颈动脉测量项

gavin.chen 2 years ago
parent
commit
c1c3e4f650

+ 4 - 0
lib/interfaces/process/workspace/application.dart

@@ -32,6 +32,10 @@ abstract class IApplication {
   bool get isAdaptiveCarotid2D;
   set isAdaptiveCarotid2D(bool value);
 
+  /// 颈动脉2D图像的尺寸
+  Size get carotid2DSize;
+  set carotid2DSize(Size value);
+
   /// 当前帧
   VidUsImage? get frameData;
 

+ 132 - 0
lib/process/calcuators/shell_calcuator.dart

@@ -0,0 +1,132 @@
+import 'dart:convert';
+import 'dart:ui';
+
+import 'package:fis_measure/interfaces/date_types/point.dart';
+import 'package:fis_measure/interfaces/process/items/measure_terms.dart';
+import 'package:fis_measure/process/primitives/carotid_imt.dart';
+import 'package:fis_measure/utils/js_utils.dart';
+import 'package:vid/us/vid_us_unit.dart';
+
+import 'calculator.dart';
+
+class ShellCal extends Calculator<CarotidIMT, double> {
+  ShellCal(CarotidIMT ref) : super(ref);
+
+  @override
+  void calculate() {
+    if (ref.feature == null) return;
+    final p1 = ref.feature!.startPoint;
+    final p2 = ref.feature!.endPoint;
+    //左上顶点
+    final leftTopPoint =
+        DPoint(p1.x < p2.x ? p1.x : p2.x, p1.y < p2.y ? p1.y : p2.y);
+    //右下顶点
+    final rightBottomPoint =
+        DPoint(p1.x > p2.x ? p1.x : p2.x, p1.y > p2.y ? p1.y : p2.y);
+    final outputItem = createOutput(MeasureTerms.Distance, 0.0, VidUsUnit.cm);
+    //图片尺寸
+    final imageSize = ref.application.carotid2DSize;
+    //画布尺寸
+    final canavsSize = ref.application.displaySize;
+    //图像缩放比
+    final imageScale = ref.application.displayScaleRatio;
+    //标准画布尺寸
+    final stdCanavsSize =
+        Size(canavsSize.width / imageScale, canavsSize.height / imageScale);
+    final imageLeftTopPoint = DPoint(
+        (stdCanavsSize.width - imageSize.width) / 2,
+        (stdCanavsSize.height - imageSize.height) / 2);
+    final imageRightBottomPoint = DPoint(imageLeftTopPoint.x + imageSize.width,
+        imageLeftTopPoint.y + imageSize.height);
+    final rectLeftTopPoint = DPoint(stdCanavsSize.width * leftTopPoint.x,
+        stdCanavsSize.height * leftTopPoint.y);
+    final rectRightBottomPoint = DPoint(
+        stdCanavsSize.width * rightBottomPoint.x,
+        stdCanavsSize.height * rightBottomPoint.y);
+
+    /// 在图像外的点改到图像边缘上
+    if (rectLeftTopPoint.x < imageLeftTopPoint.x) {
+      rectLeftTopPoint.x = imageLeftTopPoint.x;
+    }
+    if (rectLeftTopPoint.x > imageRightBottomPoint.x) {
+      rectLeftTopPoint.x = imageRightBottomPoint.x;
+    }
+    if (rectLeftTopPoint.y < imageLeftTopPoint.y) {
+      rectLeftTopPoint.y = imageLeftTopPoint.y;
+    }
+    if (rectLeftTopPoint.y > imageRightBottomPoint.y) {
+      rectLeftTopPoint.y = imageRightBottomPoint.y;
+    }
+    if (rectRightBottomPoint.x > imageRightBottomPoint.x) {
+      rectRightBottomPoint.x = imageRightBottomPoint.x;
+    }
+    if (rectRightBottomPoint.x < imageLeftTopPoint.x) {
+      rectRightBottomPoint.x = imageLeftTopPoint.x;
+    }
+    if (rectRightBottomPoint.y > imageRightBottomPoint.y) {
+      rectRightBottomPoint.y = imageRightBottomPoint.y;
+    }
+    if (rectRightBottomPoint.y < imageLeftTopPoint.y) {
+      rectRightBottomPoint.y = imageLeftTopPoint.y;
+    }
+
+    final rectLeft = (rectLeftTopPoint.x - imageLeftTopPoint.x).round();
+    final rectTop = (rectLeftTopPoint.y - imageLeftTopPoint.y).round();
+    final rectWidth = (rectRightBottomPoint.x - rectLeftTopPoint.x).round();
+    final rectHeight = (rectRightBottomPoint.y - rectLeftTopPoint.y).round();
+    final params =
+        "{'MeasureItemType':'AntMeasureItem','IntimaRect':{'Left':$rectLeft,'Top':$rectTop,'Width':$rectWidth,'Height':$rectHeight}}";
+
+    String description = "IMT\n Measuring";
+
+    /// [Carotid] ✅在此处通知 Shell 计算,获取 description,touch/mouse finished 时绘制结果
+    try {
+      callShellMethod('getMeasureResult', [params]).callMethod(
+        'then',
+        [
+          (result) {
+            // print("getMeasureResult: $result");
+            final res = jsonDecode(result);
+            // print("getMeasureResult: $res");
+            // print("getMeasureResult: ${res['ErrorCode']}");
+            if (res['ErrorCode'].toString() == "1000") {
+              description =
+                  "Ant.CCA IMT\n Max: ${res['MaxThickness'].toStringAsFixed(2)}mm\n Min: ${res['MinThickness'].toStringAsFixed(2)}mm\n Avg: ${res['AverageThickness'].toStringAsFixed(2)}mm\n SD: ${res['SdThickness'].toStringAsFixed(2)}mm";
+              List<Offset> lowerPoints = [];
+              List<Offset> upperPoints = [];
+              for (var i = 0; i < res['PointLower'].length; i++) {
+                final xyStr = res['PointLower'][i].split(',');
+                lowerPoints.add(Offset(
+                    (double.parse(xyStr[0]) + imageLeftTopPoint.x) * imageScale,
+                    (double.parse(xyStr[1]) + imageLeftTopPoint.y) *
+                        imageScale));
+              }
+              for (var i = 0; i < res['PointUpper'].length; i++) {
+                final xyStr = res['PointUpper'][i].split(',');
+                upperPoints.add(Offset(
+                    (double.parse(xyStr[0]) + imageLeftTopPoint.x) * imageScale,
+                    (double.parse(xyStr[1]) + imageLeftTopPoint.y) *
+                        imageScale));
+              }
+              ref.feature!.offsetsList.add(lowerPoints);
+              ref.feature!.offsetsList.add(upperPoints);
+            } else {
+              description = "Ant.CCA IMT\n Measure failed";
+            }
+            outputs.add(outputItem);
+            outputs.last.updateDescription(description: description);
+
+            /// [Carotid] ✅在此处通知canvas 重绘结果
+
+            ref.application.updateRenderReady.emit(this, null);
+          },
+        ],
+      );
+    } catch (e) {
+      outputs.add(outputItem);
+      outputs.last
+          .updateDescription(description: "Ant.CCA IMT\n Measure failed");
+      print(e);
+    }
+  }
+}

+ 106 - 0
lib/process/primitives/carotid_imt.dart

@@ -0,0 +1,106 @@
+import 'dart:ui';
+
+import 'package:fis_measure/interfaces/date_types/point.dart';
+import 'package:fis_measure/interfaces/enums/items.dart';
+import 'package:fis_measure/interfaces/process/items/item.dart';
+import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
+import 'package:fis_measure/process/calcuators/shell_calcuator.dart';
+import 'package:fis_measure/process/items/item.dart';
+import 'package:fis_measure/utils/canvas.dart';
+
+import '../items/item_feature.dart';
+
+class CarotidIMT extends MeasureItem<CarotidIMTFeature> {
+  CarotidIMT(ItemMeta meta, IMeasureItem? parent) : super(meta, parent);
+
+  static CarotidIMT createMeasureRect(ItemMeta meta, [IMeasureItem? parent]) {
+    CarotidIMT measureRect = CarotidIMT(meta, parent);
+    measureRect.calculator = ShellCal(measureRect);
+    return measureRect;
+  }
+
+  @override
+  bool onExecuteMouse(PointInfo args) {
+    if (state == ItemStates.finished) {
+      if (args.pointType == PointInfoType.mouseDown) {
+        state = ItemStates.waiting;
+      }
+    }
+
+    if (state == ItemStates.waiting) {
+      if (args.pointType == PointInfoType.mouseDown) {
+        handleMouseDownWhileWaiting(args);
+      }
+    } else if (state == ItemStates.running) {
+      if (args.pointType == PointInfoType.mouseUp) return false;
+
+      feature?.endPoint = args;
+      if (args.pointType == PointInfoType.mouseDown) {
+        doCalculate();
+
+        ///TODO 能否可以不加 delay,直接执行(此处是为了 cal 内 ref.feature!.lowerPoints = lowerPoints; 能够执行完成)
+        Future.delayed(const Duration(milliseconds: 500), () {
+          doFeatureFinish();
+        });
+      }
+    }
+    return true;
+  }
+
+  @override
+  bool onExecuteTouch(PointInfo args) {
+    // TODO: implement onExecuteTouch
+    throw UnimplementedError();
+  }
+
+  void handleMouseDownWhileWaiting(PointInfo args) {
+    // TODO: 判断是否当前area
+    // 转换为Area逻辑位置
+    final point = args.toAreaLogicPoint();
+    feature = CarotidIMTFeature(this, point, point);
+    if (args.hostVisualArea != null) {
+      feature!.hostVisualArea = args.hostVisualArea;
+    }
+    state = ItemStates.running;
+  }
+}
+
+class CarotidIMTFeature extends MeasureItemFeature {
+  CarotidIMTFeature(
+    IMeasureItem refItem,
+    DPoint startPoint,
+    DPoint endPoint,
+  ) : super(refItem) {
+    innerPoints.add(startPoint);
+    innerPoints.add(endPoint);
+  }
+
+  /// 起点
+  DPoint get startPoint => innerPoints[0];
+  set startPoint(DPoint value) => innerPoints[0] = value;
+
+  /// 终点
+  DPoint get endPoint => innerPoints[1];
+  set endPoint(DPoint value) => innerPoints[1] = value;
+
+  List<List<Offset>> offsetsList = [];
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    if (startPoint == endPoint) return;
+
+    var idText = '$id.${refItem.briefAnnotation}';
+    drawId(canvas, size, idText);
+
+    final startOffset = convert2ViewPoint(size, startPoint).toOffset();
+    drawVertex(canvas, startOffset);
+
+    final endOffset = convert2ViewPoint(size, endPoint).toOffset();
+    canvas.drawDashRect(startOffset, endOffset, 1, 10, paintPan);
+    drawVertex(canvas, endOffset, isActive);
+    //绘制点集连线
+    for (var offsets in offsetsList) {
+      canvas.drawPointsLine(offsets, paintPan);
+    }
+  }
+}

+ 25 - 0
lib/process/workspace/application.dart

@@ -15,6 +15,7 @@ import 'package:fis_measure/interfaces/process/workspace/recorder.dart';
 import 'package:fis_measure/process/annotations/arrow_annotation.dart';
 import 'package:fis_measure/process/annotations/input_annotation.dart';
 import 'package:fis_measure/process/annotations/label_annotation.dart';
+import 'package:fis_measure/process/primitives/carotid_imt.dart';
 import 'package:fis_measure/process/primitives/location.dart';
 import 'package:fis_measure/process/primitives/straightline.dart';
 import 'package:fis_measure/process/visual/tissue_area.dart';
@@ -40,6 +41,7 @@ class Application implements IApplication {
   bool _canOperate = false;
   Size _displaySize = Size.zero;
   bool _isAdaptiveCarotid2D = false;
+  Size _carotid2DSize = Size.zero;
   MeasureOperateType _currOpType = MeasureOperateType.measure;
   final Set<IMeasureItem> _measureItems = {};
   final Set<IAnnotationItem> _annotationItems = {};
@@ -147,6 +149,15 @@ class Application implements IApplication {
     }
   }
 
+  @override
+  Size get carotid2DSize => _carotid2DSize;
+  @override
+  set carotid2DSize(Size value) {
+    if (value != _carotid2DSize) {
+      _carotid2DSize = value;
+    }
+  }
+
   @override
   IMeasureItem? get activeMeasureItem => _activeMeasureItem;
   set activeMeasureItem(IMeasureItem? value) {
@@ -323,6 +334,20 @@ class Application implements IApplication {
       );
       return;
     }
+    if (name == MeasureTerms.AntCCA_IMT) {
+      print("进入AntCCA_IMT 模式");
+      activeMeasureItem = CarotidIMT.createMeasureRect(
+          ItemMeta(
+            MeasureTerms.AntCCA_IMT,
+            {
+              "Description": MeasureTerms.AntCCA_IMT,
+              "BriefDescription": "",
+              "Unit": VidUsUnit.cm,
+            },
+          ),
+          null);
+      return;
+    }
 
     activeMeasureItem = null;
   }

+ 48 - 0
lib/utils/canvas.dart

@@ -104,6 +104,54 @@ extension MeasureCanvasExt on Canvas {
     );
   }
 
+  /// 画虚线框
+  ///
+  /// [p1] 起始顶点
+  ///
+  /// [p2] 结束顶点
+  ///
+  /// [dashWidth] 虚线点长度
+  ///
+  /// [spaceWidth] 虚线点间隔长度
+  ///
+  /// [paint] 画笔
+  void drawDashRect(
+    Offset p1,
+    Offset p2,
+    double dashWidth,
+    double spaceWidth,
+    Paint paint,
+  ) {
+    final path = Path()
+      ..moveTo(p1.dx, p1.dy)
+      ..lineTo(p2.dx, p1.dy)
+      ..lineTo(p2.dx, p2.dy)
+      ..lineTo(p1.dx, p2.dy)
+      ..close();
+    drawPath(
+      dashPath(
+        path,
+        dashArray: CircularIntervalList<double>([dashWidth, spaceWidth]),
+      ),
+      paint,
+    );
+  }
+
+  /// 画点集连线
+  /// [points] 点集
+  /// [paint] 画笔
+  void drawPointsLine(List<Offset> points, Paint paint) {
+    final path = Path();
+    for (var i = 0; i < points.length; i++) {
+      if (i == 0) {
+        path.moveTo(points[i].dx, points[i].dy);
+      } else {
+        path.lineTo(points[i].dx, points[i].dy);
+      }
+    }
+    drawPath(path, paint);
+  }
+
   static final Paint _vertexPaint = Paint()
     ..strokeWidth = 1
     ..style = PaintingStyle.stroke

+ 3 - 1
lib/view/3d_view/carotid_player.dart

@@ -45,7 +45,9 @@ class _CarotidPlayerState extends State<CarotidPlayer> {
     // application.loadFrame(widget.carotidController.image4Measure!);
     application.loadFrame(VidUsImage(1, stdSize.width.toInt(),
         stdSize.height.toInt(), Uint8List.fromList([0])));
-
+    application.carotid2DSize = Size(
+        widget.carotidController.image4Measure!.width.toDouble(),
+        widget.carotidController.image4Measure!.height.toDouble());
     widget.playerController.eventHandler.addListener(onControllerEvent);
     loadFrame(widget.carotidController.image4Measure!);
     super.initState();