// ignore_for_file: invalid_use_of_protected_member import 'package:fis_measure/interfaces/date_types/point.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/auto_doppler_trace.dart'; import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/cardiac_cycle.dart'; import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/data.dart'; import 'dart:math' as math; import 'calculator.dart'; class AutoDopplerTraceCal extends Calculator { AutoDopplerTraceCal(TraceItemAbstract ref) : super(ref); double cyclesStart = double.maxFinite; double cyclesEnd = 0; int validSystoleCyclesLength = 0; @override void calculate() { if (ref.feature == null) return; Map calculateDopplerTraceResult = {}; AutoDopplerTraceFeature feature = ref.feature! as AutoDopplerTraceFeature; /// 最大点集 List maxPhysicalPonints = TraceListData.aboveMaxPonints; List validSystoleCycles = feature.currentCardiacCycleList; if (validSystoleCycles.isEmpty) { List regionPoints = feature.innerPoints .map((e) => convertTimeMotionPoint(feature, e)) .toList(); final yFlippedPoints = regionPoints; double min = math.min(yFlippedPoints.first.x, yFlippedPoints.last.x); double max = math.max(yFlippedPoints.first.x, yFlippedPoints.last.x); calculateDopplerTraceResult = calculateDopplerTrace( yFlippedPoints, min, max, ); } if (validSystoleCyclesLength != validSystoleCycles.length) { print(validSystoleCyclesLength); for (int i = 0; i < validSystoleCycles.length; i++) { /// 获取物理点坐标 List regionPoints = getPoints( maxPhysicalPonints, validSystoleCycles[i], ); final points = regionPoints.map((e) { final currentPoint = convertTimeMotionPoint(feature, e); return DPoint(currentPoint.x, currentPoint.y); }).toList(); if (points.isEmpty) { return; } double min = math.min(points.first.x, points.last.x); double max = math.max(points.first.x, points.last.x); calculateDopplerTraceResult = calculateDopplerTrace( points, min, max, validSystoleCycles[i], ); } if (validSystoleCycles.isEmpty) { feature.initValues(); } validSystoleCyclesLength = validSystoleCycles.length; } for (var output in ref.meta.outputs) { ///TODO:[Gavin] 实现以下计算逻辑 switch (output.name) { case MeasureTerms.Placeholder: feature.updateStringValue(output, "", output.unit); break; case MeasureTerms.TAMAX: if (calculateDopplerTraceResult[MeasureTerms.TAMAX] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.TAMAX]!, output.unit); } break; case MeasureTerms.TAMEAN: if (calculateDopplerTraceResult[MeasureTerms.TAMEAN] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.TAMEAN]!, output.unit); } break; case MeasureTerms.PS: if (calculateDopplerTraceResult[MeasureTerms.PS] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.PS]!, output.unit); } break; case MeasureTerms.ED: if (calculateDopplerTraceResult[MeasureTerms.ED] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.ED]!, output.unit); } break; case MeasureTerms.MD: if (calculateDopplerTraceResult[MeasureTerms.MD] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.MD]!, output.unit); } break; case MeasureTerms.HeartRate: if (calculateDopplerTraceResult[MeasureTerms.HeartRate] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.HeartRate]!, output.unit); } break; case MeasureTerms.Acceleration: if (calculateDopplerTraceResult[MeasureTerms.Acceleration] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.Acceleration]!, output.unit); } break; case MeasureTerms.AT: if (calculateDopplerTraceResult[MeasureTerms.AT] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.AT]!, output.unit); } break; case MeasureTerms.PSED: if (calculateDopplerTraceResult[MeasureTerms.PSED] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.PSED]!, output.unit); } break; case MeasureTerms.EDPS: if (calculateDopplerTraceResult[MeasureTerms.EDPS] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.EDPS]!, output.unit); } break; case MeasureTerms.PI: if (calculateDopplerTraceResult[MeasureTerms.PI] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.PI]!, output.unit); } break; case MeasureTerms.PIMD: if (calculateDopplerTraceResult[MeasureTerms.PIMD] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.PIMD]!, output.unit); } break; case MeasureTerms.RI: if (calculateDopplerTraceResult[MeasureTerms.RI] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.RI]!, output.unit); } break; case MeasureTerms.RIMD: if (calculateDopplerTraceResult[MeasureTerms.RIMD] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.RIMD]!, output.unit); } break; case MeasureTerms.MaxPG: if (calculateDopplerTraceResult[MeasureTerms.MaxPG] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.MaxPG]!, output.unit); } break; case MeasureTerms.VelocityMax: if (calculateDopplerTraceResult[MeasureTerms.VelocityMax] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.VelocityMax]!, output.unit); } break; case MeasureTerms.VelocityMean: if (calculateDopplerTraceResult[MeasureTerms.VelocityMean] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.VelocityMean]!, output.unit); } break; case MeasureTerms.PeakPG: if (calculateDopplerTraceResult[MeasureTerms.PeakPG] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.PeakPG]!, output.unit); } break; case MeasureTerms.VTI: if (calculateDopplerTraceResult[MeasureTerms.VTI] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.VTI]!, output.unit); } break; case MeasureTerms.VTIMean: if (calculateDopplerTraceResult[MeasureTerms.VTIMean] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.VTIMean]!, output.unit); } break; case MeasureTerms.MPG: if (calculateDopplerTraceResult[MeasureTerms.MPG] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.MPG]!, output.unit); } break; case MeasureTerms.MMPG: if (calculateDopplerTraceResult[MeasureTerms.MMPG] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.MMPG]!, output.unit); } break; case MeasureTerms.TiEnv: // var outputTiEnv = GeneralFormulas.countEnvelopeTime(points); if (calculateDopplerTraceResult[MeasureTerms.TiEnv] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.TiEnv]!, output.unit); } break; case MeasureTerms.EVEL: if (calculateDopplerTraceResult[MeasureTerms.EVEL] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.EVEL]!, output.unit); } break; case MeasureTerms.AVEL: if (calculateDopplerTraceResult[MeasureTerms.AVEL] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.AVEL]!, output.unit); } break; case MeasureTerms.EARatio: if (calculateDopplerTraceResult[MeasureTerms.EARatio] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.EARatio]!, output.unit); } break; case MeasureTerms.DT: if (calculateDopplerTraceResult[MeasureTerms.DT] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.DT]!, output.unit); } break; case MeasureTerms.PHT: if (calculateDopplerTraceResult[MeasureTerms.PHT] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.PHT]!, output.unit); } break; case MeasureTerms.VA: if (calculateDopplerTraceResult[MeasureTerms.VA] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.VA]!, output.unit); } break; case MeasureTerms.ADur: if (calculateDopplerTraceResult[MeasureTerms.ADur] != null) { feature.updateFloatValue(output, calculateDopplerTraceResult[MeasureTerms.ADur]!, output.unit); } break; case MeasureTerms.ATDTRatio: if (calculateDopplerTraceResult[MeasureTerms.ATDTRatio] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.ATDTRatio]!, output.unit); } break; case MeasureTerms.ATETRatio: if (calculateDopplerTraceResult[MeasureTerms.ATETRatio] != null) { feature.updateFloatValue( output, calculateDopplerTraceResult[MeasureTerms.ATETRatio]!, output.unit); } break; // case MeasureTerms.Trace: // if (calculateDopplerTraceResult[MeasureTerms.TAMAX] != null) { // break; default: break; } } } List getPoints( List lines, CardiacCycle cycle, ) { // Size displaySize = Get.find().displaySize; List points = []; for (DPoint line in lines) { // DPoint logicPoint = convert2LogicPoint(line); final systoleStartX = cycle.systoleStart.x; final diastoleEndX = cycle.diastoleEnd.x; if (line.x >= systoleStartX && line.x <= diastoleEndX) { points.add(DPoint(line.x, line.y)); } } return points; } List getFeatureYFlippedPoints(MeasureItemFeature feature) { final regionPoints = feature.innerPoints .map((e) => convertTimeMotionPoint(feature, e)) .toList(); return regionPoints; } 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) { List meanResult = GeneralFormulas.countVTI(meanTraceLineOfCycle); vtiMean = meanResult.first; taMean = meanResult[2]; } 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? maxTraceLine, double envStart = double.nan, double envEnd = double.nan, CardiacCycle? cardiacCycle, ]) { Map baseValues = {}; if (cardiacCycle != null) { CardiacCycle transitionCardiacCycle = CardiacCycle( diastoleEnd: DPoint(0, 0), systoleStart: DPoint(0, 0), peakSystolic: DPoint(0, 0), minimumAbsoluteVelocity: DPoint(0, 0), index: 0, ); transitionCardiacCycle.peakSystolic = convertTimeMotionPoint(ref.feature!, cardiacCycle.peakSystolic); transitionCardiacCycle.systoleStart = convertTimeMotionPoint(ref.feature!, cardiacCycle.systoleStart); transitionCardiacCycle.diastoleEnd = convertTimeMotionPoint(ref.feature!, cardiacCycle.diastoleEnd); transitionCardiacCycle.index = cardiacCycle.index; cyclesStart = math.min( transitionCardiacCycle.systoleStart.x, cyclesStart, ); cyclesEnd = math.min( transitionCardiacCycle.diastoleEnd.x, cyclesEnd, ); List maxTraceLineOfCycle = maxTraceLine ?? []; List meanTraceLineOfCycle = maxTraceLine?.map((e) => DPoint(e.x, e.y * 0.75)).toList() ?? []; baseValues = calculateHeartCycleRelevantValues( transitionCardiacCycle, 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, }); } } else if (!envStart.isNaN && !envEnd.isNaN && !envStart.almostEquals(double.minPositive, 0.000001) && !envEnd.almostEquals(double.minPositive, 0.000001)) { // List maxTraceLine = []; // List meanTraceLine = []; Map meanValues = tryCalculateByTrace( maxTraceLine!.map((e) => DPoint(e.x, e.y * 0.75)).toList(), envStart, envEnd, false, ); Map maxValues = tryCalculateByTrace( maxTraceLine, envStart, envEnd, true, ); baseValues.addAll(meanValues); baseValues.addAll(maxValues); } 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) { line.add(point); // 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); } }