|
@@ -0,0 +1,584 @@
|
|
|
+import 'dart:convert';
|
|
|
+import 'dart:math';
|
|
|
+
|
|
|
+import 'package:fis_jsonrpc/rpc.dart';
|
|
|
+import 'package:fis_measure/process/language/measure_language.dart';
|
|
|
+import 'package:fis_measure/process/workspace/rpc_helper.dart';
|
|
|
+import 'package:fis_measure/view/ai_result_modifier/state.dart';
|
|
|
+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';
|
|
|
+
|
|
|
+class AiResultModifierController extends GetxController {
|
|
|
+ final rpcHelper = Get.find<RPCHelper>();
|
|
|
+
|
|
|
+ /// 后台语言包控制器
|
|
|
+ // final languageService = Get.find<LanguageService>();
|
|
|
+ final state = AiResultModifierState();
|
|
|
+
|
|
|
+ /// 传入的图像参数
|
|
|
+ // String patientCode = "";
|
|
|
+ String remedicalCode = "";
|
|
|
+ // String recordCode = "";
|
|
|
+
|
|
|
+ /// 初次查询到的完整数据
|
|
|
+ AIDiagnosisPerImageDTO resultDTO = AIDiagnosisPerImageDTO();
|
|
|
+
|
|
|
+ /// 编辑后的完整数据【用于发给后端】
|
|
|
+ AIDiagnosisPerImageDTO modifiedDataDTO = AIDiagnosisPerImageDTO();
|
|
|
+
|
|
|
+ // 用于画布绘制的轮廓点集
|
|
|
+ List<Offset> _canvasContoursPoints = [];
|
|
|
+ // 用于画布绘制的关键点集【拖拽模式】
|
|
|
+ List<Offset> _canvasKeyPoints = [];
|
|
|
+ // 用于画布绘制的高亮关键点集【拖拽模式】
|
|
|
+ final List<Offset> _canvasAffectedKeyPoints = [];
|
|
|
+ // 用于画布绘制的病灶大小横纵比例线段【四个坐标下标】
|
|
|
+ List<int> _canvasLesionSizePointsIndexes = [];
|
|
|
+ // 用于画布绘制的轮廓关键点下标集合【画轮廓模式】
|
|
|
+ final List<int> _canvasPenModeKeyPointIndexes = [];
|
|
|
+ // 用于画布绘制的轮廓关键点下标集合【画轮廓模式】
|
|
|
+ final List<Offset> _canvasNewContoursPoints = [];
|
|
|
+
|
|
|
+ // 播放器组件的key
|
|
|
+ final List<Offset> _aiPoints = [];
|
|
|
+ // 病灶结论列表
|
|
|
+ List<EnumItemDTO> _diagnosisEnumItems = [];
|
|
|
+ // 当前横线像素长度
|
|
|
+ final int _horizontalLengthInPixel = 0;
|
|
|
+ // 当前横线像素长度
|
|
|
+ final int _verticalLengthInPixel = 0;
|
|
|
+
|
|
|
+ GlobalKey framePlayerKey = GlobalKey();
|
|
|
+ // 画布组件的大小
|
|
|
+ Size aiCanvasSize = Size.zero;
|
|
|
+ // 图像的实际大小
|
|
|
+ Size frameSize = Size.zero;
|
|
|
+ // 图像的缩放比例
|
|
|
+ double _scale = 1.0;
|
|
|
+
|
|
|
+ // 当前的轮廓点集
|
|
|
+ List<AIDiagnosisPoint2D> contours = [];
|
|
|
+ // 当前的病灶大小
|
|
|
+ AIDiagnosisLesionSize? lesionSize;
|
|
|
+
|
|
|
+ // 当前的关键点集
|
|
|
+ List<DiagnosisKeyPointDTO> keyPoints = [];
|
|
|
+ // 当前受影响的高亮的关键点下标集合
|
|
|
+ List<int> affectedKeyPointIndexes = [];
|
|
|
+ // 当前操作模式
|
|
|
+ AiResultModifierMode _mode = AiResultModifierMode.drag;
|
|
|
+ // 当前是否正在绘制新轮廓
|
|
|
+ bool _isDrawingNewContours = false;
|
|
|
+ // 拖拽起点
|
|
|
+ Offset _dragStartPoint = Offset.zero;
|
|
|
+ // 拖拽开始时的轮廓点集【仅用于发请求】
|
|
|
+ List<AIDiagnosisPoint2D> contoursOnDragStart = [];
|
|
|
+ // 拖拽开始时的关键点集【仅用于发请求】
|
|
|
+ List<DiagnosisKeyPointDTO> keyPointsOnDragStart = [];
|
|
|
+
|
|
|
+ /// 测量语言包
|
|
|
+ final measureLanguage = MeasureLanguage();
|
|
|
+
|
|
|
+ AiResultModifierController();
|
|
|
+
|
|
|
+ /// 多个ai病灶
|
|
|
+ List<AIDetectedObject> get aiDetectedObjectList =>
|
|
|
+ modifiedDataDTO.diagResultsForEachOrgan?.first.detectedObjects ?? [];
|
|
|
+
|
|
|
+ List<Offset> get aiPoints => _aiPoints;
|
|
|
+
|
|
|
+ List<Offset> get canvasAffectedKeyPoints => _canvasAffectedKeyPoints;
|
|
|
+ List<Offset> get canvasContoursPoints => _canvasContoursPoints;
|
|
|
+ List<Offset> get canvasKeyPoints => _canvasKeyPoints;
|
|
|
+
|
|
|
+ List<int> get canvasLesionSizePointsIndexes => _canvasLesionSizePointsIndexes;
|
|
|
+ List<Offset> get canvasNewContoursPoints => _canvasNewContoursPoints;
|
|
|
+ List<int> get canvasPenModeKeyPointIndexes => _canvasPenModeKeyPointIndexes;
|
|
|
+ AiResultModifierMode get currMode => _mode;
|
|
|
+
|
|
|
+ List<EnumItemDTO> get diagnosisEnumItems => _diagnosisEnumItems;
|
|
|
+
|
|
|
+ /// 当前器官
|
|
|
+ DiagnosisOrganEnum get diagnosisOrgan =>
|
|
|
+ modifiedDataDTO.diagResultsForEachOrgan?.first.organ ??
|
|
|
+ DiagnosisOrganEnum.Null;
|
|
|
+ int get horizontalLengthInPixel => _horizontalLengthInPixel;
|
|
|
+ double get scale => _scale;
|
|
|
+
|
|
|
+ int get verticalLengthInPixel => _verticalLengthInPixel;
|
|
|
+
|
|
|
+ /// 切换操作模式
|
|
|
+ void changeModifierMode(AiResultModifierMode newMode) {
|
|
|
+ if (_mode == newMode) return;
|
|
|
+ _mode = newMode;
|
|
|
+ _canvasAffectedKeyPoints.clear();
|
|
|
+ update(['ai_result_modifier']);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 获取AI模块的翻译值
|
|
|
+ String getValuesFromAiLanguage(String code) {
|
|
|
+ final value = measureLanguage.t('ai', code);
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 加载AI结果并调用绘制
|
|
|
+ Future<void> loadAIResult() async {
|
|
|
+ try {
|
|
|
+ final result =
|
|
|
+ await rpcHelper.rpc.remedical.getRemedicalDiagnosisDataAsync(
|
|
|
+ GetRemedicalDiagnosisDataRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ remedicalCode: remedicalCode,
|
|
|
+ frameIndex: 0,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ resultDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
|
|
|
+ modifiedDataDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
|
|
|
+ contours =
|
|
|
+ resultDTO.diagResultsForEachOrgan![0].detectedObjects![0].contours ??
|
|
|
+ [];
|
|
|
+ List<AIDiagnosisDescription>? descriptions = resultDTO
|
|
|
+ .diagResultsForEachOrgan![0].detectedObjects![0].descriptions;
|
|
|
+ //遍历 descriptions 取出病灶大小
|
|
|
+ for (AIDiagnosisDescription description in descriptions!) {
|
|
|
+ if (description.type == DiagnosisDescriptionEnum.LesionSize) {
|
|
|
+ lesionSize = AIDiagnosisLesionSize.fromJson(
|
|
|
+ jsonDecode(description.value ?? ""));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ keyPoints = await _queryAllKeyPoints();
|
|
|
+ _updateCurrContoursPoints();
|
|
|
+ _updateCurrKeyPoints();
|
|
|
+ _getDiagnosisEnumItemsAsync();
|
|
|
+ update(['ai_result_canvas', 'ai_result_panel']);
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void onClose() {
|
|
|
+ super.onClose();
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 鼠标拖拽
|
|
|
+ void onMouseDrag(DragUpdateDetails details) {
|
|
|
+ switch (_mode) {
|
|
|
+ case AiResultModifierMode.drag:
|
|
|
+ utils.throttle(() {
|
|
|
+ _onDragModeCallDragFunction(details.localPosition);
|
|
|
+ }, 'onMouseDrag', 100);
|
|
|
+ break;
|
|
|
+ case AiResultModifierMode.pen:
|
|
|
+ _onPenModeCallDragFunction(details.localPosition);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 鼠标拖拽结束
|
|
|
+ void onMouseDragEnd(DragEndDetails details) async {
|
|
|
+ switch (_mode) {
|
|
|
+ case AiResultModifierMode.drag:
|
|
|
+ break;
|
|
|
+ case AiResultModifierMode.pen:
|
|
|
+ if (_isDrawingNewContours) {
|
|
|
+ _isDrawingNewContours = false;
|
|
|
+ await _callContourMergeAsync();
|
|
|
+ _updateCurrContoursPoints();
|
|
|
+ _updateCurrKeyPoints();
|
|
|
+ }
|
|
|
+ _canvasNewContoursPoints.clear();
|
|
|
+ update(['ai_result_canvas']);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 鼠标拖拽开始【记录起点】
|
|
|
+ void onMouseDragStart(DragDownDetails details) {
|
|
|
+ switch (_mode) {
|
|
|
+ case AiResultModifierMode.drag:
|
|
|
+ _dragStartPoint = details.localPosition;
|
|
|
+ contoursOnDragStart = contours;
|
|
|
+ keyPointsOnDragStart = keyPoints;
|
|
|
+ break;
|
|
|
+ case AiResultModifierMode.pen:
|
|
|
+ if (_canvasPenModeKeyPointIndexes.isNotEmpty) {
|
|
|
+ _isDrawingNewContours = true;
|
|
|
+ _dragStartPoint = details.localPosition;
|
|
|
+ _canvasNewContoursPoints.clear();
|
|
|
+ _canvasNewContoursPoints
|
|
|
+ .add(_canvasContoursPoints[_canvasPenModeKeyPointIndexes[0]]);
|
|
|
+ _canvasNewContoursPoints.add(_dragStartPoint);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 鼠标悬浮移动
|
|
|
+ void onMouseHover(PointerHoverEvent e) async {
|
|
|
+ if (keyPoints.isEmpty) return;
|
|
|
+ switch (_mode) {
|
|
|
+ case AiResultModifierMode.drag:
|
|
|
+ utils.throttle(() {
|
|
|
+ _onDragModeCallHoverFunction(e.localPosition);
|
|
|
+ }, 'onMouseHover', 100);
|
|
|
+ break;
|
|
|
+ case AiResultModifierMode.pen:
|
|
|
+ print("画笔模式,遍历查找最近的点");
|
|
|
+ utils.throttle(() {
|
|
|
+ _onPenModeCallHoverFunction(e.localPosition);
|
|
|
+ }, 'onMouseHover', 10);
|
|
|
+ // Offset point = e.localPosition;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void onReady() {
|
|
|
+ super.onReady();
|
|
|
+ _initData();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 保存AI修改结果
|
|
|
+ Future<void> saveAIResult({
|
|
|
+ String? code,
|
|
|
+ int frameIndex = 0,
|
|
|
+ }) async {
|
|
|
+ try {
|
|
|
+ final result =
|
|
|
+ await rpcHelper.rpc.remedical.saveRemedicalAISelectedInfoAsync(
|
|
|
+ SaveRemedicalAISelectedInfoRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ remedicalCode: remedicalCode,
|
|
|
+ code: code,
|
|
|
+ frameIndex: frameIndex,
|
|
|
+ diagnosisData: jsonEncode(resultDTO),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 自动吸附闭合判断
|
|
|
+ void _autoCloseContours() async {
|
|
|
+ if (_canvasNewContoursPoints.length < 6) return;
|
|
|
+ double minDistance = double.infinity;
|
|
|
+ int nearestKeyPointIndex = -1;
|
|
|
+ final lastPoint = _canvasNewContoursPoints.last;
|
|
|
+
|
|
|
+ /// 遍历所有关键点keyPoints,找到离localPosition最近的关键点
|
|
|
+ for (int i = 0; i < canvasContoursPoints.length; i++) {
|
|
|
+ final point = canvasContoursPoints[i];
|
|
|
+ final double distance = (point - lastPoint).distance;
|
|
|
+ if (distance < minDistance) {
|
|
|
+ minDistance = distance;
|
|
|
+ nearestKeyPointIndex = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ print("最小距离 $minDistance");
|
|
|
+ if (minDistance < 6) {
|
|
|
+ print("吸附成功");
|
|
|
+ _canvasPenModeKeyPointIndexes.add(nearestKeyPointIndex);
|
|
|
+ _canvasNewContoursPoints.add(canvasContoursPoints[nearestKeyPointIndex]);
|
|
|
+ _isDrawingNewContours = false;
|
|
|
+ await _callContourMergeAsync();
|
|
|
+ _updateCurrContoursPoints();
|
|
|
+ _updateCurrKeyPoints();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送请求通知后端合并轮廓
|
|
|
+ Future<bool> _callContourMergeAsync() async {
|
|
|
+ final ContourMergeResult result =
|
|
|
+ await rpcHelper.rpc.aIDiagnosis.contourMergeAsync(
|
|
|
+ ContourMergeRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ contourPoints: contours,
|
|
|
+ lesionSize: lesionSize,
|
|
|
+ drawingNewContourPoints: _convertCanvasPoints(_canvasNewContoursPoints),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ //TODO:此处可以拿到合并后的纵横比数据 to Baka
|
|
|
+ print(result);
|
|
|
+ contours = result.dstContours ?? [];
|
|
|
+ lesionSize = result.dstLesionSize;
|
|
|
+ keyPoints = await _queryAllKeyPoints();
|
|
|
+ return true;
|
|
|
+ // if (result.success) {
|
|
|
+ // // _initData();
|
|
|
+ // }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 画布坐标系转换【画布坐标系 -> 接口坐标系】
|
|
|
+ List<AIDiagnosisPoint2D> _convertCanvasPoints(List<Offset> points) {
|
|
|
+ List<AIDiagnosisPoint2D> result = [];
|
|
|
+ for (Offset point in points) {
|
|
|
+ result.add(
|
|
|
+ AIDiagnosisPoint2D(x: point.dx ~/ _scale, y: point.dy ~/ _scale));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 关键点坐标转换【接口坐标系 -> 画布坐标系】同时更新横纵比例线段下标
|
|
|
+ List<Offset> _convertKeyPoints(List<DiagnosisKeyPointDTO> points) {
|
|
|
+ List<Offset> result = [];
|
|
|
+ List<int> pointIndexes = List.generate(4, (_) => 0);
|
|
|
+ for (int i = 0; i < points.length; i++) {
|
|
|
+ final point = points[i];
|
|
|
+ if (point.point == null) continue;
|
|
|
+ result.add(Offset(point.point!.x.toDouble() * _scale,
|
|
|
+ point.point!.y.toDouble() * _scale));
|
|
|
+ if (point.type != DiagnosisKeyPointType.OtherKeyPoints) {
|
|
|
+ switch (point.type) {
|
|
|
+ case DiagnosisKeyPointType.HorizontalPointLeft:
|
|
|
+ pointIndexes[0] = i;
|
|
|
+ break;
|
|
|
+ case DiagnosisKeyPointType.HorizontalPointRight:
|
|
|
+ pointIndexes[1] = i;
|
|
|
+ break;
|
|
|
+ case DiagnosisKeyPointType.VerticalPointUp:
|
|
|
+ pointIndexes[2] = i;
|
|
|
+ break;
|
|
|
+ case DiagnosisKeyPointType.VerticalPointDown:
|
|
|
+ pointIndexes[3] = i;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _canvasLesionSizePointsIndexes = pointIndexes;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 坐标转换【接口坐标系 -> 画布坐标系】
|
|
|
+ List<Offset> _convertPoints(List<AIDiagnosisPoint2D> points) {
|
|
|
+ List<Offset> result = [];
|
|
|
+ for (AIDiagnosisPoint2D point in points) {
|
|
|
+ result.add(
|
|
|
+ Offset(point.x.toDouble() * _scale, point.y.toDouble() * _scale));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 获取ai结果相关枚举集合
|
|
|
+ Future<void> _getDiagnosisEnumItemsAsync() async {
|
|
|
+ final getDiagnosisEnumItems =
|
|
|
+ await rpcHelper.rpc.aIDiagnosis.getDiagnosisEnumItemsAsync(
|
|
|
+ GetDiagnosisEnumItemsRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ _diagnosisEnumItems = getDiagnosisEnumItems.source ?? [];
|
|
|
+ update(['ai_result_panel']);
|
|
|
+ }
|
|
|
+
|
|
|
+ void _initData() {
|
|
|
+ update(["ai_result_modifier"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 在拖拽模式下触发拖拽事件【每隔100ms触发一次】
|
|
|
+ void _onDragModeCallDragFunction(Offset pos) async {
|
|
|
+ print("鼠标拖拽 $_dragStartPoint -> $pos");
|
|
|
+ AIDiagnosisPoint2D startPoint = AIDiagnosisPoint2D(
|
|
|
+ x: _dragStartPoint.dx ~/ _scale, y: _dragStartPoint.dy ~/ _scale);
|
|
|
+ AIDiagnosisPoint2D endPoint =
|
|
|
+ AIDiagnosisPoint2D(x: pos.dx ~/ _scale, y: pos.dy ~/ _scale);
|
|
|
+ final bool success = await _queryDragResult(startPoint, endPoint);
|
|
|
+ if (success) {
|
|
|
+ _updateCurrKeyPoints();
|
|
|
+ _updateCurrContoursPoints();
|
|
|
+ _updateCurrAffectedKeyPoints();
|
|
|
+ update(["ai_result_canvas"]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 在拖拽模式下,通过鼠标位置更新高亮的关键点下标【每隔100ms触发一次】
|
|
|
+ void _onDragModeCallHoverFunction(Offset localPosition) async {
|
|
|
+ final mousePos = AIDiagnosisPoint2D(
|
|
|
+ x: localPosition.dx ~/ _scale, y: localPosition.dy ~/ _scale);
|
|
|
+ affectedKeyPointIndexes = await _queryAffectedKeyPoints(mousePos);
|
|
|
+ print("影响到的关键点数量:${affectedKeyPointIndexes.length}");
|
|
|
+ _updateCurrAffectedKeyPoints();
|
|
|
+ update(["ai_result_canvas"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 在画轮廓模式下触发拖拽事件
|
|
|
+ void _onPenModeCallDragFunction(Offset pos) async {
|
|
|
+ if (!_isDrawingNewContours) return;
|
|
|
+ // 点间距【疏密程度】
|
|
|
+ const double pointDistance = 8;
|
|
|
+ final double distance = (pos - _canvasNewContoursPoints.last).distance;
|
|
|
+ print("当前点到上一个点的距离:$distance");
|
|
|
+ if (distance >= pointDistance) {
|
|
|
+ int numPointsToInsert = (distance / pointDistance).ceil() - 1; // 需要插入的点数
|
|
|
+ for (int i = 0; i < numPointsToInsert; i++) {
|
|
|
+ double t = (i + 1) / (numPointsToInsert + 1);
|
|
|
+ Offset interpolatedPoint = Offset(
|
|
|
+ _canvasNewContoursPoints.last.dx +
|
|
|
+ t * (pos.dx - _canvasNewContoursPoints.last.dx),
|
|
|
+ _canvasNewContoursPoints.last.dy +
|
|
|
+ t * (pos.dy - _canvasNewContoursPoints.last.dy),
|
|
|
+ );
|
|
|
+ _canvasNewContoursPoints.add(interpolatedPoint);
|
|
|
+ }
|
|
|
+ _canvasNewContoursPoints.add(pos);
|
|
|
+ update(["ai_result_canvas"]);
|
|
|
+ }
|
|
|
+ print("当前轮廓点数量:${_canvasNewContoursPoints.length}");
|
|
|
+ _autoCloseContours();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 在画轮廓模式下,通过鼠标位置更新最近的关键点【每隔10ms触发一次】
|
|
|
+ void _onPenModeCallHoverFunction(Offset localPosition) async {
|
|
|
+ double minDistance = double.infinity;
|
|
|
+ // Offset nearestKeyPoint = Offset.zero;
|
|
|
+ int nearestKeyPointIndex = -1;
|
|
|
+
|
|
|
+ /// 遍历所有关键点keyPoints,找到离localPosition最近的关键点
|
|
|
+ for (int i = 0; i < canvasContoursPoints.length; i++) {
|
|
|
+ final point = canvasContoursPoints[i];
|
|
|
+ final double distance = (point - localPosition).distance;
|
|
|
+ if (distance < minDistance) {
|
|
|
+ minDistance = distance;
|
|
|
+ // nearestKeyPoint = point;
|
|
|
+ nearestKeyPointIndex = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _canvasPenModeKeyPointIndexes.clear();
|
|
|
+ if (minDistance < 10) {
|
|
|
+ _canvasPenModeKeyPointIndexes.add(nearestKeyPointIndex);
|
|
|
+ }
|
|
|
+ update(["ai_result_canvas"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 根据鼠标位置查询受影响的关键点
|
|
|
+ Future<List<int>> _queryAffectedKeyPoints(AIDiagnosisPoint2D mousePos) async {
|
|
|
+ try {
|
|
|
+ final List<int> result =
|
|
|
+ await rpcHelper.rpc.aIDiagnosis.affectedKeyPointsByDragActionAsync(
|
|
|
+ AffectedKeyPointsByDragActionRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ keyPoints: keyPoints,
|
|
|
+ mousePoint: mousePos,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ // print(result);
|
|
|
+ return result;
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 查询所有关键点【需要先存好contours和lesionSize】
|
|
|
+ Future<List<DiagnosisKeyPointDTO>> _queryAllKeyPoints() async {
|
|
|
+ try {
|
|
|
+ final List<DiagnosisKeyPointDTO> result =
|
|
|
+ await rpcHelper.rpc.aIDiagnosis.getKeyPointsOfContourAsync(
|
|
|
+ GetKeyPointsOfContourRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ contours: contours,
|
|
|
+ lesionSize: lesionSize,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ return result;
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 查询拖拽结果集合【需要先存好 contoursOnDragStart 和 keyPointsOnDragStart】
|
|
|
+ Future<bool> _queryDragResult(
|
|
|
+ AIDiagnosisPoint2D startPoint, AIDiagnosisPoint2D endPoint) async {
|
|
|
+ try {
|
|
|
+ final ContourAndKeyPointsAfterDragResult result =
|
|
|
+ await rpcHelper.rpc.aIDiagnosis.contourAndKeyPointsAfterDragAsync(
|
|
|
+ ContourAndKeyPointsAfterDragRequest(
|
|
|
+ token: rpcHelper.userToken,
|
|
|
+ contours: contoursOnDragStart,
|
|
|
+ keyPoints: keyPointsOnDragStart,
|
|
|
+ startPoint: startPoint,
|
|
|
+ endPoint: endPoint,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ keyPoints = result.dstKeyPoints ?? [];
|
|
|
+ contours = result.dstContours ?? [];
|
|
|
+ affectedKeyPointIndexes = result.affectedKeyPointIndexes!;
|
|
|
+ print("拖拽结果:${keyPoints.length} ${contours.length}");
|
|
|
+ return true;
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// [⭐ _canvasAffectedKeyPoints ] 根据当前的受影响关键点下标更新受影响关键点集
|
|
|
+ void _updateCurrAffectedKeyPoints() {
|
|
|
+ _canvasAffectedKeyPoints.clear();
|
|
|
+ if (keyPoints.isEmpty) return;
|
|
|
+ for (int i = 0; i < keyPoints.length; i++) {
|
|
|
+ if (affectedKeyPointIndexes.contains(i)) {
|
|
|
+ _canvasAffectedKeyPoints.add(Offset(
|
|
|
+ keyPoints[i].point!.x.toDouble() * _scale,
|
|
|
+ keyPoints[i].point!.y.toDouble() * _scale));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ print("受影响的点数:${_canvasAffectedKeyPoints.length}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// [⭐ _canvasContoursPoints ] 更新当前轮廓点
|
|
|
+ void _updateCurrContoursPoints() {
|
|
|
+ _canvasContoursPoints = _convertPoints(contours);
|
|
|
+ print("轮廓点数:${_canvasContoursPoints.length}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// [⭐ _canvasKeyPoints ] 更新当前关键点
|
|
|
+ void _updateCurrKeyPoints() async {
|
|
|
+ _canvasKeyPoints = _convertKeyPoints(keyPoints);
|
|
|
+ print("关键点数:${_canvasKeyPoints.length}");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+enum AiResultModifierMode {
|
|
|
+ /// 拖拽
|
|
|
+ drag,
|
|
|
+
|
|
|
+ /// 画笔
|
|
|
+ pen,
|
|
|
+}
|