import 'package:fis_measure/interfaces/date_types/point.dart'; import 'package:fis_measure/interfaces/date_types/vector.dart'; import 'package:fis_measure/interfaces/process/items/terms.dart'; import 'package:fis_measure/process/calcuators/formulas/cardiac.dart'; import 'package:fis_measure/process/calcuators/formulas/general.dart'; import 'package:fis_measure/process/items/item_feature.dart'; import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/cardiac_cycle.dart'; import 'package:fis_measure/process/primitives/multi_method/multiple_trace.dart'; import 'dart:math' as math; import 'calculator.dart'; class TraceCal extends Calculator { TraceCal(TraceItemAbstract ref) : super(ref); @override void calculate() { if (ref.feature == null) return; final feature = ref.feature!; final viewport = feature.hostVisualArea!.viewport!; // 加入画布偏移量 final canvasOffset = feature.hostVisualArea!.layoutRegion!.topLeft; //加入坐标系偏移量 final coordinateOffset = viewport.region; final regionPoints = feature.innerPoints .map((e) => viewport .convert( e.clone().addVector(DVector(-canvasOffset.x, -canvasOffset.y))) .addVector(DVector(coordinateOffset.left, -coordinateOffset.top))) .toList(); final yFlippedPoints = regionPoints.map((e) => DPoint(e.x, -e.y)).toList(); double fakeOutputDate = 0; var countVTIResult = GeneralFormulas.countVTI(yFlippedPoints); Map result = calculateDopplerTrace(yFlippedPoints); var outputVTI = countVTIResult[0]; var outputVTIMean = countVTIResult[0]; var outputTiEnv = countVTIResult[1]; var outputTAMAX = countVTIResult[2]; var outputTAMEAN = countVTIResult[2]; var outputVelocityMax = countVTIResult[3]; var outputVelocityMean = countVTIResult[3]; var outputMPG = countVTIResult[4]; var outputMMPG = countVTIResult[4]; var outputHR = countVTIResult[5]; for (var output in ref.meta.outputs) { ///TODO:[Gavin] 实现以下计算逻辑 switch (output.name) { case MeasureTerms.TAMAX: feature.updateFloatValue(output, outputTAMAX, output.unit); break; case MeasureTerms.TAMEAN: feature.updateFloatValue(output, outputTAMEAN, output.unit); break; case MeasureTerms.PS: if (result[MeasureTerms.PS] != null) { feature.updateFloatValue( output, result[MeasureTerms.PS]!, output.unit); } break; case MeasureTerms.ED: if (result[MeasureTerms.ED] != null) { feature.updateFloatValue( output, result[MeasureTerms.ED]!, output.unit); } break; case MeasureTerms.MD: if (result[MeasureTerms.MD] != null) { feature.updateFloatValue( output, result[MeasureTerms.MD]!, output.unit); } break; case MeasureTerms.HeartRate: feature.updateFloatValue(output, outputHR, output.unit); break; case MeasureTerms.Acceleration: if (result[MeasureTerms.Acceleration] != null) { feature.updateFloatValue( output, result[MeasureTerms.Acceleration]!, output.unit); } break; case MeasureTerms.AT: if (result[MeasureTerms.AT] != null) { feature.updateFloatValue( output, result[MeasureTerms.AT]!, output.unit); } break; case MeasureTerms.PSED: if (result[MeasureTerms.PSED] != null) { feature.updateFloatValue( output, result[MeasureTerms.PSED]!, output.unit); } break; case MeasureTerms.EDPS: if (result[MeasureTerms.EDPS] != null) { feature.updateFloatValue( output, result[MeasureTerms.EDPS]!, output.unit); } break; case MeasureTerms.PI: if (result[MeasureTerms.PI] != null) { feature.updateFloatValue( output, result[MeasureTerms.PI]!, output.unit); } break; case MeasureTerms.PIMD: if (result[MeasureTerms.PIMD] != null) { feature.updateFloatValue( output, result[MeasureTerms.PIMD]!, output.unit); } break; case MeasureTerms.RI: if (result[MeasureTerms.RI] != null) { feature.updateFloatValue( output, result[MeasureTerms.RI]!, output.unit); } break; case MeasureTerms.RIMD: if (result[MeasureTerms.RIMD] != null) { feature.updateFloatValue( output, result[MeasureTerms.RIMD]!, output.unit); } break; case MeasureTerms.MaxPG: feature.updateFloatValue( output, result[MeasureTerms.MaxPG]!, output.unit); break; case MeasureTerms.VelocityMax: feature.updateFloatValue(output, outputVelocityMax, output.unit); break; case MeasureTerms.VelocityMean: feature.updateFloatValue(output, outputVelocityMean, output.unit); break; case MeasureTerms.PeakPG: feature.updateFloatValue( output, result[MeasureTerms.PeakPG]!, output.unit); break; case MeasureTerms.VTI: feature.updateFloatValue(output, outputVTI, output.unit); break; case MeasureTerms.VTIMean: feature.updateFloatValue(output, outputVTIMean, output.unit); break; case MeasureTerms.MPG: feature.updateFloatValue(output, outputMPG, output.unit); break; case MeasureTerms.MMPG: feature.updateFloatValue(output, outputMMPG, output.unit); break; case MeasureTerms.TiEnv: // var outputTiEnv = GeneralFormulas.countEnvelopeTime(points); feature.updateFloatValue(output, outputTiEnv, output.unit); break; case MeasureTerms.EVEL: if (result[MeasureTerms.EVEL] != null) { feature.updateFloatValue( output, result[MeasureTerms.EVEL]!, output.unit); } break; case MeasureTerms.AVEL: if (result[MeasureTerms.AVEL] != null) { feature.updateFloatValue( output, result[MeasureTerms.AVEL]!, output.unit); } break; case MeasureTerms.EARatio: if (result[MeasureTerms.EARatio] != null) { feature.updateFloatValue( output, result[MeasureTerms.EARatio]!, output.unit); } break; case MeasureTerms.DT: if (result[MeasureTerms.DT] != null) { feature.updateFloatValue( output, result[MeasureTerms.DT]!, output.unit); } break; case MeasureTerms.PHT: if (result[MeasureTerms.PHT] != null) { feature.updateFloatValue( output, result[MeasureTerms.PHT]!, output.unit); } break; case MeasureTerms.VA: if (result[MeasureTerms.VA] != null) { feature.updateFloatValue( output, result[MeasureTerms.VA]!, output.unit); } break; case MeasureTerms.ADur: if (result[MeasureTerms.ADur] != null) { feature.updateFloatValue( output, result[MeasureTerms.ADur]!, output.unit); } break; case MeasureTerms.ATDTRatio: if (result[MeasureTerms.ATDTRatio] != null) { feature.updateFloatValue( output, result[MeasureTerms.ATDTRatio]!, output.unit); } break; case MeasureTerms.ATETRatio: if (result[MeasureTerms.ATETRatio] != null) { feature.updateFloatValue( output, result[MeasureTerms.ATETRatio]!, output.unit); } break; // case MeasureTerms.Trace: // feature.updateFloatValue(output, fakeOutputDate, output.unit); // break; default: break; } } } static List getFeatureYFlippedPoints(MeasureItemFeature feature) { final viewport = feature.hostVisualArea!.viewport!; // 加入画布偏移量 final canvasOffset = feature.hostVisualArea!.layoutRegion!.topLeft; //加入坐标系偏移量 final coordinateOffset = viewport.region; final regionPoints = feature.innerPoints .map((e) => viewport .convert( e.clone().addVector(DVector(-canvasOffset.x, -canvasOffset.y))) .addVector(DVector(coordinateOffset.left, -coordinateOffset.top))) .toList(); final points = regionPoints.map((e) => DPoint(e.x, -e.y)).toList(); return points; } static List getCountVTI(MeasureItemFeature feature) { final yFlippedPoints = getFeatureYFlippedPoints(feature); final result = GeneralFormulas.countVTI(yFlippedPoints); return result; } /// 获取到值之后 Map calculateHeartCycleRelevantValues( CardiacCycle heartCycle, List maxTraceLineOfCycle, List meanTraceLineOfCycle, ) { double pv = double.nan; double vtiMean = double.nan; double taMean = double.nan; double mmpg = double.nan; if (meanTraceLineOfCycle.isNotEmpty) { vtiMean = GeneralFormulas.countVTI(meanTraceLineOfCycle).first; } double vti = double.nan; double taMax = double.nan; double mpg = double.nan; double ps = double.nan; double ed = double.nan; double md = double.nan; double acctime = double.nan; double accel = double.nan; if (maxTraceLineOfCycle.isNotEmpty) { List vtiResult = GeneralFormulas.countVTI(maxTraceLineOfCycle); vti = vtiResult.first; ps = heartCycle.peakSystolic.y; ed = heartCycle.diastoleEnd.y; md = heartCycle.minimumAbsoluteVelocity.y; taMax = vtiResult[2]; pv = vtiResult[3]; mpg = vtiResult[4]; mmpg = vtiResult[4]; acctime = heartCycle.peakSystolic.x - heartCycle.systoleStart.x; accel = (heartCycle.peakSystolic.y - heartCycle.systoleStart.y).abs() / acctime; } double velE = double.nan; double velA = double.nan; double dt = double.nan; double pht = double.nan; double va = double.nan; double aDur = double.nan; double eAration = double.nan; if (heartCycle.ePeak != DPoint.zero && heartCycle.ePeak != null) { velE = heartCycle.ePeak!.y; if (heartCycle.ePeakEnd != DPoint.zero && heartCycle.ePeak != DPoint.zero) { acctime = heartCycle.ePeak!.x - heartCycle.systoleStart.x; accel = (heartCycle.ePeak!.y - heartCycle.systoleStart.y).abs() / acctime; } } if (heartCycle.aPeak != DPoint.zero && heartCycle.aPeak != null) { velA = heartCycle.aPeak!.y; } if (heartCycle.ePeak != DPoint.zero && heartCycle.aPeak != DPoint.zero) { eAration = (velE - velA).abs(); } if (heartCycle.ePeak != DPoint.zero && heartCycle.ePeakEnd != DPoint.zero && heartCycle.aPeak != null && heartCycle.ePeakEnd != null) { dt = (heartCycle.ePeakEnd!.x - heartCycle.ePeak!.x).abs(); pht = CardiacFormulas.phtByDecT(dt); va = CardiacFormulas.mvaByPht(pht); } if (heartCycle.aPeakStart != DPoint.zero && heartCycle.aPeakStart != null) { aDur = (heartCycle.diastoleEnd.x - heartCycle.aPeakStart!.x).abs(); } return { MeasureTerms.PS: ps, MeasureTerms.ED: ed, MeasureTerms.MD: md, MeasureTerms.VelocityMax: pv, MeasureTerms.AT: acctime, MeasureTerms.Acceleration: accel, MeasureTerms.TAMAX: taMax, MeasureTerms.TAMEAN: taMean, MeasureTerms.VTI: vti, MeasureTerms.VTIMean: vtiMean, MeasureTerms.MPG: mpg, MeasureTerms.MMPG: mmpg, MeasureTerms.EVEL: velE, MeasureTerms.AVEL: velA, MeasureTerms.EARatio: eAration, MeasureTerms.DT: dt, MeasureTerms.PHT: pht, MeasureTerms.VA: va, MeasureTerms.ADur: aDur, }; } double ratio(double d1, double d2) { return d1 / d2; } Map calculateDopplerTrace([ List? maxTraceLineOfCycle, ]) { double cyclesStart = double.maxFinite; double cyclesEnd = double.minPositive; DPoint systoleStart = maxTraceLineOfCycle!.first; DPoint diastoleEnd = maxTraceLineOfCycle.last; double maxDistance = 0; DPoint peakSystolic = DPoint(0, 0); for (var i = 1; i < maxTraceLineOfCycle.length; i++) { final point = maxTraceLineOfCycle[i]; final distance = (point.y - systoleStart.y).abs(); if (distance > maxDistance) { maxDistance = distance; peakSystolic = point; } } List validSystoleCycles = [ CardiacCycle( diastoleEnd: diastoleEnd, systoleStart: systoleStart, minimumAbsoluteVelocity: DPoint(0, 0), index: 0, peakSystolic: peakSystolic, ) ]; Map baseValues = {}; /// 获取全量的测量值的方法 dopplerTrace.AvgHeartCycle > 0 && dopplerTrace.CardiacCycles.Count >= dopplerTrace.AvgHeartCycle for (CardiacCycle i in validSystoleCycles) { cyclesStart = math.min(i.systoleStart.x, cyclesStart); cyclesEnd = math.max(i.diastoleEnd.x, cyclesEnd); /// TODO 这边需要看超声机代码,继续捞值 // List maxTraceLineOfCycle = []; List meanTraceLineOfCycle = []; baseValues = calculateHeartCycleRelevantValues( i, maxTraceLineOfCycle, meanTraceLineOfCycle, ); } double? ps = baseValues[MeasureTerms.PS]; double? ed = baseValues[MeasureTerms.ED]; double? md = baseValues[MeasureTerms.MD]; double? taMax = baseValues[MeasureTerms.TAMAX]; if (ps != null) { if (ed != null) { double maxPG = GeneralFormulas.maxPG(ps, ed); double psed = ratio(ps, ed).abs(); double edps = ratio(ed, ps).abs(); double ri = GeneralFormulas.countRI(ps, ed); double velocityMean = GeneralFormulas.medianVelocity(ps, ed); baseValues.addAll({ MeasureTerms.MaxPG: maxPG, MeasureTerms.PSED: psed, MeasureTerms.EDPS: edps, MeasureTerms.RI: ri.abs(), MeasureTerms.VelocityMean: velocityMean, }); if (taMax != null) { double pi = GeneralFormulas.pi(ps, ed, taMax); baseValues.addAll({ MeasureTerms.PI: pi, }); } double vMean = GeneralFormulas.medianVelocity(ps, ed); double piTCD = GeneralFormulas.pi(ps, ed, vMean); if (!piTCD.isNaN) { baseValues.addAll({ MeasureTerms.PITCD: piTCD, }); } } if (md != null) { double rimd = GeneralFormulas.countRI(ps, md).abs(); baseValues.addAll({ MeasureTerms.RIMD: rimd, }); if (taMax != null) { double piMd = GeneralFormulas.pi(ps, md, taMax); if (!piMd.isNaN) { baseValues.addAll({ MeasureTerms.PIMD: piMd, }); } } } } double? pv = baseValues[MeasureTerms.VelocityMax]; if (pv != null) { double peakPG = GeneralFormulas.countPressure(pv); baseValues.addAll({ MeasureTerms.PeakPG: peakPG, }); } double cycleEnvTimeSpan = (cyclesEnd - cyclesStart).abs(); if (cycleEnvTimeSpan > 0) { /// dopplerTrace.AvgHeartCycle, 需要获取 double? hr = GeneralFormulas.countHR(cycleEnvTimeSpan); baseValues.addAll({ MeasureTerms.HeartRate: hr, MeasureTerms.TiEnv: cycleEnvTimeSpan, }); } double? velE = baseValues[MeasureTerms.EVEL]; double? velA = baseValues[MeasureTerms.AVEL]; if (velA != null && velE != null) { double? eARatio = ratio(velA, velE).abs(); baseValues.addAll({ MeasureTerms.EARatio: eARatio, }); } double? at = baseValues[MeasureTerms.AT]; double? dt = baseValues[MeasureTerms.DT]; double? et = baseValues[MeasureTerms.TiEnv]; if (at != null && dt != null && et != null) { double? atdt = ratio(at, dt).abs(); double? atet = ratio(at, et).abs(); baseValues.addAll({ MeasureTerms.ATDTRatio: atdt, MeasureTerms.ATETRatio: atet, }); } return baseValues; } Map tryCalculateByTrace( List traceLine, double envStart, double envEnd, bool isMax, ) { double vti = double.nan; double tiEnv = double.nan; double ta = double.nan; double pv = double.nan; double meanPG = double.nan; if (traceLine.isNotEmpty) { List line = []; for (var point in traceLine) { if (point.x.almostNotLessThan(point.x, envStart) && point.x.almostNotGreaterThan(point.x, envEnd)) { line.add(point); } } if (line.isNotEmpty) { List vueResult = GeneralFormulas.countVTI(line); vti = vueResult.first; var isShowAbsValue = true; // 假设这是从某个服务获取的值 if (isShowAbsValue) { ta = vueResult[2].abs(); pv = vueResult[3].abs(); tiEnv = vueResult[1]; meanPG = vueResult[4]; } } } if (isMax) { return { MeasureTerms.TAMAX: ta, MeasureTerms.VTI: vti, MeasureTerms.MPG: meanPG, MeasureTerms.TiEnv: tiEnv, MeasureTerms.VelocityMax: pv }; } return { MeasureTerms.TAMEAN: ta, MeasureTerms.VTIMean: vti, MeasureTerms.MMPG: meanPG, MeasureTerms.TiEnv: tiEnv, MeasureTerms.VelocityMax: pv, MeasureTerms.PeakPG: pv, }; } } extension DoubleExtensions on double { bool almostEquals(double other, double precision) { if (isNaN && other.isNaN) { return true; } return (this - other).abs() <= precision.abs(); } bool almostNotLessThan(double double1, double double2, [double precision = 0.000001]) { if (double1 < double2) { return true; } return double1.almostEquals(double2, precision); } bool almostNotGreaterThan(double double1, double double2, [double precision = 0.000001]) { if (double1 > double2) { return true; } return double1.almostEquals(double2, precision); } }