Explorar o código

support FlowAreaByVti

Melon hai 10 meses
pai
achega
563a678c92

+ 3 - 0
lib/interfaces/process/items/item.dart

@@ -82,6 +82,9 @@ abstract class ITopMeasureItem implements IMeasureItem {
   /// 是否跨帧测量模式
   bool get isCrossFrameMode;
 
+  /// 是否允许子项自行输出
+  bool get canChildOutputSelf;
+
   /// 工作子项变更事件
   late final FEventHandler<int> workingChildChanged;
 

+ 2 - 0
lib/interfaces/process/items/terms.dart

@@ -439,6 +439,8 @@ class MeasureTerms {
   /// 血流的速度时间积分
   static const VTI = "VTI";
 
+  static const OT = "OT";
+
   /// 血流的速度时间积分
   static const VTIMean = "VTI(Mean)";
 // TODO:

+ 1 - 0
lib/interfaces/process/items/types.dart

@@ -146,6 +146,7 @@ class MeasureTypes {
   static const MultiQpQs = "MultiQpQs";
   static const LvDpDt = "LvDpDt";
   static const FlowVol = "FlowVol";
+  static const FlowAreaByVTI = "FlowAreaByVTI";
 
   /// 阻力指数
   static const ResistivityIndex = "ResistivityIndex";

+ 145 - 1
lib/process/calcuators/flow.dart

@@ -1,11 +1,15 @@
+import 'package:fis_measure/configs/patient.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/trace.dart';
 import 'package:fis_measure/process/items/item_feature.dart';
+import 'package:fis_measure/process/primitives/combos/flow_area_vti.dart';
 import 'package:fis_measure/process/primitives/combos/sv.dart';
 import 'package:fis_measure/process/primitives/multi_method/multiple_trace.dart';
 import 'package:vid/us/vid_us_unit.dart';
 
 import 'calculator.dart';
+import 'formulas/cardiac.dart';
 import 'formulas/general.dart';
 
 class FlowVolumeCal extends Calculator<Sv, double> {
@@ -84,7 +88,7 @@ class FlowVolumeCal extends Calculator<Sv, double> {
   }
 }
 
-class FlowAreaByVTICal extends Calculator<Sv, double> {
+class FlowAreaByVTICal extends Calculator<FlowAreaByVti, double> {
   FlowAreaByVTICal(super.ref);
 
   @override
@@ -92,5 +96,145 @@ class FlowAreaByVTICal extends Calculator<Sv, double> {
     if (ref.feature == null) return;
 
     final feature = ref.feature!;
+
+    final diam = pickChildFloatValue(ref.l);
+
+    feature.values.clear();
+    updateStringValue("");
+    if (diam == null) {
+      return;
+    }
+
+    final diamOutputIndex =
+        ref.l.meta.outputs.indexWhere((e) => e.name == MeasureTerms.Diam);
+    if (diamOutputIndex > -1) {
+      final output = ref.l.meta.outputs[diamOutputIndex];
+      feature.updateFloatValue(output, diam, VidUsUnit.cm);
+    }
+
+    double va = double.nan;
+    double dvi = double.nan;
+    double avaIndex = double.nan;
+    double otVti = double.nan;
+    double vti = double.nan;
+    double otSV = double.nan;
+    double otCSA = double.nan;
+
+    otCSA = GeneralFormulas.csa(diam); // ???
+
+    final otFeature = _findTraceOT();
+    final vtiFeature = _findTraceVTI();
+
+    if (otFeature != null) {
+      final countOtVTIResult = TraceCal.getCountVTI(otFeature);
+      otVti = countOtVTIResult[0];
+    }
+
+    if (vtiFeature != null) {
+      final countVTIResult = TraceCal.getCountVTI(vtiFeature);
+      vti = countVTIResult[0];
+    }
+
+    if (!vti.isNaN && !otVti.isNaN) {
+      va = CardiacFormulas.flowAreaByVTI(diam, otVti, vti);
+      dvi = CardiacFormulas.dviByVTI(otVti, vti);
+
+      final bsa = GlobalPatientConfig.bsa;
+      if (bsa > 0) {
+        avaIndex = CardiacFormulas.avaIndex(va, bsa);
+      }
+
+      otSV = GeneralFormulas.svDiam(diam, otVti);
+      otCSA = GeneralFormulas.csa(diam);
+    }
+
+    if (otFeature != null) {
+      for (var output in ref.trace1.meta.outputs) {
+        if (output.name == MeasureTerms.SV_Trace) {
+          feature.updateStringValue(
+            ItemOutputMeta(output.description, output.description, output.unit),
+            '',
+          );
+        } else if (output.name == MeasureTerms.VTI) {
+          if (!otVti.isNaN) {
+            feature.updateFloatValue(
+              ItemOutputMeta(
+                  output.description, output.description, output.unit),
+              otVti,
+              VidUsUnit.cm,
+            );
+          }
+        }
+      }
+    }
+    if (vtiFeature != null) {
+      for (var output in ref.trace2.meta.outputs) {
+        if (output.name == MeasureTerms.SV_Trace) {
+          feature.updateStringValue(
+            ItemOutputMeta(output.description, output.description, output.unit),
+            '',
+          );
+        } else if (output.name == MeasureTerms.VTI) {
+          if (!vti.isNaN) {
+            feature.updateFloatValue(
+              ItemOutputMeta(
+                  output.description, output.description, output.unit),
+              vti,
+              VidUsUnit.cm,
+            );
+          }
+        }
+      }
+    }
+
+    for (var output in ref.meta.outputs) {
+      switch (output.name) {
+        case MeasureTerms.FlowArea:
+          if (!va.isNaN) {
+            feature.updateFloatValue(output, va, VidUsUnit.cm2);
+          }
+          break;
+        case MeasureTerms.AVAI:
+          if (!avaIndex.isNaN) {
+            feature.updateFloatValue(output, avaIndex, VidUsUnit.cm2m2);
+          }
+          break;
+        case MeasureTerms.DVI:
+          if (!dvi.isNaN) {
+            feature.updateFloatValue(output, dvi, VidUsUnit.None);
+          }
+          break;
+        case MeasureTerms.SV:
+          if (!otSV.isNaN) {
+            feature.updateFloatValue(output, otSV, VidUsUnit.cm3);
+          }
+          break;
+        case MeasureTerms.CSA:
+          if (!otCSA.isNaN) {
+            feature.updateFloatValue(output, otCSA, VidUsUnit.cm2);
+          }
+          break;
+      }
+    }
+  }
+
+  TraceItemFeatureAbstract? _findTraceOT() {
+    TraceItemFeatureAbstract? traceFeature = ref.trace1.feature;
+    if (traceFeature == null) {
+      if (ref.trace1.measuredFeatures.isNotEmpty) {
+        traceFeature = ref.trace1.measuredFeatures.first;
+      }
+    }
+    return traceFeature;
+  }
+
+  TraceItemFeatureAbstract? _findTraceVTI() {
+    TraceItemFeatureAbstract? traceFeature = ref.trace2.feature;
+    if (traceFeature == null) {
+      if (ref.trace2.measuredFeatures.isNotEmpty) {
+        traceFeature = ref.trace2.measuredFeatures.first;
+      }
+    }
+    return traceFeature;
   }
 }

+ 42 - 0
lib/process/calcuators/formulas/cardiac.dart

@@ -4,6 +4,8 @@ import 'package:fis_common/logger/logger.dart';
 import 'package:fis_measure/configs/cardiac.dart';
 import 'package:fis_measure/utils/number.dart';
 
+import 'general.dart';
+
 class CardiacFormulas {
   CardiacFormulas._();
 
@@ -176,4 +178,44 @@ class CardiacFormulas {
     double si = sv / bsa;
     return si;
   }
+
+  /// <summary>
+  /// <para>MVA VTI = 1/4 x π x (LVOT Diam)^2 x LVOT VTI/MV VTI </para>
+  /// <para>AVA VTI = 1/4 x π x (LVOT Diam)^2 x LVOT VTI/AV VTI</para>
+  /// <para>TVA VTI = 1/4 x π x (RVOT Diam)^2 x RVOT VTI/TV VTI</para>
+  /// <para>PVA VTI = 1/4 x π x (RVOT Diam)^2 x RVOT VTI/PV VTI</para>
+  /// </summary>
+  /// <param name="otDiam">cm</param>
+  /// <param name="otvti">cm</param>
+  /// <param name="vti">cm</param>
+  /// <returns>cm^2</returns>
+  static double flowAreaByVTI(double otDiam, double otvti, double vti) {
+    double sv = 0.25 * math.pi * math.pow(otDiam, 2) * otvti / vti;
+    return sv;
+  }
+
+  /// <param name="otvti">cm</param>
+  /// <param name="vti">cm</param>
+  /// <returns>Unit None</returns>
+  static double dviByVTI(double otvti, double vti) {
+    double dvi = double.nan;
+    if (!GeneralFormulas.doubleAlmostEquals(vti, 0)) {
+      dvi = otvti / vti;
+    }
+    return dvi;
+  }
+
+  /// <summary>
+  /// AVA Index = avaByVTI/bsa
+  /// </summary>
+  /// <param name="avaByVTI">cm2</param>
+  /// <param name="bsa">m2</param>
+  /// <returns>cm2/m2</returns>
+  static double avaIndex(double avaByVTI, double bsa) {
+    double index = double.nan;
+    if (!GeneralFormulas.doubleAlmostEquals(bsa, 0)) {
+      index = avaByVTI / bsa;
+    }
+    return index;
+  }
 }

+ 4 - 1
lib/process/items/factory.dart

@@ -6,6 +6,7 @@ import 'package:fis_measure/process/primitives/carotid_imt.dart';
 import 'package:fis_measure/process/primitives/combos/afi.dart';
 import 'package:fis_measure/process/primitives/combos/area_straightline.dart';
 import 'package:fis_measure/process/primitives/combos/depth2baseline.dart';
+import 'package:fis_measure/process/primitives/combos/flow_area_vti.dart';
 import 'package:fis_measure/process/primitives/combos/lv_mass.dart';
 import 'package:fis_measure/process/primitives/combos/lwh_straightline.dart';
 import 'package:fis_measure/process/primitives/combos/lv_study.dart';
@@ -100,6 +101,9 @@ class MeasureItemFactory {
     _singleton._register(MeasureTypes.SV, Sv.createSV);
     _singleton._register(MeasureTypes.QpQs, TwoSv.crateQpQs);
     _singleton._register(MeasureTypes.MultiQpQs, TwoSv.crateQpQs);
+    _singleton._register(MeasureTypes.FlowVol, Sv.createFlowVol);
+    _singleton._register(
+        MeasureTypes.FlowAreaByVTI, FlowAreaByVti.createFlowAreaByVti);
 
     // Empty
     _singleton._register(MeasureTypes.Empty, Empty.createEmpty);
@@ -121,7 +125,6 @@ class MeasureItemFactory {
     _singleton._register(MeasureTypes.Distance, StraightLine.createDistance);
     _singleton._register(MeasureTypes.Ray, Ray.createRay);
     _singleton._register(MeasureTypes.LvDpDt, StraightLine.createLvDpDt);
-    _singleton._register(MeasureTypes.FlowVol, Sv.createFlowVol);
 
     // Three StraightLine
     _singleton._register(

+ 5 - 0
lib/process/items/top_item.dart

@@ -13,6 +13,7 @@ abstract class TopMeasureItem<T extends MeasureItemFeature>
   final List<IMeasureItem> _childItems = [];
   int _childIndex = 0;
   bool _isCrossFrameMode = false;
+  bool _canChildOutputSelf = true;
 
   bool get ifAutoStart => false;
   bool get ifAutoFinish => false;
@@ -47,6 +48,10 @@ abstract class TopMeasureItem<T extends MeasureItemFeature>
   @protected
   set isCrossFrameMode(bool val) => _isCrossFrameMode = val;
 
+  @override
+  bool get canChildOutputSelf => _canChildOutputSelf;
+  set canChildOutputSelf(bool val) => _canChildOutputSelf = val;
+
   @override
   late final FEventHandler<int> workingChildChanged;
 

+ 42 - 0
lib/process/primitives/combos/flow_area_vti.dart

@@ -0,0 +1,42 @@
+import 'package:fis_measure/interfaces/process/items/item.dart';
+import 'package:fis_measure/interfaces/process/items/item_metas.dart';
+import 'package:fis_measure/process/calcuators/flow.dart';
+import 'package:fis_measure/process/items/top_item.dart';
+import 'package:fis_measure/process/items/top_item_feature.dart';
+import 'package:fis_measure/process/primitives/multi_method/multiple_trace.dart';
+import 'package:fis_measure/process/primitives/straightline.dart';
+
+class FlowAreaByVti extends TopMeasureItem<FlowAreaByVtiFeature> {
+  late final StraightLine l;
+  late final MultiTrace trace1;
+  late final MultiTrace trace2;
+
+  FlowAreaByVti(super.meta) {
+    final metaDiam = meta.childItems[0];
+    final metaTrace1 = meta.childItems[1];
+    final metaTrace2 = meta.childItems[2];
+    l = StraightLine.createDistance(metaDiam, this);
+    trace1 = MultiTrace.createTrace(metaTrace1, this);
+    trace2 = MultiTrace.createTrace(metaTrace2, this);
+    childItems.add(l);
+    childItems.add(trace1);
+    childItems.add(trace2);
+  }
+
+  @override
+  FlowAreaByVtiFeature buildFeature() => FlowAreaByVtiFeature(this);
+
+  static FlowAreaByVti createFlowAreaByVti(ItemMeta meta,
+      [IMeasureItem? parent]) {
+    var ins = FlowAreaByVti(meta);
+    ins.canChildOutputSelf = false;
+    ins.calculator = FlowAreaByVTICal(ins);
+    return ins;
+  }
+}
+
+class FlowAreaByVtiFeature extends TopMeasureItemFeature {
+  FlowAreaByVtiFeature(
+    ITopMeasureItem refItem,
+  ) : super(refItem);
+}

+ 1 - 1
lib/view/result/converter.dart

@@ -27,7 +27,7 @@ class FeatureValueDescConverter {
     arr.addAll(mainLines);
 
     final ref = feature.refItem;
-    if (ref is ITopMeasureItem) {
+    if (ref is ITopMeasureItem && ref.canChildOutputSelf) {
       for (var child in ref.childItems) {
         for (var feature in child.measuredFeatures) {
           final str = _findChildLine(feature);