Browse Source

update(ai): 优化单帧播放器

gavin.chen 1 year ago
parent
commit
b8815db019

+ 13 - 12
lib/process/workspace/measure_handler.dart

@@ -7,13 +7,14 @@ import 'package:fis_measure/interfaces/enums/operate.dart';
 import 'package:fis_measure/view/measure/operate_type_change_button.dart';
 import 'package:fis_measure/view/measure/measure_panel_head.dart';
 
-class ChangeImageInfo {
+/// 当前选中的图像
+class CurrImageInfo {
   /// 图片地址
   final String? imageUrl;
 
   /// 图像编码
   final String? remedicalCode;
-  ChangeImageInfo(
+  CurrImageInfo(
     this.imageUrl,
     this.remedicalCode,
   );
@@ -57,7 +58,7 @@ class MeasureModeSubmit {
 
 abstract class IMeasureHandler {
   /// 测量切换图片事件
-  late FEventHandler<ChangeImageInfo> onImageChanged;
+  late FEventHandler<CurrImageInfo> onImageChanged;
 
   /// 测量方式选择
   late FEventHandler<MeasureModeSelection> onMeasureModeSelectionChanged;
@@ -95,8 +96,8 @@ abstract class IMeasureHandler {
   late final FEventHandler<bool> canMeasureDrawingChanged;
 
   /// 切换图像传递数据
-  ChangeImageInfo get imageChanged;
-  set imageChanged(ChangeImageInfo value);
+  CurrImageInfo get currSelectedImage;
+  set currSelectedImage(CurrImageInfo value);
 
   /// 切换图像测量方式变更数据
   MeasureModeSelection get measureModeChanged;
@@ -132,7 +133,7 @@ abstract class IMeasureHandler {
 }
 
 class MeasureHandler implements IMeasureHandler {
-  ChangeImageInfo _imageChanged = ChangeImageInfo(null, null);
+  CurrImageInfo _currSelectedImage = CurrImageInfo(null, null);
   MeasureModeSelection _measureModeChanged =
       MeasureModeSelection(null, null, null);
 
@@ -151,7 +152,7 @@ class MeasureHandler implements IMeasureHandler {
 
   AnnotationType _changedAnnotationType = AnnotationType.label;
   @override
-  var onImageChanged = FEventHandler<ChangeImageInfo>();
+  var onImageChanged = FEventHandler<CurrImageInfo>();
 
   @override
   var onMeasureModeSelectionChanged = FEventHandler<MeasureModeSelection>();
@@ -190,7 +191,7 @@ class MeasureHandler implements IMeasureHandler {
   FEventHandler<bool> canMeasureDrawingChanged = FEventHandler<bool>();
 
   @override
-  ChangeImageInfo get imageChanged => _imageChanged;
+  CurrImageInfo get currSelectedImage => _currSelectedImage;
 
   @override
   MeasureModeSelection get measureModeChanged => _measureModeChanged;
@@ -262,9 +263,9 @@ class MeasureHandler implements IMeasureHandler {
   }
 
   @override
-  set imageChanged(ChangeImageInfo value) {
-    if (value != _imageChanged) {
-      _imageChanged = value;
+  set currSelectedImage(CurrImageInfo value) {
+    if (value != _currSelectedImage) {
+      _currSelectedImage = value;
       _onImageChanged();
     }
   }
@@ -286,7 +287,7 @@ class MeasureHandler implements IMeasureHandler {
   }
 
   void _onImageChanged() {
-    onImageChanged.emit(this, imageChanged);
+    onImageChanged.emit(this, currSelectedImage);
   }
 
   void _onMeasureModeSelectionChanged() {

+ 50 - 40
lib/view/ai_result_modifier/controller.dart

@@ -9,6 +9,7 @@ import 'package:fis_measure/view/mobile_view/widgets/throttle.dart' as utils;
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:vid/us/vid_us_image.dart';
 
 class AiResultModifierController extends GetxController {
   final rpcHelper = Get.find<RPCHelper>();
@@ -17,10 +18,10 @@ class AiResultModifierController extends GetxController {
   // final languageService = Get.find<LanguageService>();
   final state = AiResultModifierState();
 
-  /// 传入的图像参数
-  // String patientCode = "";
-  String remedicalCode = "";
-  // String recordCode = "";
+  /// 传入参数 [图像code,图像帧下标,图像元数据]
+  final String remedicalCode;
+  final int currFrameIndex;
+  final VidUsImage currFrame;
 
   /// 初次查询到的完整数据
   AIDiagnosisPerImageDTO resultDTO = AIDiagnosisPerImageDTO();
@@ -81,7 +82,12 @@ class AiResultModifierController extends GetxController {
   /// 测量语言包
   final measureLanguage = MeasureLanguage();
 
-  AiResultModifierController();
+  AiResultModifierController(
+      {required this.remedicalCode,
+      required this.currFrameIndex,
+      required this.currFrame}) {
+    print('创建 AiResultModifierController');
+  }
 
   /// 多个ai病灶
   List<AIDetectedObject> get aiDetectedObjectList =>
@@ -131,7 +137,7 @@ class AiResultModifierController extends GetxController {
         GetRemedicalDiagnosisDataRequest(
           token: rpcHelper.userToken,
           remedicalCode: remedicalCode,
-          frameIndex: 0,
+          frameIndex: currFrameIndex,
         ),
       );
       resultDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
@@ -149,9 +155,10 @@ class AiResultModifierController extends GetxController {
         }
       }
       keyPoints = await _queryAllKeyPoints();
+      _canvasAffectedKeyPoints.clear();
       _updateCurrContoursPoints();
       _updateCurrKeyPoints();
-      _getDiagnosisEnumItemsAsync();
+      await _getDiagnosisEnumItemsAsync();
       update(['ai_result_canvas', 'ai_result_panel']);
     } catch (e) {
       print(e);
@@ -164,31 +171,12 @@ class AiResultModifierController extends GetxController {
     print("AiResultModifierController close");
   }
 
-  /// 图像尺寸加载完成的回调
-  void onFrameDataLoaded(Size _frameSize) {
-    print("图像尺寸 $_frameSize");
-    frameSize = _frameSize;
-    final RenderBox box =
-        framePlayerKey.currentContext!.findRenderObject() as RenderBox;
-    final framePlayerSize = Size(box.size.width, box.size.height);
-    print("容器尺寸 $framePlayerSize");
-    _scale = min(framePlayerSize.width / frameSize.width,
-        framePlayerSize.height / frameSize.height);
-    aiCanvasSize = Size(frameSize.width * _scale, frameSize.height * _scale);
-    print("缩放比 $_scale");
-
-    /// 更新交互层尺寸
-    update(["ai_result_modifier_interactive_layer"]);
-  }
-
   @override
   void onInit() {
     super.onInit();
     print("AiResultModifierController init");
-    // 获取传递的参数
-    final Map<String, String> args = Get.arguments;
-    remedicalCode = args["remedicalCode"] ?? "";
-    print(args);
+    _updateModifierInteractiveLayerSize();
+    loadAIResult();
   }
 
   /// 鼠标拖拽
@@ -247,6 +235,12 @@ class AiResultModifierController extends GetxController {
     }
   }
 
+  /// 鼠标离开区域
+  void onMouseExit(PointerExitEvent e) async {
+    print('onMouseExit');
+    _canvasAffectedKeyPoints.clear();
+    update(['ai_result_canvas']);
+  }
   /// 鼠标悬浮移动
   void onMouseHover(PointerHoverEvent e) async {
     if (keyPoints.isEmpty) return;
@@ -257,7 +251,6 @@ class AiResultModifierController extends GetxController {
         }, 'onMouseHover', 100);
         break;
       case AiResultModifierMode.pen:
-        print("画笔模式,遍历查找最近的点");
         utils.throttle(() {
           _onPenModeCallHoverFunction(e.localPosition);
         }, 'onMouseHover', 10);
@@ -294,6 +287,26 @@ class AiResultModifierController extends GetxController {
     }
   }
 
+  /// 更新交互层尺寸
+  void _updateModifierInteractiveLayerSize() {
+    frameSize = Size(currFrame.width.toDouble(), currFrame.height.toDouble());
+    print("图像尺寸 $frameSize");
+    // 在首帧渲染完成后获取容器尺寸
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      final RenderBox box =
+          framePlayerKey.currentContext!.findRenderObject() as RenderBox;
+      final framePlayerSize = Size(box.size.width, box.size.height);
+      print("容器尺寸 $framePlayerSize");
+      _scale = min(framePlayerSize.width / frameSize.width,
+          framePlayerSize.height / frameSize.height);
+      aiCanvasSize = Size(frameSize.width * _scale, frameSize.height * _scale);
+      print("缩放比 $_scale");
+
+      /// 更新交互层尺寸
+      update(["ai_result_modifier_interactive_layer"]);
+    });
+  }
+
   /// 自动吸附闭合判断
   void _autoCloseContours() async {
     if (_canvasNewContoursPoints.length < 6) return;
@@ -310,9 +323,7 @@ class AiResultModifierController extends GetxController {
         nearestKeyPointIndex = i;
       }
     }
-    print("最小距离 $minDistance");
     if (minDistance < 6) {
-      print("吸附成功");
       _canvasPenModeKeyPointIndexes.add(nearestKeyPointIndex);
       _canvasNewContoursPoints.add(canvasContoursPoints[nearestKeyPointIndex]);
       _isDrawingNewContours = false;
@@ -404,7 +415,6 @@ class AiResultModifierController extends GetxController {
       ),
     );
     _diagnosisEnumItems = getDiagnosisEnumItems.source ?? [];
-    update(['ai_result_panel']);
   }
 
   void _initData() {
@@ -413,7 +423,7 @@ class AiResultModifierController extends GetxController {
 
   /// 在拖拽模式下触发拖拽事件【每隔100ms触发一次】
   void _onDragModeCallDragFunction(Offset pos) async {
-    print("鼠标拖拽 $_dragStartPoint -> $pos");
+    // print("鼠标拖拽 $_dragStartPoint -> $pos");
     AIDiagnosisPoint2D startPoint = AIDiagnosisPoint2D(
         x: _dragStartPoint.dx ~/ _scale, y: _dragStartPoint.dy ~/ _scale);
     AIDiagnosisPoint2D endPoint =
@@ -432,7 +442,7 @@ class AiResultModifierController extends GetxController {
     final mousePos = AIDiagnosisPoint2D(
         x: localPosition.dx ~/ _scale, y: localPosition.dy ~/ _scale);
     affectedKeyPointIndexes = await _queryAffectedKeyPoints(mousePos);
-    print("影响到的关键点数量:${affectedKeyPointIndexes.length}");
+    // print("影响到的关键点数量:${affectedKeyPointIndexes.length}");
     _updateCurrAffectedKeyPoints();
     update(["ai_result_canvas"]);
   }
@@ -443,7 +453,7 @@ class AiResultModifierController extends GetxController {
     // 点间距【疏密程度】
     const double pointDistance = 8;
     final double distance = (pos - _canvasNewContoursPoints.last).distance;
-    print("当前点到上一个点的距离:$distance");
+    // print("当前点到上一个点的距离:$distance");
     if (distance >= pointDistance) {
       int numPointsToInsert = (distance / pointDistance).ceil() - 1; // 需要插入的点数
       for (int i = 0; i < numPointsToInsert; i++) {
@@ -459,7 +469,7 @@ class AiResultModifierController extends GetxController {
       _canvasNewContoursPoints.add(pos);
       update(["ai_result_canvas"]);
     }
-    print("当前轮廓点数量:${_canvasNewContoursPoints.length}");
+    // print("当前轮廓点数量:${_canvasNewContoursPoints.length}");
     _autoCloseContours();
   }
 
@@ -540,7 +550,7 @@ class AiResultModifierController extends GetxController {
       keyPoints = result.dstKeyPoints ?? [];
       contours = result.dstContours ?? [];
       affectedKeyPointIndexes = result.affectedKeyPointIndexes!;
-      print("拖拽结果:${keyPoints.length} ${contours.length}");
+      // print("拖拽结果:${keyPoints.length} ${contours.length}");
       return true;
     } catch (e) {
       print(e);
@@ -559,19 +569,19 @@ class AiResultModifierController extends GetxController {
             keyPoints[i].point!.y.toDouble() * _scale));
       }
     }
-    print("受影响的点数:${_canvasAffectedKeyPoints.length}");
+    // print("受影响的点数:${_canvasAffectedKeyPoints.length}");
   }
 
   /// [⭐ _canvasContoursPoints ] 更新当前轮廓点
   void _updateCurrContoursPoints() {
     _canvasContoursPoints = _convertPoints(contours);
-    print("轮廓点数:${_canvasContoursPoints.length}");
+    // print("轮廓点数:${_canvasContoursPoints.length}");
   }
 
   /// [⭐ _canvasKeyPoints ] 更新当前关键点
   void _updateCurrKeyPoints() async {
     _canvasKeyPoints = _convertKeyPoints(keyPoints);
-    print("关键点数:${_canvasKeyPoints.length}");
+    // print("关键点数:${_canvasKeyPoints.length}");
   }
 }
 

+ 19 - 32
lib/view/ai_result_modifier/view.dart

@@ -1,49 +1,37 @@
 import 'package:fis_measure/define.dart';
 import 'package:fis_measure/view/ai_result_modifier/widgets/ai_conclusion_result.dart';
+import 'package:fis_measure/view/ai_result_modifier/widgets/ai_one_frame_player.dart';
 import 'package:fis_ui/index.dart';
 import 'package:fis_ui/interface/interactive_container.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
-
-import 'index.dart';
-import 'widgets/ai_modifier_preview.dart';
+import 'package:vid/us/vid_us_image.dart';
 import 'widgets/ai_result_canvas.dart';
 
-// void _openMeasurePage(
-//     String patientCode, String remedicalCode, String recordCode) {
-//   var parasmeters = {
-//     "page": "measure",
-//     "patientCode": patientCode,
-//     "remedicalCode": remedicalCode,
-//     "recordCode": recordCode,
-//     "token": Store.user.token
-//   };
-
-//   /// FIXME: 临时劫持测量入口
-//   _tempOpenAiResultModifierDialog(parasmeters);
-//   // router.to(RouteNames.Remedical.Measure,
-//   //     id: NavIds.HOME, parameters: parasmeters);
-// }
-
-// void _tempOpenAiResultModifierDialog(Map<String, String> parasmeters) {
-//   Get.dialog(
-//     AiResultModifierDialog(),
-//     arguments: parasmeters,
-//   );
-// }
+import 'index.dart';
 
 class AiResultModifierDialog extends FisView<AiResultModifierController>
     implements FInteractiveContainer {
-  const AiResultModifierDialog({Key? key}) : super(key: key);
-
+  const AiResultModifierDialog(
+      {Key? key,
+      required this.remedicalCode,
+      required this.currFrameIndex,
+      required this.currFrame})
+      : super(key: key);
+  final String remedicalCode;
+  final int currFrameIndex;
+  final VidUsImage currFrame;
   @override
   String get pageName => "ai_result_modifier";
 
   @override
   FWidget build(BuildContext context) {
     return FisBuilder<AiResultModifierController>(
-      init: AiResultModifierController(),
+      init: AiResultModifierController(
+          remedicalCode: remedicalCode,
+          currFrameIndex: currFrameIndex,
+          currFrame: currFrame),
       id: "ai_result_modifier",
       builder: (_) {
         return FDialog(
@@ -145,10 +133,8 @@ class AiResultModifierDialog extends FisView<AiResultModifierController>
         child: FStack(
           key: controller.framePlayerKey,
           children: [
-            FAIModifierPreview(
-              vidUrl:
-                  "http://cdn-bj.fis.plus/3005AE58DFBE4B4B8062E8E2A1404BB9.vid",
-              onFrameSizeLoaded: controller.onFrameDataLoaded,
+            FOneFramePlayer(
+              oneFrame: currFrame,
             ),
             FisBuilder<AiResultModifierController>(
               id: 'ai_result_canvas',
@@ -175,6 +161,7 @@ class AiResultModifierDialog extends FisView<AiResultModifierController>
                 return FCenter(
                   child: FMouseRegion(
                     onHover: (event) => _.onMouseHover(event),
+                    onExit: (event) => _.onMouseExit(event),
                     child: FGestureDetector(
                         behavior: HitTestBehavior.opaque,
                         businessParent: this,

+ 0 - 102
lib/view/ai_result_modifier/widgets/ai_modifier_preview.dart

@@ -1,102 +0,0 @@
-import 'package:fis_i18n/i18n.dart';
-import 'package:fis_measure/define.dart';
-import 'package:fis_measure/index.dart';
-import 'package:fis_measure/utils/prompt_box.dart';
-import 'package:fis_measure/view/player/controller_old.dart';
-import 'package:fis_ui/index.dart';
-import 'package:fis_vid/data_host/data_host.dart';
-import 'package:flutter/material.dart';
-
-/// 用于浏览指定帧的简单播放器
-class FAIModifierPreview extends FStatefulWidget {
-  final String vidUrl;
-  final double? width;
-  final double? height;
-  final ValueCallback<Size>? onFrameSizeLoaded;
-
-  const FAIModifierPreview(
-      {super.key,
-      required this.vidUrl,
-      this.width,
-      this.height,
-      this.onFrameSizeLoaded});
-
-  @override
-  _FQuickPreviewState createState() => _FQuickPreviewState();
-}
-
-class _FQuickPreviewState extends FState<FAIModifierPreview> {
-  late final VidPlayerControllerNoSharing _playerController;
-  bool isShowLoading = true;
-
-  @override
-  FWidget build(BuildContext context) {
-    return FStack(children: <FWidget>[
-      // 使用 Offstage 组件来控制第二个组件的可见性
-      FCenter(
-        child: FRepaintBoundary(
-          child: QuickFWidget(
-            VidPlayer(_playerController),
-          ),
-        ),
-      ),
-      FCenter(
-        child: FOffstage(
-          child: FContainer(
-            padding: const EdgeInsets.all(10),
-            width: 50,
-            height: 50,
-            child: const FCircularProgressIndicator(),
-          ),
-          offstage: !isShowLoading,
-        ),
-      ),
-    ]);
-  }
-
-  @override
-  void dispose() {
-    _playerController.dispose();
-    super.dispose();
-  }
-
-  @override
-  void initState() {
-    final dataHost = VidDataHost(widget.vidUrl);
-    final playerController = VidPlayerControllerNoSharing(dataHost: dataHost);
-    _playerController = playerController;
-    loadVidDataHost(_playerController);
-    super.initState();
-  }
-
-  void loadVidDataHost(VidPlayerControllerNoSharing playerController) async {
-    int retryCount = 0;
-    while (retryCount < 3) {
-      try {
-        bool success = await playerController.load();
-        if (!success) {
-          throw "loadVidDataHost failed";
-        }
-        setState(() {
-          isShowLoading = false;
-        });
-        playerController.play();
-        await Future.delayed(const Duration(milliseconds: 100));
-        Size size = Size(playerController.currentFrame!.width.toDouble(),
-            playerController.currentFrame!.height.toDouble());
-        widget.onFrameSizeLoaded?.call(size);
-        return;
-      } catch (e) {
-        retryCount++;
-        if (retryCount == 3) {
-          PromptBox.toast(i18nBook.common.NetImageError.t);
-          setState(() {
-            isShowLoading = false;
-          });
-        } else if (!mounted) {
-          return; // 如果组件已经被销毁,则停止重试
-        }
-      }
-    }
-  }
-}

+ 23 - 0
lib/view/ai_result_modifier/widgets/ai_one_frame_player.dart

@@ -0,0 +1,23 @@
+import 'package:fis_ui/index.dart';
+import 'package:flutter/material.dart';
+import 'package:vid/us/vid_us_image.dart';
+
+/// 单帧播放器
+class FOneFramePlayer extends FStatelessWidget {
+  const FOneFramePlayer({Key? key, required this.oneFrame}) : super(key: key);
+  final VidUsImage oneFrame;
+
+  @override
+  FWidget build(BuildContext context) {
+    return FCenter(
+      child: FContainer(
+        width: double.infinity,
+        height: double.infinity,
+        child: FImage(
+          fit: BoxFit.contain,
+          image: MemoryImage(oneFrame.imageData),
+        ),
+      ),
+    );
+  }
+}

+ 2 - 2
lib/view/ai_result_modifier/widgets/ai_result_canvas.dart

@@ -155,9 +155,9 @@ class AIResultCanvas extends CustomPainter {
     final newContoursPaint = Paint()
       ..color = const Color.fromARGB(255, 32, 218, 103)
       ..strokeCap = StrokeCap.round
-      ..strokeWidth = 4.0
+      ..strokeWidth = 2.0
       ..style = PaintingStyle.stroke;
-    canvas.drawPoints(PointMode.points, newContoursPoints, newContoursPaint);
+    canvas.drawPoints(PointMode.polygon, newContoursPoints, newContoursPaint);
   }
 
   @override

+ 6 - 1
lib/view/measure/measure_images_bar.dart

@@ -232,7 +232,12 @@ class _ScrollableImageListState extends State<ScrollableImageList> {
     }
 
     measureHandler.changeImageLoaded = true;
-    measureHandler.imageChanged = ChangeImageInfo(
+    measureData.measureImageData = MeasureImageData(
+      patientCode: measureData.measureImageData.patientCode,
+      recordCode: measureData.measureImageData.recordCode,
+      remedicalCode: remedicalCode,
+    );
+    measureHandler.currSelectedImage = CurrImageInfo(
       imageUrl,
       remedicalCode,
     );

+ 14 - 5
lib/view/paint/parts/ai_result.dart

@@ -1,5 +1,8 @@
 import 'package:fis_i18n/i18n.dart';
+import 'package:fis_measure/index.dart';
+import 'package:fis_measure/interfaces/process/player/play_controller.dart';
 import 'package:fis_measure/process/workspace/measure_data_controller.dart';
+import 'package:fis_measure/process/workspace/measure_handler.dart';
 import 'package:fis_measure/view/ai_result_modifier/view.dart';
 import 'package:fis_measure/view/paint/ai_patint_controller.dart';
 import 'package:fis_measure/view/paint/date_structure.dart';
@@ -7,6 +10,7 @@ import 'package:fis_measure/view/paint/parts/ai_resul_info.dart';
 import 'package:fis_measure/view/paint/parts/feature_analysis.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:vid/us/vid_us_image.dart';
 
 class AIResultPanel extends StatefulWidget {
   final List<AIDetectedObject> aiDetectedObject;
@@ -25,10 +29,12 @@ class _AIResultPanelState extends State<AIResultPanel> {
   late AIDetectedObject aiDetectedObjectItem;
   final aiPatintController = Get.find<AiPatintController>();
   MeasureDataController get measureData => Get.find<MeasureDataController>();
+  VidPlayerController get playerController =>
+      Get.find<IPlayerController>() as VidPlayerController;
+  String get remedicalCode => measureData.measureImageData.remedicalCode ?? "";
+  int get currFrameIndex => playerController.currentFrameIndex;
+  VidUsImage get currFrame => playerController.currentFrame!;
 
-  Map<String, String> get parasmeters => {
-        "remedicalCode": measureData.measureImageData.remedicalCode ?? "",
-      };
   @override
   Widget build(BuildContext context) {
     return Row(
@@ -99,8 +105,11 @@ class _AIResultPanelState extends State<AIResultPanel> {
                           InkWell(
                             onTap: () {
                               Get.dialog(
-                                const AiResultModifierDialog(),
-                                arguments: parasmeters,
+                                AiResultModifierDialog(
+                                  remedicalCode: remedicalCode,
+                                  currFrameIndex: currFrameIndex,
+                                  currFrame: currFrame,
+                                ),
                               );
                             },
                             child: const Text(