From 2b704ed8681986ce435f926b452d2fc24812aa2a Mon Sep 17 00:00:00 2001 From: Divya Date: Sun, 1 Nov 2026 09:00:43 +0530 Subject: [PATCH 1/3] Fix wrsamp with missing digital signal --- docs/changes.rst | 4 ++++ src/wfdb/io/record.py | 3 +++ tests/test_record.py | 14 ++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index db184f8d..aa5f84a3 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -8,6 +8,10 @@ This page lists recent changes in the `wfdb` package (since version 4.0.0) that Unreleased ---------- +**Fix writing records with physical signals** + ``Record.wrsamp`` now converts ``p_signal`` to digital samples when ``d_signal`` is + not present, allowing physical-only records to be written. + **Drop support for Python 3.9 and 3.10** The minimum supported Python version is now 3.11. The package is now also tested with Python 3.14 and 3.15. diff --git a/src/wfdb/io/record.py b/src/wfdb/io/record.py index 52a879b3..854fbad0 100644 --- a/src/wfdb/io/record.py +++ b/src/wfdb/io/record.py @@ -894,6 +894,9 @@ def wrsamp(self, expanded=False, write_dir="", wfdb_archive=None): N/A """ + if not expanded and self.d_signal is None and self.p_signal is not None: + self.d_signal = self.adc() + # Update the checksum field (except for channels that did not have # a checksum to begin with, or where the checksum was already # valid.) diff --git a/tests/test_record.py b/tests/test_record.py index 00fafa09..4725120b 100644 --- a/tests/test_record.py +++ b/tests/test_record.py @@ -600,6 +600,20 @@ def test_2b(self, source, temp_path): assert record.__eq__(record_named) assert record.__eq__(record_write) + def test_wrsamp_physical_only_record(self, temp_path): + record = wfdb.rdrecord("sample-data/100", sampto=1000) + expected_d_signal = record.adc() + + assert record.d_signal is None + + record.wrsamp(write_dir=temp_path) + record_write = wfdb.rdrecord( + os.path.join(temp_path, "100"), + physical=False, + ) + + np.testing.assert_array_equal(record_write.d_signal, expected_d_signal) + def test_2c(self, temp_path): """Format 212, entire signal, physical, odd sampled record. From 4b17cf70620b508b9999107a07d05e79dc49e16b Mon Sep 17 00:00:00 2001 From: Divya Date: Sun, 1 Nov 2026 22:22:36 +0530 Subject: [PATCH 2/3] Fix wrsamp for physical-only records --- changelog.d/wrsamp-physical.rst | 1 + src/wfdb/io/record.py | 49 ++++++++++++++++++++------------- tests/test_record.py | 45 +++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 changelog.d/wrsamp-physical.rst diff --git a/changelog.d/wrsamp-physical.rst b/changelog.d/wrsamp-physical.rst new file mode 100644 index 00000000..4439fe01 --- /dev/null +++ b/changelog.d/wrsamp-physical.rst @@ -0,0 +1 @@ +Fix writing physical-only and expanded physical signals without retaining converted digital samples on the record. diff --git a/src/wfdb/io/record.py b/src/wfdb/io/record.py index 854fbad0..216dbaea 100644 --- a/src/wfdb/io/record.py +++ b/src/wfdb/io/record.py @@ -894,29 +894,40 @@ def wrsamp(self, expanded=False, write_dir="", wfdb_archive=None): N/A """ + converted = None if not expanded and self.d_signal is None and self.p_signal is not None: self.d_signal = self.adc() + converted = "d_signal" + elif expanded and self.e_d_signal is None and self.e_p_signal is not None: + self.e_d_signal = self.adc(expanded=True) + converted = "e_d_signal" - # Update the checksum field (except for channels that did not have - # a checksum to begin with, or where the checksum was already - # valid.) - if self.checksum is not None: - checksums = self.calc_checksum(expanded=expanded) - for ch, old_val in enumerate(self.checksum): - if old_val is None or (checksums[ch] - old_val) % 65536 == 0: - checksums[ch] = old_val - self.checksum = checksums - - with archive_for_writing(wfdb_archive, write_dir) as archive: - # Perform field validity and cohesion checks, and write the - # header file. - self.wrheader(write_dir=write_dir, expanded=expanded, wfdb_archive=archive) - if self.n_sig > 0: - # Perform signal validity and cohesion checks, and write the - # associated dat files. - self.wr_dats( - expanded=expanded, write_dir=write_dir, wfdb_archive=archive + try: + # Update the checksum field (except for channels that did not have + # a checksum to begin with, or where the checksum was already + # valid.) + if self.checksum is not None: + checksums = self.calc_checksum(expanded=expanded) + for ch, old_val in enumerate(self.checksum): + if old_val is None or (checksums[ch] - old_val) % 65536 == 0: + checksums[ch] = old_val + self.checksum = checksums + + with archive_for_writing(wfdb_archive, write_dir) as archive: + # Perform field validity and cohesion checks, and write the + # header file. + self.wrheader( + write_dir=write_dir, expanded=expanded, wfdb_archive=archive ) + if self.n_sig > 0: + # Perform signal validity and cohesion checks, and write the + # associated dat files. + self.wr_dats( + expanded=expanded, write_dir=write_dir, wfdb_archive=archive + ) + finally: + if converted: + setattr(self, converted, None) def _arrange_fields(self, channels, sampfrom, smooth_frames): """Arrange/edit object fields to reflect user channel and/or signal range input. diff --git a/tests/test_record.py b/tests/test_record.py index 4725120b..3228305e 100644 --- a/tests/test_record.py +++ b/tests/test_record.py @@ -602,7 +602,9 @@ def test_2b(self, source, temp_path): def test_wrsamp_physical_only_record(self, temp_path): record = wfdb.rdrecord("sample-data/100", sampto=1000) - expected_d_signal = record.adc() + expected_d_signal = wfdb.rdrecord( + "sample-data/100", sampto=1000, physical=False + ).d_signal assert record.d_signal is None @@ -613,6 +615,47 @@ def test_wrsamp_physical_only_record(self, temp_path): ) np.testing.assert_array_equal(record_write.d_signal, expected_d_signal) + assert record.d_signal is None + + def test_wrsamp_physical_only_record_second_write(self, temp_path): + record = wfdb.rdrecord("sample-data/100", sampto=1000) + record.wrsamp(write_dir=temp_path) + + assert record.d_signal is None + + record.p_signal[:] = 0 + record.wrsamp(write_dir=temp_path) + record_write = wfdb.rdrecord( + os.path.join(temp_path, "100"), + physical=False, + ) + + expected_d_signal = np.tile(record.baseline, (record.sig_len, 1)) + np.testing.assert_array_equal(record_write.d_signal, expected_d_signal) + assert record.d_signal is None + + def test_wrsamp_expanded_physical_only_record(self, temp_path): + record = wfdb.rdrecord( + "sample-data/test01_00s_frame", smooth_frames=False + ) + expected_e_d_signal = wfdb.rdrecord( + "sample-data/test01_00s_frame", + smooth_frames=False, + physical=False, + ).e_d_signal + + assert record.e_d_signal is None + + record.wrsamp(write_dir=temp_path, expanded=True) + record_write = wfdb.rdrecord( + os.path.join(temp_path, "test01_00s_frame"), + smooth_frames=False, + physical=False, + ) + + for written, expected in zip(record_write.e_d_signal, expected_e_d_signal): + np.testing.assert_array_equal(written, expected) + assert record.e_d_signal is None def test_2c(self, temp_path): """Format 212, entire signal, physical, odd sampled record. From a046259a3d073823454b253bd1b8d59c72b20164 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Sun, 1 Nov 2026 16:28:59 -0500 Subject: [PATCH 3/3] Format test with Ruff --- tests/test_record.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_record.py b/tests/test_record.py index 76e5479f..d66f265c 100644 --- a/tests/test_record.py +++ b/tests/test_record.py @@ -635,9 +635,7 @@ def test_wrsamp_physical_only_record_second_write(self, temp_path): assert record.d_signal is None def test_wrsamp_expanded_physical_only_record(self, temp_path): - record = wfdb.rdrecord( - "sample-data/test01_00s_frame", smooth_frames=False - ) + record = wfdb.rdrecord("sample-data/test01_00s_frame", smooth_frames=False) expected_e_d_signal = wfdb.rdrecord( "sample-data/test01_00s_frame", smooth_frames=False,