123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- 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;
- // 当前AI病灶下标
- int currentAiDetectedObjectIndex = 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病灶
- void changeAiDetectedObjectIndex(int index) {
- currentAiDetectedObjectIndex = index;
- update(['ai_result_modifier', 'ai_result_panel']);
- }
- /// 获取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();
- await _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 ?? [];
- }
- 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,
- }
|