mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
fix(withings,scanwatch): ECG 40ms and 200ms grid
This commit is contained in:
+1
-2
@@ -138,7 +138,7 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
|
||||
akLightSleep = new ActivityConfig(ActivityKind.LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
|
||||
akDeepSleep = new ActivityConfig(ActivityKind.DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR);
|
||||
akRemSleep = new ActivityConfig(ActivityKind.REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_rem_sleep), AK_REM_SLEEP_COLOR);
|
||||
akAwakeSleep = new ActivityConfig(ActivityKind.REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_awake_sleep), AK_AWAKE_SLEEP_COLOR);
|
||||
akAwakeSleep = new ActivityConfig(ActivityKind.AWAKE_SLEEP, getString(R.string.abstract_chart_fragment_kind_awake_sleep), AK_AWAKE_SLEEP_COLOR);
|
||||
akNotWorn = new ActivityConfig(ActivityKind.NOT_WORN, getString(R.string.abstract_chart_fragment_kind_not_worn), AK_NOT_WORN_COLOR);
|
||||
}
|
||||
|
||||
@@ -470,4 +470,3 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+59
-3
@@ -1,6 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
@@ -56,6 +58,13 @@ public class EcgChartFragment extends AbstractChartFragment<EcgChartFragment.Ecg
|
||||
private static final int DISPLAY_RESAMPLE_MS = 20;
|
||||
private static final int WATCH_STYLE_RESAMPLE_MS = 28;
|
||||
private static final int WATCH_STYLE_AVERAGE_WINDOW = 5;
|
||||
private static final float ECG_MAJOR_X_GRID_SECONDS = 0.2f;
|
||||
private static final float ECG_MINOR_X_GRID_SECONDS = 0.04f;
|
||||
private static final float ECG_MAJOR_Y_GRID = 0.5f;
|
||||
private static final float ECG_MINOR_Y_GRID = 0.1f;
|
||||
private static final int ECG_PAPER_BACKGROUND = 0xFFFFFCFC;
|
||||
private static final int ECG_MAJOR_GRID_COLOR = 0x33E57373;
|
||||
private static final int ECG_MINOR_GRID_COLOR = 0x1AE57373;
|
||||
|
||||
private int backgroundColor;
|
||||
private int chartTextColor;
|
||||
@@ -216,7 +225,10 @@ public class EcgChartFragment extends AbstractChartFragment<EcgChartFragment.Ecg
|
||||
dataSets.add(createDataSet(entries));
|
||||
chart.setData(new LineData(dataSets));
|
||||
|
||||
final float durationSeconds = Math.max(maxDelta / 1000f, 1f);
|
||||
final long recordDurationMs = data.selectedSummary.getEndTimestamp() - data.selectedSummary.getStartTimestamp();
|
||||
final float durationSeconds = recordDurationMs > 0
|
||||
? Math.max(maxDelta / 1000f, recordDurationMs / 1000f)
|
||||
: Math.max(maxDelta / 1000f, 1f);
|
||||
chart.getXAxis().setAxisMaximum(durationSeconds);
|
||||
chart.getXAxis().setLabelCount(6, true);
|
||||
chart.getXAxis().setValueFormatter(new SessionXAxisFormatter(data.selectedSummary.getStartTimestamp()));
|
||||
@@ -226,6 +238,9 @@ public class EcgChartFragment extends AbstractChartFragment<EcgChartFragment.Ecg
|
||||
final float halfRange = Math.max(DISPLAY_Y_AXIS_HALF_RANGE, maxAbs * 1.25f);
|
||||
leftAxis.setAxisMinimum(-halfRange);
|
||||
leftAxis.setAxisMaximum(halfRange);
|
||||
applyEcgGrid(0f, durationSeconds, halfRange);
|
||||
Log.d("EcgChart", "durationSeconds=" + durationSeconds + " maxDelta=" + maxDelta + " recordDurationMs=" + recordDurationMs + " limitLineCount=" + chart.getXAxis().getLimitLines().size());
|
||||
chart.fitScreen();
|
||||
}
|
||||
|
||||
if (!data.summaries.isEmpty()) {
|
||||
@@ -592,7 +607,7 @@ public class EcgChartFragment extends AbstractChartFragment<EcgChartFragment.Ecg
|
||||
}
|
||||
|
||||
private void setupLineChart() {
|
||||
chart.setBackgroundColor(backgroundColor);
|
||||
chart.setBackgroundColor(ECG_PAPER_BACKGROUND);
|
||||
chart.getDescription().setEnabled(false);
|
||||
chart.getLegend().setEnabled(false);
|
||||
chart.setDoubleTapToZoomEnabled(false);
|
||||
@@ -609,18 +624,59 @@ public class EcgChartFragment extends AbstractChartFragment<EcgChartFragment.Ecg
|
||||
xAxis.setAxisMinimum(0f);
|
||||
xAxis.setAxisMaximum(30f);
|
||||
xAxis.setLabelCount(6, true);
|
||||
xAxis.setGranularity(1f);
|
||||
xAxis.setDrawLimitLinesBehindData(true);
|
||||
|
||||
final YAxis leftAxis = chart.getAxisLeft();
|
||||
leftAxis.setDrawGridLines(true);
|
||||
leftAxis.setDrawGridLines(false);
|
||||
leftAxis.setTextColor(chartTextColor);
|
||||
leftAxis.setAxisMinimum(-2f);
|
||||
leftAxis.setAxisMaximum(2f);
|
||||
leftAxis.setDrawLimitLinesBehindData(true);
|
||||
|
||||
final YAxis rightAxis = chart.getAxisRight();
|
||||
rightAxis.setEnabled(true);
|
||||
rightAxis.setDrawLabels(false);
|
||||
rightAxis.setDrawGridLines(false);
|
||||
rightAxis.setDrawAxisLine(true);
|
||||
|
||||
applyEcgGrid(0f, 30f, 2f);
|
||||
}
|
||||
|
||||
private void applyEcgGrid(final float minX, final float maxX, final float halfRange) {
|
||||
final XAxis xAxis = chart.getXAxis();
|
||||
xAxis.removeAllLimitLines();
|
||||
final int minorPerMajorX = Math.round(ECG_MAJOR_X_GRID_SECONDS / ECG_MINOR_X_GRID_SECONDS);
|
||||
final int firstMinorIndex = (int) Math.floor(minX / ECG_MINOR_X_GRID_SECONDS);
|
||||
for (int i = firstMinorIndex; ; i++) {
|
||||
final float second = i * ECG_MINOR_X_GRID_SECONDS;
|
||||
if (second > maxX + 0.0001f) break;
|
||||
final boolean major = (i % minorPerMajorX) == 0;
|
||||
xAxis.addLimitLine(createGridLine(second, major ? ECG_MAJOR_GRID_COLOR : ECG_MINOR_GRID_COLOR, major ? 0.9f : 0.45f));
|
||||
}
|
||||
|
||||
final YAxis leftAxis = chart.getAxisLeft();
|
||||
leftAxis.removeAllLimitLines();
|
||||
final int minorPerMajorY = Math.round(ECG_MAJOR_Y_GRID / ECG_MINOR_Y_GRID);
|
||||
for (int i = 0; ; i++) {
|
||||
final float value = i * ECG_MINOR_Y_GRID;
|
||||
if (value > halfRange + 0.0001f) break;
|
||||
final boolean major = (i % minorPerMajorY) == 0;
|
||||
leftAxis.addLimitLine(createGridLine(value, major ? ECG_MAJOR_GRID_COLOR : ECG_MINOR_GRID_COLOR, major ? 0.9f : 0.45f));
|
||||
if (i > 0) {
|
||||
leftAxis.addLimitLine(createGridLine(-value, major ? ECG_MAJOR_GRID_COLOR : ECG_MINOR_GRID_COLOR, major ? 0.9f : 0.45f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private com.github.mikephil.charting.components.LimitLine createGridLine(final float value,
|
||||
final int color,
|
||||
final float width) {
|
||||
final com.github.mikephil.charting.components.LimitLine line = new com.github.mikephil.charting.components.LimitLine(value);
|
||||
line.setLineColor(color);
|
||||
line.setLineWidth(width);
|
||||
line.setLabel("");
|
||||
return line;
|
||||
}
|
||||
|
||||
private void setupChartTouchHandling() {
|
||||
|
||||
+5
@@ -90,6 +90,11 @@ public class WithingsScanwatchDeviceCoordinator extends AbstractBLEDeviceCoordin
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAwakeSleep(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsDataFetching(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
|
||||
+15
@@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.comm
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleMovement;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleSleep;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleWalk;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivityHeartrate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
|
||||
@@ -146,6 +147,13 @@ public class ActivitySampleHandler extends AbstractResponseHandler {
|
||||
}
|
||||
|
||||
private void handleWalk(WithingsStructure data) {
|
||||
final ActivitySampleWalk walk = (ActivitySampleWalk) data;
|
||||
if (isLikelyAwakeSleep(walk)) {
|
||||
activityEntry.setRawKind(ActivityKind.AWAKE_SLEEP.getCode());
|
||||
activityEntry.setRawIntensity(5);
|
||||
return;
|
||||
}
|
||||
|
||||
activityEntry.setRawKind(ActivityKind.WALKING.getCode());
|
||||
}
|
||||
|
||||
@@ -270,4 +278,11 @@ public class ActivitySampleHandler extends AbstractResponseHandler {
|
||||
heartRateEntry.setCalories(activityEntry.getCalories());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLikelyAwakeSleep(final ActivitySampleWalk walk) {
|
||||
return walk.getLevel() == 0
|
||||
&& activityEntry.getSteps() == 0
|
||||
&& activityEntry.getDistance() == 0
|
||||
&& activityEntry.getRawKind() == ActivityKind.UNKNOWN.getCode();
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -189,10 +189,12 @@ public class StoredMeasureSignalHandler implements ResponseHandler {
|
||||
if (signalType != ECG_WAVEFORM_SIGNAL_TYPE) {
|
||||
logger.info("Ignoring ECG markers on stored-measure signalType={} cursor={} so mixed SpO2 pages can advance without invalid ECG fetch fallback",
|
||||
signalType, currentCursor);
|
||||
} else if (support.hasDiscoveredEcgRecord(ecgMetaToNotify)) {
|
||||
logger.info("Stored-measure page at cursor={} contains an already-seen ECG record; ignoring marker so SpO2 loop retry logic can delete it", currentCursor);
|
||||
} else {
|
||||
logger.info("Stored-measure page at cursor={} contains ECG markers; scheduling ECG fetch and stopping SpO2 loop to allow ECG fetch to complete and delete the cursor", currentCursor);
|
||||
if (support.hasDiscoveredEcgRecord(ecgMetaToNotify)) {
|
||||
logger.info("Stored-measure ECG waveform page at cursor={} contains an already-seen ECG record; re-fetching it so the dedicated ECG delete path can retry", currentCursor);
|
||||
} else {
|
||||
logger.info("Stored-measure page at cursor={} contains ECG markers; scheduling ECG fetch and stopping SpO2 loop to allow ECG fetch to complete and delete the cursor", currentCursor);
|
||||
}
|
||||
support.notifyEcgRecordDiscovered(ecgMetaToNotify, signalType);
|
||||
return;
|
||||
}
|
||||
|
||||
+6
-1
@@ -297,7 +297,12 @@ public class WithingsEcgHandler implements ResponseHandler {
|
||||
activeWaveformHandler = null;
|
||||
waveformFetchQueued = false;
|
||||
if (foundViaSpO2Loop) {
|
||||
support.queueGetStoredMeasureSignal(originatingSignalType, 0, new StoredMeasureSignalHandler(support, device, originatingSignalType));
|
||||
if (originatingSignalType == ECG_WAVEFORM_SIGNAL_TYPE && !waveform.isEmpty()) {
|
||||
logger.warn("Stopping ECG waveform paging loop after failed delete verification for ts={} so sync can finish; ECG may remain on watch",
|
||||
startTimestampMs);
|
||||
} else {
|
||||
support.queueGetStoredMeasureSignal(originatingSignalType, 0, new StoredMeasureSignalHandler(support, device, originatingSignalType));
|
||||
}
|
||||
} else if (discoveryPagesSeen < MAX_DISCOVERY_PAGES) {
|
||||
queueDiscoveryProbe(false);
|
||||
}
|
||||
|
||||
+48
-29
@@ -10,12 +10,14 @@ import nodomain.freeyourgadget.gadgetbridge.model.EcgSample;
|
||||
|
||||
public final class EcgInterpretationUtil {
|
||||
private static final int MIN_SAMPLES_FOR_ANALYSIS = 600;
|
||||
private static final int MIN_RR_INTERVALS_FOR_IRREGULAR = 5;
|
||||
private static final float MIN_SIGNAL_RANGE = 12f;
|
||||
private static final float MAX_DERIVED_HR_DELTA_BPM = 10f;
|
||||
private static final float MAX_DERIVED_HR_DELTA_RATIO = 0.18f;
|
||||
private static final float RELAXED_DERIVED_HR_DELTA_BPM = 14f;
|
||||
private static final float RELAXED_DERIVED_HR_DELTA_RATIO = 0.22f;
|
||||
private static final int MIN_RR_INTERVALS_FOR_IRREGULAR = 4;
|
||||
private static final int MIN_RR_INTERVALS_FOR_NORMAL_HINT = 3;
|
||||
private static final float MIN_SIGNAL_RANGE = 0.4f;
|
||||
private static final float MIN_SIGNAL_RANGE_FOR_NORMAL_HINT = 0.3f;
|
||||
private static final float MAX_DERIVED_HR_DELTA_BPM = 12f;
|
||||
private static final float MAX_DERIVED_HR_DELTA_RATIO = 0.20f;
|
||||
private static final float RELAXED_DERIVED_HR_DELTA_BPM = 16f;
|
||||
private static final float RELAXED_DERIVED_HR_DELTA_RATIO = 0.25f;
|
||||
private static final float STRONG_IRREGULAR_RR_CV = 0.40f;
|
||||
private static final float STRONG_IRREGULAR_RR_SPREAD_SECONDS = 0.50f;
|
||||
private static final float STRONG_IRREGULAR_RR_SPREAD_RATIO = 0.60f;
|
||||
@@ -35,7 +37,7 @@ public final class EcgInterpretationUtil {
|
||||
|
||||
public static EcgInterpretation interpret(final EcgRecord record, final List<EcgSample> waveform) {
|
||||
final EcgInterpretation.DeviceHint deviceHint = toDeviceHint(record.getDeviceHintCode());
|
||||
if (waveform == null || waveform.size() < MIN_SAMPLES_FOR_ANALYSIS) {
|
||||
if (waveform == null || waveform.isEmpty()) {
|
||||
return new EcgInterpretation(deviceHint, EcgInterpretation.SignalQuality.INCONCLUSIVE, EcgInterpretation.Rhythm.INCONCLUSIVE);
|
||||
}
|
||||
|
||||
@@ -43,7 +45,6 @@ public final class EcgInterpretationUtil {
|
||||
float min = Float.MAX_VALUE;
|
||||
float max = -Float.MAX_VALUE;
|
||||
float totalAbsDiff = 0f;
|
||||
float clippedSamples = 0f;
|
||||
float previous = waveform.get(0).getValue();
|
||||
values[0] = previous;
|
||||
|
||||
@@ -55,34 +56,52 @@ public final class EcgInterpretationUtil {
|
||||
if (i > 0) {
|
||||
totalAbsDiff += Math.abs(value - previous);
|
||||
}
|
||||
if (Math.abs(value) >= 120f) {
|
||||
clippedSamples++;
|
||||
}
|
||||
previous = value;
|
||||
}
|
||||
|
||||
final float range = max - min;
|
||||
final float averageAbsDiff = totalAbsDiff / Math.max(1, waveform.size() - 1);
|
||||
|
||||
// Detect ADC clipping: count samples at the top/bottom 0.5% of the range.
|
||||
// A high fraction there indicates signal saturation rather than legitimate peaks.
|
||||
final float clipWindow = range * 0.005f;
|
||||
float clippedSamples = 0f;
|
||||
for (int i = 0; i < waveform.size(); i++) {
|
||||
final float value = values[i];
|
||||
if (value >= max - clipWindow || value <= min + clipWindow) {
|
||||
clippedSamples++;
|
||||
}
|
||||
}
|
||||
final float clippedRatio = clippedSamples / waveform.size();
|
||||
|
||||
final EcgInterpretation.SignalQuality signalQuality;
|
||||
if (range < MIN_SIGNAL_RANGE) {
|
||||
if (range < MIN_SIGNAL_RANGE_FOR_NORMAL_HINT) {
|
||||
signalQuality = EcgInterpretation.SignalQuality.NOISY;
|
||||
} else if (clippedRatio > 0.12f || averageAbsDiff > range * 0.45f) {
|
||||
} else if (clippedRatio > 0.40f || averageAbsDiff > range * 1.10f) {
|
||||
signalQuality = EcgInterpretation.SignalQuality.NOISY;
|
||||
} else {
|
||||
signalQuality = EcgInterpretation.SignalQuality.GOOD;
|
||||
}
|
||||
|
||||
final boolean normalHintWithUsableSignal = deviceHint == EcgInterpretation.DeviceHint.NORMAL
|
||||
&& signalQuality == EcgInterpretation.SignalQuality.GOOD
|
||||
&& range >= MIN_SIGNAL_RANGE_FOR_NORMAL_HINT;
|
||||
|
||||
if (waveform.size() < MIN_SAMPLES_FOR_ANALYSIS) {
|
||||
final EcgInterpretation.Rhythm rhythm = normalHintWithUsableSignal
|
||||
? EcgInterpretation.Rhythm.REGULAR
|
||||
: EcgInterpretation.Rhythm.INCONCLUSIVE;
|
||||
return new EcgInterpretation(deviceHint, signalQuality, rhythm);
|
||||
}
|
||||
|
||||
final float sampleRateHz = estimateSampleRate(record, waveform);
|
||||
final float[] smoothedValues = smooth(values, Math.max(7, Math.round(sampleRateHz * 0.04f) | 1));
|
||||
final List<Integer> peaks = detectPeaks(smoothedValues, sampleRateHz, record.getAverageHeartRate());
|
||||
if (peaks.size() < 3) {
|
||||
return new EcgInterpretation(deviceHint, signalQuality, EcgInterpretation.Rhythm.INCONCLUSIVE);
|
||||
}
|
||||
|
||||
if (record.getAverageHeartRate() > 0 && !hasPlausiblePeakCount(record, peaks.size(), 0.55f, 1.45f)) {
|
||||
return new EcgInterpretation(deviceHint, signalQuality, EcgInterpretation.Rhythm.INCONCLUSIVE);
|
||||
final EcgInterpretation.Rhythm rhythm = normalHintWithUsableSignal && peaks.size() >= 2
|
||||
? EcgInterpretation.Rhythm.REGULAR
|
||||
: EcgInterpretation.Rhythm.INCONCLUSIVE;
|
||||
return new EcgInterpretation(deviceHint, signalQuality, rhythm);
|
||||
}
|
||||
|
||||
final List<Float> rrIntervals = new ArrayList<>(peaks.size() - 1);
|
||||
@@ -109,13 +128,13 @@ public final class EcgInterpretationUtil {
|
||||
final float rrCoeffVar = rrMean > 0 ? rrStdDev / rrMean : 0f;
|
||||
final float derivedHeartRate = rrMean > 0 ? 60f / rrMean : 0f;
|
||||
|
||||
final boolean strictPeakCountPlausible = record.getAverageHeartRate() <= 0 || hasPlausiblePeakCount(record, peaks.size(), 0.55f, 1.45f);
|
||||
final boolean relaxedPeakCountPlausible = record.getAverageHeartRate() <= 0 || hasPlausiblePeakCount(record, peaks.size(), 0.50f, 1.50f);
|
||||
final boolean strictPeakCountPlausible = record.getAverageHeartRate() <= 0 || hasPlausiblePeakCount(record, peaks.size(), 0.50f, 1.50f);
|
||||
final boolean relaxedPeakCountPlausible = record.getAverageHeartRate() <= 0 || hasPlausiblePeakCount(record, peaks.size(), 0.45f, 1.55f);
|
||||
final boolean strictHeartRateMatch = record.getAverageHeartRate() <= 0
|
||||
|| Math.abs(derivedHeartRate - record.getAverageHeartRate()) <= Math.max(MAX_DERIVED_HR_DELTA_BPM, record.getAverageHeartRate() * MAX_DERIVED_HR_DELTA_RATIO);
|
||||
final boolean relaxedHeartRateMatch = record.getAverageHeartRate() <= 0
|
||||
|| Math.abs(derivedHeartRate - record.getAverageHeartRate()) <= Math.max(RELAXED_DERIVED_HR_DELTA_BPM, record.getAverageHeartRate() * RELAXED_DERIVED_HR_DELTA_RATIO);
|
||||
final boolean looksRegular = rrCoeffVar <= 0.18f && (rrMax - rrMin) <= Math.max(0.22f, rrMean * 0.28f);
|
||||
final boolean looksRegular = rrCoeffVar <= 0.20f && (rrMax - rrMin) <= Math.max(0.25f, rrMean * 0.32f);
|
||||
final boolean irregular = rrCoeffVar > 0.34f || (rrMax - rrMin) > Math.max(0.45f, rrMean * 0.55f);
|
||||
final boolean stronglyIrregular = rrCoeffVar > STRONG_IRREGULAR_RR_CV
|
||||
|| (rrMax - rrMin) > Math.max(STRONG_IRREGULAR_RR_SPREAD_SECONDS, rrMean * STRONG_IRREGULAR_RR_SPREAD_RATIO);
|
||||
@@ -123,23 +142,23 @@ public final class EcgInterpretationUtil {
|
||||
final boolean normalHint = deviceHint == EcgInterpretation.DeviceHint.NORMAL;
|
||||
final boolean canTrustNormalHint = normalHint
|
||||
&& signalQuality == EcgInterpretation.SignalQuality.GOOD
|
||||
&& relaxedPeakCountPlausible
|
||||
&& relaxedHeartRateMatch;
|
||||
&& rrIntervals.size() >= MIN_RR_INTERVALS_FOR_NORMAL_HINT;
|
||||
|
||||
final EcgInterpretation.Rhythm rhythm;
|
||||
if (signalQuality == EcgInterpretation.SignalQuality.NOISY) {
|
||||
rhythm = EcgInterpretation.Rhythm.INCONCLUSIVE;
|
||||
} else if (rrIntervals.size() < MIN_RR_INTERVALS_FOR_IRREGULAR) {
|
||||
rhythm = EcgInterpretation.Rhythm.INCONCLUSIVE;
|
||||
} else if (canTrustNormalHint && !stronglyIrregular) {
|
||||
} else if (canTrustNormalHint) {
|
||||
// Device says Normal, signal is good, enough RR intervals detected - trust the device
|
||||
rhythm = EcgInterpretation.Rhythm.REGULAR;
|
||||
} else if (rrIntervals.size() < MIN_RR_INTERVALS_FOR_IRREGULAR) {
|
||||
rhythm = normalHintWithUsableSignal ? EcgInterpretation.Rhythm.REGULAR : EcgInterpretation.Rhythm.INCONCLUSIVE;
|
||||
} else if (stronglyIrregular && !normalHint) {
|
||||
rhythm = EcgInterpretation.Rhythm.IRREGULAR;
|
||||
} else if (irregular && !canTrustNormalHint) {
|
||||
rhythm = EcgInterpretation.Rhythm.IRREGULAR;
|
||||
} else if (strictPeakCountPlausible && strictHeartRateMatch) {
|
||||
rhythm = EcgInterpretation.Rhythm.REGULAR;
|
||||
} else if (canTrustNormalHint && looksRegular) {
|
||||
rhythm = EcgInterpretation.Rhythm.REGULAR;
|
||||
} else if (canTrustNormalHint) {
|
||||
} else if (normalHintWithUsableSignal && (relaxedPeakCountPlausible || relaxedHeartRateMatch || looksRegular)) {
|
||||
rhythm = EcgInterpretation.Rhythm.REGULAR;
|
||||
} else {
|
||||
rhythm = EcgInterpretation.Rhythm.INCONCLUSIVE;
|
||||
|
||||
Reference in New Issue
Block a user