Explorar o código

fix 0018968: 【图像测量】【小动物】PW(CW)模式下,测量项PISA MR(二尖瓣反流)测量时,1.计算项缺少Als.vel 2. Vmax参数项缺失 3.Reg Volume结果反差大

Melon hai 9 meses
pai
achega
8a74c78feb

+ 18 - 0
lib/interfaces/process/items/item_metas.dart

@@ -84,4 +84,22 @@ class ItemOutputMeta {
     this.additionalAnnotation,
     this.fractionalDigits = 2,
   });
+
+  ItemOutputMeta copyWith({
+    String? name,
+    String? description,
+    VidUsUnit? unit,
+    String? briefAnnotation,
+    String? additionalAnnotation,
+    int? fractionalDigits,
+  }) {
+    final meta = ItemOutputMeta(
+      name ?? this.name,
+      description ?? this.description,
+      unit ?? this.unit,
+      additionalAnnotation: additionalAnnotation ?? this.additionalAnnotation,
+      fractionalDigits: fractionalDigits ?? this.fractionalDigits,
+    );
+    return meta;
+  }
 }

+ 5 - 3
lib/process/calcuators/pisa.dart

@@ -54,14 +54,15 @@ class PisaCal extends Calculator<Pisa, double> {
         if (output.name == MeasureTerms.Placeholder) {
           feature.updateStringValue(output, "");
         } else if (output.name == MeasureTerms.VTI) {
-          feature.updateFloatValue(output, vti, VidUsUnit.cm);
+          feature.updateFloatValue(
+              output, vti.abs(), VidUsUnit.cm); // 特殊处理,显示绝对值
         } else if (output.name == MeasureTerms.VelocityMax) {
           feature.updateFloatValue(output, mrVmax, VidUsUnit.cms);
         }
       }
     }
 
-    double mrAlsVel = ref.alsVel;
+    double mrAlsVel = ref.alsVel.value!; // TODO: with unit
 
     double mrRV = double.nan;
     double mrEROA = double.nan;
@@ -76,7 +77,8 @@ class PisaCal extends Calculator<Pisa, double> {
     for (var output in ref.meta.outputs) {
       if (output.name == rvKey) {
         if (!mrRV.isNaN) {
-          feature.updateFloatValue(output, mrVmax, VidUsUnit.ml);
+          feature.updateFloatValue(
+              output, mrRV.abs(), VidUsUnit.ml); // 特殊处理,显示绝对值
         }
       } else if (output.name == eroaKey) {
         if (!mrEROA.isNaN) {

+ 6 - 1
lib/process/calcuators/three_distance.dart

@@ -86,6 +86,8 @@ class PercentMAMCalculator extends Calculator<LWHStraightLine, double> {
   void calculate() {
     if (ref.feature == null) return;
 
+    final feature = ref.feature!;
+
     final l = findChildFeature(ref.l);
     final w = findChildFeature(ref.w);
     final h = findChildFeature(ref.h);
@@ -102,7 +104,10 @@ class PercentMAMCalculator extends Calculator<LWHStraightLine, double> {
 
     if (mapse != null && lvidd != null && lvids != null) {
       double mam = CardiacFormulas.mamPercent(mapse, lvidd, lvids);
-      updateFloatValue(mam, unit: VidUsUnit.percent);
+      // updateFloatValue(mam, unit: VidUsUnit.percent);
+      final outputMeta =
+          ref.meta.outputs.first.copyWith(unit: VidUsUnit.percent);
+      feature.updateFloatValue(outputMeta, mam, VidUsUnit.percent);
     }
   }
 }

+ 17 - 1
lib/process/primitives/combos/pisa.dart

@@ -1,8 +1,11 @@
+import 'package:fis_measure/interfaces/process/calculators/values.dart';
 import 'package:fis_measure/interfaces/process/items/item.dart';
 import 'package:fis_measure/interfaces/process/items/item_metas.dart';
+import 'package:fis_measure/interfaces/process/items/terms.dart';
 import 'package:fis_measure/process/calcuators/pisa.dart';
 import 'package:fis_measure/process/items/top_item.dart';
 import 'package:fis_measure/process/items/top_item_feature.dart';
+import 'package:vid/us/vid_us_unit.dart';
 
 import '../multi_method/multiple_trace.dart';
 import '../straightline.dart';
@@ -10,7 +13,7 @@ import '../straightline.dart';
 class Pisa extends TopMeasureItem<PisaFeature> {
   late final StraightLine radius;
   late final MultiTrace trace;
-  double alsVel = 65.9; // TODO: 可手动设置 cm/s
+  late final FloatValue _alsVel;
 
   Pisa(super.meta) {
     final metaRadius = meta.childItems[0];
@@ -21,14 +24,27 @@ class Pisa extends TopMeasureItem<PisaFeature> {
     childItems.add(trace);
     isCrossAreaMode = true;
     canChildOutputSelf = false;
+
+    final alsVelOutputMeta =
+        ItemOutputMeta(MeasureTerms.AlsVel, "Als.Vel", VidUsUnit.cms);
+    _alsVel = FloatValue(alsVelOutputMeta, 65.0, alsVelOutputMeta.unit);
+    // _alsVel = FloatValue(alsVelOutputMeta, 65.9, alsVelOutputMeta.unit);
   }
 
   @override
   bool get finishAfterUnactive => true;
 
+  FloatValue get alsVel => _alsVel;
+
   @override
   PisaFeature buildFeature() => PisaFeature(this);
 
+  void updateAlsVel(double val) {
+    _alsVel.value = val;
+    calculator?.calculate();
+    update();
+  }
+
   static Pisa createPisa(ItemMeta meta, [IMeasureItem? parent]) {
     var pisa = Pisa(meta);
     pisa.calculator = PisaCal(pisa);

+ 1 - 0
lib/process/primitives/rvsp.dart

@@ -49,6 +49,7 @@ class Rvsp extends TopMeasureItem<RvspFeature> {
   void updateRap(double val) {
     _rap.value = val;
     calculator?.calculate();
+    update();
   }
 
   static Rvsp createRvsp(

+ 8 - 2
lib/view/measure/custom_item_buttons/combo.dart

@@ -3,10 +3,10 @@ import 'package:fis_measure/interfaces/process/items/types.dart';
 import 'package:fis_ui/index.dart';
 import 'package:fis_ui/interface/interactive_container.dart';
 import 'package:flutter/material.dart';
-import 'package:get/get.dart';
 
 import '../measure_tool.dart';
 import 'normal_child.dart';
+import 'pisa.dart';
 import 'rvsp.dart';
 
 class CustomComboItemGroup extends FStatelessWidget {
@@ -15,6 +15,7 @@ class CustomComboItemGroup extends FStatelessWidget {
 
   static final Map<String, List<String>> specialItemChildTypes = {
     MeasureTypes.RVSP: ["Rap"],
+    MeasureTypes.PISA: ["AlsVel"],
   };
 
   final FInteractiveContainer businessParent;
@@ -80,6 +81,7 @@ class CustomComboItemGroup extends FStatelessWidget {
 class _SpecialChildItemWidget extends FStatelessWidget {
   late final Map<String, FWidgetBuilder> _specialWidgetMap = {
     "Rap": buildRap,
+    "AlsVel": buildAlsVel,
   };
 
   final String typeName;
@@ -94,6 +96,10 @@ class _SpecialChildItemWidget extends FStatelessWidget {
   }
 
   FWidget buildRap(BuildContext context) {
-    return RVSPItemButton();
+    return const RapItemButton();
+  }
+
+  FWidget buildAlsVel(BuildContext context) {
+    return const AlsVelItemButton();
   }
 }

+ 108 - 0
lib/view/measure/custom_item_buttons/pisa.dart

@@ -0,0 +1,108 @@
+import 'package:fis_i18n/i18n.dart';
+import 'package:fis_measure/define.dart';
+import 'package:fis_measure/interfaces/process/items/item_metas.dart';
+import 'package:fis_measure/interfaces/process/workspace/application.dart';
+import 'package:fis_measure/process/primitives/combos/pisa.dart';
+import 'package:fis_measure/process/primitives/rvsp.dart';
+import 'package:fis_measure/utils/prompt_box.dart';
+import 'package:fis_ui/index.dart';
+import 'package:fis_ui/interface/interactive_container.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import 'normal_child.dart';
+import 'widgets.dart';
+
+class AlsVelItemButton extends FStatefulWidget {
+  const AlsVelItemButton({super.key});
+
+  @override
+  FState<FStatefulWidget> createState() => _AlsVelItemButtonState();
+}
+
+class _AlsVelItemButtonState extends FState<AlsVelItemButton> {
+  double _customAlsVelValue = 0;
+
+  @override
+  void initState() {
+    _initState();
+    super.initState();
+  }
+
+  @override
+  FWidget build(BuildContext context) {
+    return NormalChildButton(
+      businessParent: const FPlaceHolderInteractiveContainer(),
+      activeIndex: -1,
+      index: 0,
+      itemMeta: ItemMeta(
+        "Als.Vel",
+        measureType: '',
+        description: 'Als.Vel',
+        outputs: [],
+      ),
+      onClick: () {
+        Get.dialog(_buildDialog());
+      },
+    );
+  }
+
+  void _initState() {
+    final activeMeasureItem = Get.find<IApplication>().activeMeasureItem;
+    if (activeMeasureItem == null) {
+      return;
+    }
+    _customAlsVelValue = (activeMeasureItem as Pisa).alsVel.value!;
+  }
+
+  FWidget _buildDialog() {
+    final children = <Widget>[];
+    children.add(_buildInputButton());
+    return FSimpleDialog(
+      title: const FText("Als.Vel", style: TextStyle(color: Colors.white)),
+      okString: i18nBook.common.confirm.t,
+      cancelString: i18nBook.common.cancel.t,
+      isDefault: true,
+      children: children,
+      onOk: () {
+        _onValueConfrim();
+        Get.back();
+      },
+      onCancel: () {
+        Get.back();
+      },
+      onClose: () {
+        Get.back();
+      },
+    );
+  }
+
+  void _onValueConfrim() {
+    final activeMeasureItem = Get.find<IApplication>().activeMeasureItem;
+    if (activeMeasureItem == null) {
+      return;
+    }
+
+    double alsVel = _customAlsVelValue;
+    (activeMeasureItem as Pisa).updateAlsVel(alsVel);
+  }
+
+  Widget _buildInputButton() {
+    return CustomChildInputOptionWidget(
+      isActive: false,
+      value: _customAlsVelValue.toString(),
+      suffixWidget: const Text(" cm/s"),
+      onClick: () {
+        //
+      },
+      onInputChanged: (String value) {
+        final doubleValue = double.tryParse(value);
+        if (doubleValue == null) {
+          PromptBox.toast("请输入正确的Als.Vel数值");
+        } else {
+          _customAlsVelValue = doubleValue;
+        }
+      },
+    );
+  }
+}

+ 6 - 129
lib/view/measure/custom_item_buttons/rvsp.dart

@@ -4,21 +4,21 @@ import 'package:fis_measure/interfaces/process/items/item_metas.dart';
 import 'package:fis_measure/interfaces/process/workspace/application.dart';
 import 'package:fis_measure/process/primitives/rvsp.dart';
 import 'package:fis_measure/utils/prompt_box.dart';
+import 'package:fis_measure/view/measure/custom_item_buttons/widgets.dart';
 import 'package:fis_ui/index.dart';
-import 'package:fis_ui/interface/interactive_container.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 
 import 'normal_child.dart';
 
-class RVSPItemButton extends FStatefulWidget {
-  const RVSPItemButton({super.key});
+class RapItemButton extends FStatefulWidget {
+  const RapItemButton({super.key});
 
   @override
-  FState<FStatefulWidget> createState() => _RVSPItemButtonState();
+  FState<FStatefulWidget> createState() => _RapItemButtonState();
 }
 
-class _RVSPItemButtonState extends FState<RVSPItemButton> {
+class _RapItemButtonState extends FState<RapItemButton> {
   static const _options = <double>[double.nan, 5, 10, 15];
   late final RxInt _activeIndex;
   double _customRapValue = 0;
@@ -132,6 +132,7 @@ class _RVSPItemButtonState extends FState<RVSPItemButton> {
     return CustomChildInputOptionWidget(
       isActive: isActive,
       value: _customRapValue.toString(),
+      suffixWidget: const Text(" mmHg"),
       onClick: () {
         _activeIndex.value = index;
       },
@@ -146,127 +147,3 @@ class _RVSPItemButtonState extends FState<RVSPItemButton> {
     );
   }
 }
-
-class CustomChildInputOptionWidget extends StatelessWidget {
-  final bool isActive;
-  final String value;
-  final Widget? suffixWidget;
-  final VoidCallback onClick;
-  final ValueChanged<String> onInputChanged;
-
-  const CustomChildInputOptionWidget({
-    super.key,
-    required this.isActive,
-    required this.value,
-    required this.onClick,
-    required this.onInputChanged,
-    this.suffixWidget,
-  });
-
-  @override
-  Widget build(BuildContext context) {
-    return CustomChildOptionWidget(
-      isActive: isActive,
-      onClick: onClick,
-      child: _buildInput(context),
-    );
-  }
-
-  Widget _buildInput(BuildContext context) {
-    final children = <Widget>[];
-    children.add(
-      FBorderInput(
-        controller: TextEditingController(text: value),
-        onChanged: (value) {
-          onInputChanged.call(value);
-        },
-      ),
-    );
-    if (suffixWidget != null) {
-      children.add(suffixWidget!);
-    }
-    return Row(
-      children: children,
-      mainAxisAlignment: MainAxisAlignment.center,
-    );
-  }
-}
-
-class CustomChildOptionDivider extends StatelessWidget {
-  const CustomChildOptionDivider({super.key});
-
-  @override
-  Widget build(BuildContext context) {
-    return Divider(color: Colors.grey.shade100, height: 1);
-  }
-}
-
-class CustomChildTextOptionWidget extends StatelessWidget {
-  final bool isActive;
-  final String text;
-  final VoidCallback onClick;
-
-  const CustomChildTextOptionWidget({
-    super.key,
-    required this.isActive,
-    required this.text,
-    required this.onClick,
-  });
-
-  @override
-  Widget build(BuildContext context) {
-    return CustomChildOptionWidget(
-      child: Text(text),
-      isActive: isActive,
-      onClick: onClick,
-    );
-  }
-}
-
-class CustomChildOptionWidget extends StatelessWidget {
-  final bool isActive;
-  final Widget child;
-  final VoidCallback onClick;
-
-  const CustomChildOptionWidget({
-    super.key,
-    required this.isActive,
-    required this.child,
-    required this.onClick,
-  });
-
-  @override
-  Widget build(BuildContext context) {
-    return Material(
-      child: Container(
-        margin: const EdgeInsets.only(bottom: 4),
-        child: GestureDetector(
-          onTap: () {
-            onClick.call();
-          },
-          child: Container(
-            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18),
-            alignment: Alignment.center,
-            color: isActive
-                ? Theme.of(context).secondaryHeaderColor
-                : Colors.white,
-            child: child,
-          ),
-        ),
-      ),
-    );
-  }
-}
-
-class FPlaceHolderInteractiveContainer extends FStatelessWidget
-    implements FInteractiveContainer {
-  const FPlaceHolderInteractiveContainer({super.key});
-
-  @override
-  FWidget build(BuildContext context) {
-    return const FSizedBox();
-  }
-
-  @override
-  String get pageName => "";
-}

+ 127 - 0
lib/view/measure/custom_item_buttons/widgets.dart

@@ -0,0 +1,127 @@
+import 'package:fis_ui/index.dart';
+import 'package:fis_ui/interface/interactive_container.dart';
+import 'package:flutter/material.dart';
+
+class CustomChildInputOptionWidget extends StatelessWidget {
+  final bool isActive;
+  final String value;
+  final Widget? suffixWidget;
+  final VoidCallback onClick;
+  final ValueChanged<String> onInputChanged;
+
+  const CustomChildInputOptionWidget({
+    super.key,
+    required this.isActive,
+    required this.value,
+    required this.onClick,
+    required this.onInputChanged,
+    this.suffixWidget,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomChildOptionWidget(
+      isActive: isActive,
+      onClick: onClick,
+      child: _buildInput(context),
+    );
+  }
+
+  Widget _buildInput(BuildContext context) {
+    final children = <Widget>[];
+    children.add(
+      FBorderInput(
+        controller: TextEditingController(text: value),
+        onChanged: (value) {
+          onInputChanged.call(value);
+        },
+      ),
+    );
+    if (suffixWidget != null) {
+      children.add(suffixWidget!);
+    }
+    return Row(
+      children: children,
+      mainAxisAlignment: MainAxisAlignment.center,
+    );
+  }
+}
+
+class CustomChildOptionDivider extends StatelessWidget {
+  const CustomChildOptionDivider({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Divider(color: Colors.grey.shade100, height: 1);
+  }
+}
+
+class CustomChildTextOptionWidget extends StatelessWidget {
+  final bool isActive;
+  final String text;
+  final VoidCallback onClick;
+
+  const CustomChildTextOptionWidget({
+    super.key,
+    required this.isActive,
+    required this.text,
+    required this.onClick,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomChildOptionWidget(
+      child: Text(text),
+      isActive: isActive,
+      onClick: onClick,
+    );
+  }
+}
+
+class CustomChildOptionWidget extends StatelessWidget {
+  final bool isActive;
+  final Widget child;
+  final VoidCallback onClick;
+
+  const CustomChildOptionWidget({
+    super.key,
+    required this.isActive,
+    required this.child,
+    required this.onClick,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+      child: Container(
+        margin: const EdgeInsets.only(bottom: 4),
+        child: GestureDetector(
+          onTap: () {
+            onClick.call();
+          },
+          child: Container(
+            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18),
+            alignment: Alignment.center,
+            color: isActive
+                ? Theme.of(context).secondaryHeaderColor
+                : Colors.white,
+            child: child,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class FPlaceHolderInteractiveContainer extends FStatelessWidget
+    implements FInteractiveContainer {
+  const FPlaceHolderInteractiveContainer({super.key});
+
+  @override
+  FWidget build(BuildContext context) {
+    return const FSizedBox();
+  }
+
+  @override
+  String get pageName => "";
+}