Browse Source

Merge branch 'master' of http://git.ius.plus:88/Project-Wing/fis_lib_measure

gavin.chen 1 year ago
parent
commit
c00fbaad12

+ 8 - 1
lib/interfaces/process/workspace/exam_info.dart

@@ -36,5 +36,12 @@ class ExamImageInfo {
   /// 预览图链接
   final String previewUrl;
 
-  ExamImageInfo(this.url, this.previewUrl);
+  /// ai图像编辑code
+  final String? remedicalAISelectedInfoCode;
+
+  ExamImageInfo(
+    this.url,
+    this.previewUrl, {
+    this.remedicalAISelectedInfoCode,
+  });
 }

+ 2 - 0
lib/process/workspace/measure_data_controller.dart

@@ -17,10 +17,12 @@ class MeasureImageData {
   final String? patientCode;
   final String? remedicalCode;
   final String? recordCode;
+  final String? remedicalAISelectedInfoCode;
   MeasureImageData({
     this.patientCode,
     this.recordCode,
     this.remedicalCode,
+    this.remedicalAISelectedInfoCode,
   });
 }
 

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

@@ -3,11 +3,13 @@ import 'dart:math';
 import 'dart:typed_data';
 import 'dart:ui' as ui;
 
+import 'package:fis_common/index.dart';
 import 'package:fis_common/logger/logger.dart';
 import 'package:fis_i18n/i18n.dart';
 import 'package:fis_jsonrpc/rpc.dart';
 import 'package:fis_measure/process/language/measure_language.dart';
 import 'package:fis_measure/process/visual/visual.dart';
+import 'package:fis_measure/process/workspace/measure_data_controller.dart';
 import 'package:fis_measure/process/workspace/rpc_helper.dart';
 import 'package:fis_measure/process/workspace/visual_loader.dart';
 import 'package:fis_measure/utils/prompt_box.dart';
@@ -25,12 +27,13 @@ import 'package:http/http.dart' as http;
 
 class AiResultModifierController extends GetxController {
   final rpcHelper = Get.find<RPCHelper>();
+  MeasureDataController get measureData => Get.find<MeasureDataController>();
 
   /// 后台语言包控制器
   // final languageService = Get.find<LanguageService>();
   final state = AiResultModifierState();
 
-  /// 传入参数 [图像code,图像帧下标,图像元数据]
+  /// 传入参数 [图像code,图像帧下标,图像元数据, 图像编辑过的code]
   final String remedicalCode;
   final int currFrameIndex;
   final VidUsImage currFrame;
@@ -112,6 +115,12 @@ class AiResultModifierController extends GetxController {
   List<AIDetectedObject> get aiDetectedObjectList =>
       modifiedDataDTO.diagResultsForEachOrgan?.first.detectedObjects ?? [];
 
+  /// 当前病灶
+  AIDetectedObject? get aiDetectedObject => modifiedDataDTO
+      .diagResultsForEachOrgan
+      ?.first
+      .detectedObjects?[currentAiDetectedObjectIndex];
+
   List<Offset> get aiPoints => _aiPoints;
 
   List<Offset> get canvasAffectedKeyPoints => _canvasAffectedKeyPoints;
@@ -140,7 +149,10 @@ class AiResultModifierController extends GetxController {
 
   /// 病灶横纵比
   String get lesionRatio =>
-      _verticalLengthInPixel / _horizontalLengthInPixel > 1 ? '> 1' : '< 1';
+      _verticalLengthInPixel / _horizontalLengthInPixel > 1 ||
+              _verticalLengthInPixel / _horizontalLengthInPixel == 1
+          ? '> 1'
+          : '< 1';
 
   /// 切换操作模式
   void changeModifierMode(AiResultModifierMode newMode) {
@@ -229,12 +241,12 @@ class AiResultModifierController extends GetxController {
           await orginalFileImage.toByteData(format: ui.ImageByteFormat.png);
       final orginalFileByteDataBuffer =
           orginalFileByteData!.buffer.asUint8List();
-      final String originFileUrl = await rpcHelper.rpc.storage.uploadUint8List(
+      final String aiFileToken = await rpcHelper.rpc.storage.uploadUint8List(
               orginalFileByteDataBuffer,
               "ai_modified_orginal_${remedicalCode}_$currFrameIndex.png",
               rpcHelper.userToken) ??
           '';
-      print('coverUrl: $originFileUrl');
+      print('coverUrl: $aiFileToken');
 
       /// 生成缩略图
       final double scale = _calcScale(
@@ -266,11 +278,10 @@ class AiResultModifierController extends GetxController {
           '';
       print('previewFileUrl: $previewFileUrl');
       return ImageUrls(
-          originFileUrl: originFileUrl, previewFileUrl: previewFileUrl);
+          aiFileToken: aiFileToken, previewFileUrl: previewFileUrl);
     } on Exception catch (e) {
       logger.e('get screenshot failed', e);
-      return ImageUrls(
-          originFileUrl: '', previewFileUrl: '', isUploaded: false);
+      return ImageUrls(aiFileToken: '', previewFileUrl: '', isUploaded: false);
     }
   }
 
@@ -402,8 +413,9 @@ class AiResultModifierController extends GetxController {
   }
 
   /// 重置AI结果
-  void resetAIResult() {
-    _initAIResult();
+  void resetAIResult() async {
+    await _initAIResult();
+    update(['ai_conclusion_result', 'ai_result_sleek_circular_slider']);
   }
 
   @override
@@ -413,8 +425,9 @@ class AiResultModifierController extends GetxController {
   }
 
   @override
-  void onInit() {
+  void onInit() async {
     super.onInit();
+    await _getDiagnosisEnumItemsAsync();
     _updateModifierInteractiveLayerSize();
     _updateImagePhysicalSize();
     _initAIResult();
@@ -530,21 +543,26 @@ class AiResultModifierController extends GetxController {
         PromptBox.toast(i18nBook.user.saveFailed.t);
         return;
       }
+      bool hasRemedicalAISelectedInfoCode = measureData
+          .measureImageData.remedicalAISelectedInfoCode.isNotNullOrEmpty;
       final result =
           await rpcHelper.rpc.remedical.saveRemedicalAISelectedInfoAsync(
         SaveRemedicalAISelectedInfoRequest(
           token: rpcHelper.userToken,
-          remedicalCode: remedicalCode,
-          code: code,
+          remedicalCode: hasRemedicalAISelectedInfoCode ? null : remedicalCode,
+          code: hasRemedicalAISelectedInfoCode
+              ? measureData.measureImageData.remedicalAISelectedInfoCode
+              : null,
           frameIndex: currFrameIndex,
+          // diagnosisConclusion: diagnosisOrgan,
           previewFileToken: imageUrls.previewFileUrl,
-          orginalFileToken: imageUrls.originFileUrl,
+          aIFileToken: imageUrls.aiFileToken,
           diagnosisData: jsonEncode(modifiedDataDTO),
         ),
       );
       if (result) {
         PromptBox.toast(
-            "${i18nBook.user.saveSuccess.t} ${i18nBook.measure.saveLocation.t + ' > ' + i18nBook.measure.aiImage.t}");
+            "${i18nBook.user.saveSuccess.t} \r\n ${i18nBook.measure.saveLocation.t + ' > ' + i18nBook.measure.aiImage.t}");
         Get.back();
       } else {
         PromptBox.toast(i18nBook.user.saveFailed.t);
@@ -557,15 +575,22 @@ class AiResultModifierController extends GetxController {
   /// 加载AI结果并调用绘制
   Future<void> _initAIResult() async {
     try {
-      final result =
-          await rpcHelper.rpc.remedical.getRemedicalDiagnosisDataAsync(
-        GetRemedicalDiagnosisDataRequest(
-          token: rpcHelper.userToken,
-          remedicalCode: remedicalCode,
-          frameIndex: currFrameIndex,
-        ),
-      );
-      resultDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
+      if (measureData
+          .measureImageData.remedicalAISelectedInfoCode.isNotNullOrEmpty) {
+        resultDTO = AIDiagnosisPerImageDTO.fromJson(
+            jsonDecode(measureData.aiResults)[0]);
+      } else {
+        final result =
+            await rpcHelper.rpc.remedical.getRemedicalDiagnosisDataAsync(
+          GetRemedicalDiagnosisDataRequest(
+            token: rpcHelper.userToken,
+            remedicalCode: remedicalCode,
+            frameIndex: currFrameIndex,
+          ),
+        );
+        resultDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
+      }
+
       modifiedDataDTO = resultDTO;
       contours = resultDTO.diagResultsForEachOrgan![0]
               .detectedObjects![currentAiDetectedObjectIndex].contours ??
@@ -585,7 +610,6 @@ class AiResultModifierController extends GetxController {
       _canvasAffectedKeyPoints.clear();
       _updateCurrContoursPoints();
       _updateCurrKeyPoints();
-      await _getDiagnosisEnumItemsAsync();
       update(['ai_result_canvas', 'ai_result_panel', 'ai_index_tag']);
     } catch (e) {
       logger.e('load ai result failed', e);
@@ -980,7 +1004,7 @@ extension StorageServiceExt on StorageService {
 
 class ImageUrls {
   /// 原始图像地址
-  String originFileUrl;
+  String aiFileToken;
 
   /// 缩略图地址
   String previewFileUrl;
@@ -989,7 +1013,7 @@ class ImageUrls {
   bool isUploaded = true;
 
   ImageUrls({
-    required this.originFileUrl,
+    required this.aiFileToken,
     required this.previewFileUrl,
     this.isUploaded = true,
   });

+ 13 - 9
lib/view/ai_result_modifier/view.dart

@@ -17,12 +17,12 @@ import 'index.dart';
 
 class AiResultModifierDialog extends FisView<AiResultModifierController>
     implements FInteractiveContainer {
-  const AiResultModifierDialog(
-      {Key? key,
-      required this.remedicalCode,
-      required this.currFrameIndex,
-      required this.currFrame})
-      : 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;
@@ -33,9 +33,10 @@ class AiResultModifierDialog extends FisView<AiResultModifierController>
   FWidget build(BuildContext context) {
     return FisBuilder<AiResultModifierController>(
       init: AiResultModifierController(
-          remedicalCode: remedicalCode,
-          currFrameIndex: currFrameIndex,
-          currFrame: currFrame),
+        remedicalCode: remedicalCode,
+        currFrameIndex: currFrameIndex,
+        currFrame: currFrame,
+      ),
       id: "ai_result_modifier",
       builder: (_) {
         return FDialog(
@@ -65,6 +66,9 @@ class AiResultModifierDialog extends FisView<AiResultModifierController>
   FWidget _buildAiResult() {
     return FContainer(
       width: 400,
+      margin: const EdgeInsets.only(
+        left: 15,
+      ),
       child: FColumn(
         children: [
           const AiIndexTag(),

+ 60 - 49
lib/view/ai_result_modifier/widgets/ai_diagnostic_result.dart

@@ -56,14 +56,15 @@ class AiDiagnosticResult extends FisView<AiResultModifierController> {
             ],
           ),
           FContainer(
-              decoration: BoxDecoration(
-                border: Border.all(
-                  color: const Color.fromRGBO(54, 169, 206, 1),
-                ),
-                color: Colors.transparent,
+            decoration: BoxDecoration(
+              border: Border.all(
+                color: const Color.fromRGBO(54, 169, 206, 1),
               ),
-              padding: const EdgeInsets.only(bottom: 5),
-              child: _buildPossibilityProgressBar()),
+              color: Colors.transparent,
+            ),
+            padding: const EdgeInsets.only(bottom: 5),
+            child: _buildPossibilityProgressBar(),
+          ),
         ],
       ),
     );
@@ -90,7 +91,7 @@ class AiDiagnosticResult extends FisView<AiResultModifierController> {
   }
 
   Color _buildAITextColor(int label) {
-    switch (DiagnosisOrganEnum.Breast) {
+    switch (diagnosisOrgan) {
       /// 乳腺是0:未见异常; 1、2、3良性; 4、5、6、7恶性;
       case DiagnosisOrganEnum.Breast:
         switch (label) {
@@ -192,6 +193,56 @@ class AiDiagnosticResult extends FisView<AiResultModifierController> {
     );
   }
 
+  FWidget _buildCircularSliderAppearance() {
+    return FisBuilder<AiResultModifierController>(
+      id: 'ai_result_sleek_circular_slider',
+      builder: (_) {
+        return QuickFWidget(
+          SleekCircularSlider(
+            key: UniqueKey(),
+            initialValue: (_.aiDetectedObject?.confidence ?? 0) * 100,
+            appearance: CircularSliderAppearance(
+              customWidths: CustomSliderWidths(
+                progressBarWidth: 5,
+                shadowWidth: 0,
+              ),
+              customColors: CustomSliderColors(
+                progressBarColors: [
+                  _buildAITextColor(
+                    aiDetectedObject.label,
+                  ),
+                  _buildAITextColor(
+                    aiDetectedObject.label,
+                  ),
+                ],
+                gradientStartAngle: 0,
+                gradientEndAngle: 0,
+                trackColor: _buildAITextColor(
+                  aiDetectedObject.label,
+                ),
+              ),
+              infoProperties: InfoProperties(
+                mainLabelStyle: const TextStyle(
+                  fontSize: 22,
+                  color: Colors.white,
+                  fontWeight: FontWeight.bold,
+                ),
+                modifier: (double value) {
+                  return "${value.toStringAsFixed(1)}%";
+                },
+              ),
+              angleRange: 360,
+              startAngle: 270,
+            ),
+            onChange: (double value) {
+              _.aiDetectedObject?.confidence = value / 100;
+            },
+          ),
+        );
+      },
+    );
+  }
+
   FWidget _buildPossibilityProgressBar() {
     return FRow(
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -251,47 +302,7 @@ class AiDiagnosticResult extends FisView<AiResultModifierController> {
               FSizedBox(
                 width: 100,
                 height: 100,
-                child: QuickFWidget(
-                  SleekCircularSlider(
-                    initialValue: aiDetectedObject.confidence * 100,
-                    appearance: CircularSliderAppearance(
-                      customWidths: CustomSliderWidths(
-                        progressBarWidth: 5,
-                        shadowWidth: 0,
-                      ),
-                      customColors: CustomSliderColors(
-                        progressBarColors: [
-                          _buildAITextColor(
-                            aiDetectedObject.label,
-                          ),
-                          _buildAITextColor(
-                            aiDetectedObject.label,
-                          ),
-                        ],
-                        gradientStartAngle: 0,
-                        gradientEndAngle: 0,
-                        trackColor: _buildAITextColor(
-                          aiDetectedObject.label,
-                        ),
-                      ),
-                      infoProperties: InfoProperties(
-                        mainLabelStyle: const TextStyle(
-                          fontSize: 22,
-                          color: Colors.white,
-                          fontWeight: FontWeight.bold,
-                        ),
-                        modifier: (double value) {
-                          return "${value.toStringAsFixed(1)}%";
-                        },
-                      ),
-                      angleRange: 360,
-                      startAngle: 270,
-                    ),
-                    onChange: (double value) {
-                      aiDetectedObject.confidence = value / 100;
-                    },
-                  ),
-                ),
+                child: _buildCircularSliderAppearance(),
               ),
             ],
           ),

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

@@ -230,12 +230,13 @@ class _ScrollableImageListState extends State<ScrollableImageList> {
     if (selectedIndex < 0) {
       return;
     }
-
+    // remedicalAISelectedInfoCode是因为选择ai编辑过的图片code和remedicalCode等级一样
     measureHandler.changeImageLoaded = true;
     measureData.measureImageData = MeasureImageData(
       patientCode: measureData.measureImageData.patientCode,
       recordCode: measureData.measureImageData.recordCode,
       remedicalCode: remedicalCode,
+      remedicalAISelectedInfoCode: remedicalCode,
     );
     measureHandler.currSelectedImage = CurrImageInfo(
       imageUrl,

+ 7 - 7
lib/view/measure/measure_view.dart

@@ -126,7 +126,6 @@ class _MeasureMainPageState extends State<MeasureMainPage> {
           .call(_curRemedicalCode, _curToken, _curRemedicalAISelectedInfoCode);
       if (remedicalInfo != null) {
         measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
-        print('🍧');
         if (remedicalInfo.terminalImages != null) {
           _hideFullScreenLoading = true;
           measureData.itemCurrentImage =
@@ -144,11 +143,11 @@ class _MeasureMainPageState extends State<MeasureMainPage> {
 
   /// 将当前的图像信息同步到 measureData 中
   void _setCurImageData() {
-    print('🍪');
     measureData.measureImageData = MeasureImageData(
       patientCode: _curPatientCode,
       recordCode: _curRecordCode,
       remedicalCode: _curRemedicalCode,
+      remedicalAISelectedInfoCode: _curRemedicalAISelectedInfoCode,
     );
   }
 
@@ -338,8 +337,6 @@ class _MeasureMainPageState extends State<MeasureMainPage> {
         measureData.itemCurrentImage = measureData.chooseImageUrl(imgInfo);
         getExamImageInfoList(remedicals);
       }
-
-      print('🥐');
     }
 
     measureController.imageLoaded.removeListener(_onImageLoaded);
@@ -352,7 +349,11 @@ class _MeasureMainPageState extends State<MeasureMainPage> {
     List<ExamImageInfo> examImageInfoList = remedicals.map((e) {
       final imgInfo = e.terminalImages!;
       final vidUrl = measureData.chooseImageUrl(imgInfo);
-      return ExamImageInfo(vidUrl, imgInfo.previewUrl!);
+      return ExamImageInfo(
+        vidUrl,
+        imgInfo.previewUrl!,
+        remedicalAISelectedInfoCode: e.remedicalCode,
+      );
     }).toList();
 
     Get.delete<MeasureController>();
@@ -391,9 +392,8 @@ class _MeasureMainPageState extends State<MeasureMainPage> {
       var remedicalInfo = await measureData.getImageInfo(
         currentImage.remedicalCode ?? '',
         _curToken,
-        _curRemedicalAISelectedInfoCode,
+        e!.remedicalAISelectedInfoCode,
       );
-      print('🍇');
       if (remedicalInfo != null) {
         measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
         measure3DViewController.initParams();

+ 13 - 13
lib/view/paint/ai_patint_controller.dart

@@ -29,7 +29,7 @@ class AiPatintController extends GetxController {
   late List<Offset> aiResultsList = [];
 
   /// 获取病灶大小
-  late String description;
+  late String lesionSize;
 
   /// ai结果点集
   late AiDotsResults aiDotsResults;
@@ -118,11 +118,11 @@ class AiPatintController extends GetxController {
     for (int m = 0; m < detectedObjects!.length; m++) {
       final List<AIDiagnosisPoint2D>? contours = detectedObjects[m].contours;
       if (contours!.isNotEmpty && detectedObjects[m].descriptions!.isNotEmpty) {
-        description = detectedObjects[m]
-            .descriptions![detectedObjects[m].descriptions!.length - 1]
-            .value!;
+        var lesionSizeDescription = detectedObjects[m].descriptions?.firstWhere(
+            (element) => element.type == DiagnosisDescriptionEnum.LesionSize);
+        lesionSize = lesionSizeDescription?.value ?? '';
 
-        final descriptions = jsonDecode(description);
+        final lesionSizeMap = jsonDecode(lesionSize);
         if (_HAS_VIEW_STATUS_ARR.contains(state.vidStatus)) {
           aiResultsList = [];
           for (int i = 0; i < contours.length; i++) {
@@ -130,14 +130,14 @@ class AiPatintController extends GetxController {
               contours[i].x * widthScale,
               contours[i].y * widthScale,
             ));
-            p1 = Offset(descriptions['HorizontalPoint1']['X'] * widthScale,
-                descriptions['HorizontalPoint1']['Y'] * widthScale);
-            p2 = Offset(descriptions['HorizontalPoint2']['X'] * widthScale,
-                descriptions['HorizontalPoint2']['Y'] * widthScale);
-            p3 = Offset(descriptions['VerticalPoint1']['X'] * widthScale,
-                descriptions['VerticalPoint1']['Y'] * widthScale);
-            p4 = Offset(descriptions['VerticalPoint2']['X'] * widthScale,
-                descriptions['VerticalPoint2']['Y'] * widthScale);
+            p1 = Offset(lesionSizeMap['HorizontalPoint1']['X'] * widthScale,
+                lesionSizeMap['HorizontalPoint1']['Y'] * widthScale);
+            p2 = Offset(lesionSizeMap['HorizontalPoint2']['X'] * widthScale,
+                lesionSizeMap['HorizontalPoint2']['Y'] * widthScale);
+            p3 = Offset(lesionSizeMap['VerticalPoint1']['X'] * widthScale,
+                lesionSizeMap['VerticalPoint1']['Y'] * widthScale);
+            p4 = Offset(lesionSizeMap['VerticalPoint2']['X'] * widthScale,
+                lesionSizeMap['VerticalPoint2']['Y'] * widthScale);
             aiDotsResults = AiDotsResults(
               aiResultsIndex: m,
               aiResultsList: aiResultsList,

+ 29 - 15
lib/view/paint/parts/ai_resul_info.dart

@@ -2,10 +2,14 @@ import 'dart:convert';
 
 import 'package:fis_i18n/i18n.dart';
 import 'package:fis_measure/interfaces/process/workspace/application.dart';
+import 'package:fis_measure/process/visual/visual.dart';
+import 'package:fis_measure/process/workspace/visual_loader.dart';
+import 'package:fis_measure/values/unit_desc.dart';
 import 'package:fis_measure/view/paint/ai_patint_controller.dart';
 import 'package:fis_measure/view/paint/date_structure.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:vid/us/vid_us_unit.dart';
 
 /// AI诊断结果
 class ResultInfo extends StatefulWidget {
@@ -20,14 +24,13 @@ class _ResultInfoState extends State<ResultInfo> {
   late final aiPatintController = Get.find<AiPatintController>();
 
   late AIDetectedObject aiDetectedObjectItem;
-  late double unitsPhysicalPixels;
+  late double _unitsPhysicalPixels;
   late final application = Get.find<IApplication>();
-
+  // 图像的物理单位
+  String _xUnit = '';
   @override
   void initState() {
-    unitsPhysicalPixels =
-        (application.visuals[0].visualAreas[0].viewport?.region.width)! /
-            (application.frameData!.width).toDouble();
+    _updateImagePhysicalSize();
     super.initState();
   }
 
@@ -35,10 +38,11 @@ class _ResultInfoState extends State<ResultInfo> {
   Widget build(BuildContext context) {
     final description = widget
         .aiDetectedObject[aiPatintController.state.aiResultIndex].descriptions;
-
-    late final descriptions = (description?.length ?? 0) > 1
-        ? jsonDecode(description?[description.length - 1].value ?? '')
-        : '';
+    var lesionSizeDescription = description?.firstWhereOrNull(
+        (element) => element.type == DiagnosisDescriptionEnum.LesionSize);
+    var lesionSize = lesionSizeDescription?.value ?? '';
+    late final lesionSizeMap =
+        (description?.length ?? 0) > 1 ? jsonDecode(lesionSize) : '';
     return Container(
       decoration: BoxDecoration(
         border: Border.all(
@@ -94,13 +98,13 @@ class _ResultInfoState extends State<ResultInfo> {
                         i18nBook.measure.diseaseLabels.t,
                         _buildAITitle(),
                       ),
-                      if (descriptions != '' && descriptions != null)
+                      if (lesionSizeMap != '' && lesionSizeMap != null)
                         _buildTitle(
                           i18nBook.measure.isLesionSize.t,
                           _buildLesionSize(
-                            descriptions?['HorizontalLengthInPixel'] ?? 0,
-                            descriptions?['VerticalLengthInPixel'] ?? 0,
-                            unitsPhysicalPixels,
+                            lesionSizeMap?['HorizontalLengthInPixel'] ?? 0,
+                            lesionSizeMap?['VerticalLengthInPixel'] ?? 0,
+                            _unitsPhysicalPixels,
                           ),
                         )
                     ],
@@ -191,15 +195,25 @@ class _ResultInfoState extends State<ResultInfo> {
       (horizontalLengthInPixel * unitsPhysicalPixels)
               .toStringAsFixed(2)
               .toString() +
-          'cm x' +
+          '$_xUnit x' +
           (verticalLengthInPixel * unitsPhysicalPixels)
               .toStringAsFixed(2)
               .toString() +
-          'cm',
+          _xUnit,
       style: const TextStyle(color: Colors.white),
     );
   }
 
+  /// 更新图像物理尺度信息
+  void _updateImagePhysicalSize() {
+    _unitsPhysicalPixels =
+        (application.visuals[0].visualAreas[0].viewport?.region.width)! /
+            (application.frameData!.width).toDouble();
+    VidUsUnit targetUnit =
+        application.visuals[0].visualAreas[0].viewport?.xUnit ?? VidUsUnit.cm;
+    _xUnit = UnitDescriptionMap.getDesc(targetUnit);
+  }
+
   Widget _buildTitle(String label, Widget value) {
     return Column(
       mainAxisSize: MainAxisSize.max,

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

@@ -18,8 +18,11 @@ class AIResultPanel extends StatefulWidget {
   /// ai部位
   final DiagnosisOrganEnum diagnosisOrgan;
 
-  const AIResultPanel(this.aiDetectedObject, this.diagnosisOrgan, {Key? key})
-      : super(key: key);
+  const AIResultPanel(
+    this.aiDetectedObject,
+    this.diagnosisOrgan, {
+    Key? key,
+  }) : super(key: key);
 
   @override
   State<AIResultPanel> createState() => _AIResultPanelState();

+ 4 - 3
lib/view/paint/parts/feature_analysis.dart

@@ -137,10 +137,11 @@ class FeatureAnalysis extends StatelessWidget {
   String _buildDescriptionValue(String diagnosisDescriptionValue) {
     if (diagnosisDescriptionValue.length > 50) {
       final diagnosisDescription = jsonDecode(diagnosisDescriptionValue);
-      bool horizontalAndVertical =
+      double horizontalAndVerticalProportion =
           diagnosisDescription?['VerticalLengthInPixel'] /
-                  diagnosisDescription?['HorizontalLengthInPixel'] >
-              1;
+              diagnosisDescription?['HorizontalLengthInPixel'];
+      bool horizontalAndVertical = horizontalAndVerticalProportion > 1 ||
+          horizontalAndVerticalProportion == 1;
       return horizontalAndVertical ? '> 1' : '< 1';
     } else {
       switch (diagnosisDescriptionValue) {

+ 1 - 1
pubspec.yaml

@@ -100,7 +100,7 @@ dependency_overrides:
   fis_jsonrpc:
     git:
       url: http://git.ius.plus:88/Project-Wing/fis_lib_jsonrpc.git
-      ref: e5d045a
+      ref: 9fe5e10
   fis_lib_business_components:
     git:
       url: http://git.ius.plus/Project-Wing/fis_lib_business_components.git