Skip to content

Commit af4958e

Browse files
Keep invalid samples invalid in smooth_frames
smooth_frames averaged the invalid sample value with the other samples in a frame, which gave a large valid-looking value instead of an invalid one (NaN in physical units). As in the WFDB library's meansamp(), a frame that contains an invalid sample now gives the invalid value.
1 parent 74a3f56 commit af4958e

3 files changed

Lines changed: 29 additions & 5 deletions

File tree

‎docs/changes.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ Unreleased
1717
**Add timeouts to requests for remote files**
1818
Requests for files on PhysioNet (and other web servers) now time out if the server does not respond (10 seconds to connect, and 60 seconds to wait for data), instead of waiting indefinitely. A ``requests.exceptions.Timeout`` (``ConnectTimeout`` or ``ReadTimeout``) is raised in this case.
1919

20+
**Fix smoothing of frames with invalid samples**
21+
When a signal with more than one sample per frame is read with ``smooth_frames=True`` (the default), a frame that contains an invalid sample now gives an invalid value (NaN in physical units), as in the WFDB library. The invalid sample value used to be averaged with the other samples, which gave a large but valid-looking value.
22+
2023
**Build with uv_build**
2124
The package is now built with ``uv_build`` instead of ``hatchling``. The version number is specified in ``pyproject.toml`` only, and ``wfdb.__version__`` is read from the installed package metadata (it is ``"unknown"`` if the package is run from a source tree without being installed).
2225

‎src/wfdb/io/_signal.py‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,15 +1060,20 @@ def smooth_frames(self, sigtype="physical"):
10601060
if spf[ch] == 1:
10611061
signal[:, ch] = expanded_signal[ch]
10621062
else:
1063+
# As in the WFDB library, a frame with an invalid sample gives
1064+
# an invalid value. Physical samples are already NaN.
1065+
invalid = None
1066+
if sigtype == "digital" and self.fmt and self.fmt[ch]:
1067+
invalid = _digi_nan(self.fmt[ch])
10631068
frames = expanded_signal[ch].reshape(-1, spf[ch])
10641069
for chunk_start in range(0, sig_len, CHUNK_SIZE):
10651070
chunk_end = chunk_start + CHUNK_SIZE
1066-
signal_sum = np.sum(
1067-
frames[chunk_start:chunk_end, :],
1068-
axis=1,
1069-
dtype=intermediate_dtype,
1070-
)
1071+
chunk = frames[chunk_start:chunk_end, :]
1072+
signal_sum = np.sum(chunk, axis=1, dtype=intermediate_dtype)
10711073
signal[chunk_start:chunk_end, ch] = signal_sum / spf[ch]
1074+
if invalid is not None:
1075+
has_invalid = np.any(chunk == invalid, axis=1)
1076+
signal[chunk_start:chunk_end, ch][has_invalid] = invalid
10721077

10731078
return signal
10741079

‎tests/test_record.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,22 @@ def test_write_smoothed(self, temp_path):
857857
)
858858
np.testing.assert_array_equal(record.d_signal, record2.d_signal)
859859

860+
def test_smooth_frames_invalid_sample(self, temp_path):
861+
"""A frame with an invalid sample is invalid after smoothing."""
862+
# Two samples per frame. -32768 is the invalid sample value for fmt 16.
863+
raw = np.array([2, 4, -32768, 100, 6, 8], dtype="<i2")
864+
raw.tofile(os.path.join(temp_path, "invalid.dat"))
865+
with open(os.path.join(temp_path, "invalid.hea"), "w") as f:
866+
f.write("invalid 1 100 3\n")
867+
f.write("invalid.dat 16x2 200/mV 16 0 0 0 0 sig\n")
868+
record_name = os.path.join(temp_path, "invalid")
869+
870+
record = wfdb.rdrecord(record_name, physical=False)
871+
np.testing.assert_array_equal(record.d_signal[:, 0], [3, -32768, 7])
872+
873+
record = wfdb.rdrecord(record_name)
874+
np.testing.assert_allclose(record.p_signal[:, 0], [0.015, np.nan, 0.035])
875+
860876
def test_to_dataframe(self):
861877
record = wfdb.rdrecord("sample-data/test01_00s")
862878
df = record.to_dataframe()

0 commit comments

Comments
 (0)