From 4cc026799c451f2d869f472d503de5ecbb6661fc Mon Sep 17 00:00:00 2001 From: Brian Gow Date: Mon, 15 Dec 2025 10:17:17 -0500 Subject: [PATCH 1/2] fix adc gain forumla --- wfdb/io/_signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wfdb/io/_signal.py b/wfdb/io/_signal.py index 6bfafdb5..5bad70c1 100644 --- a/wfdb/io/_signal.py +++ b/wfdb/io/_signal.py @@ -792,7 +792,7 @@ def calc_adc_gain_baseline(self, ch, minvals, maxvals): if baseline > MAX_I32: # pmin maps to dmin, baseline maps to 2**31 - 1 # pmax will map to a lower value than before - adc_gain = (MAX_I32) - dmin / abs(pmin) + adc_gain = (MAX_I32 - dmin) / abs(pmin) baseline = MAX_I32 # This may happen if pmin > 0 elif baseline < MIN_I32: From 8c005b5c3c50f4d46138deee3f00ebfb5cf45dbf Mon Sep 17 00:00:00 2001 From: Brian Gow Date: Tue, 6 Jan 2026 13:38:14 -0500 Subject: [PATCH 2/2] add gain tests for baseline outside boundary --- tests/test_record.py | 100 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/tests/test_record.py b/tests/test_record.py index fff8ef44..907a929b 100644 --- a/tests/test_record.py +++ b/tests/test_record.py @@ -1335,6 +1335,106 @@ def test_physical_conversion(self): atol=(0.05 / gain), ) + def test_adc_gain_max_boundary(self): + """ + Exercise the MAX_I32 clamp path: use a tiny range around a huge negative signal + so the computed baseline would exceed MAX_I32. Verify we hit that branch and + that a write/read round-trip preserves the physical signal within expected + quantization and the formula produces the expected result. + """ + # Tiny range around a large negative value forces baseline > MAX_I32 + base_value = -1e10 + p_signal = np.array( + [ + [base_value], + [base_value + 0.5], + [base_value + 1.0], + [base_value + 1.5], + ] + ) + + # Write the record + wfdb.wrsamp( + "test_negative_signal", + fs=250, + sig_name=["ECG"], + units=["mV"], + p_signal=p_signal, + fmt=["16"], + write_dir=self.temp_path, + ) + + # Read it back + record = wfdb.rdrecord( + os.path.join(self.temp_path, "test_negative_signal"), + physical=True, + ) + + # Round-trip physical signal should match within quantization tolerance + np.testing.assert_allclose( + record.p_signal, + p_signal, + rtol=1e-4, + atol=1e4, # Larger atol for large magnitude values + ) + + # Verify baseline was clamped to MAX_I32 + self.assertEqual(record.baseline[0], 2147483647) # MAX_I32 + + # Confirm the formula is correct + expected_gain = (2147483647 - (-32768)) / abs(base_value) + np.testing.assert_allclose(record.adc_gain[0], expected_gain, rtol=1e-3) + + def test_adc_gain_min_boundary(self): + """ + Exercise the MIN_I32 clamp path: use a tiny range around a huge positive signal + so the computed baseline would drop below MIN_I32. Verify we hit that branch and + that a write/read round-trip preserves the physical signal within expected + quantization and the formula produces the expected result.. + """ + # Tiny range around a large positive value forces baseline < MIN_I32 + base_value = 1e10 + p_signal = np.array( + [ + [base_value], + [base_value + 0.5], + [base_value + 1.0], + [base_value + 1.5], + ] + ) + + # Write the record + wfdb.wrsamp( + "test_positive_signal", + fs=250, + sig_name=["ECG"], + units=["mV"], + p_signal=p_signal, + fmt=["16"], + write_dir=self.temp_path, + ) + + # Read it back + record = wfdb.rdrecord( + os.path.join(self.temp_path, "test_positive_signal"), + physical=True, + ) + + # Round-trip physical signal should match within quantization tolerance + np.testing.assert_allclose( + record.p_signal, + p_signal, + rtol=1e-4, + atol=1e4, + ) + + # Verify baseline was clamped to MIN_I32 + self.assertEqual(record.baseline[0], -2147483648) + + # Confirm the formula is correct + expected_gain = (32767 - (-2147483648)) / (base_value + 1.5) + np.testing.assert_allclose(record.adc_gain[0], expected_gain, rtol=1e-3) + @classmethod def setUpClass(cls): cls.temp_directory = tempfile.TemporaryDirectory()