From 4370a52b5b1c42adf18c464721cb6b5121d83548 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Mon, 2 Nov 2026 09:57:46 -0500 Subject: [PATCH] Accept pathlib.Path record names in rdann rdann built the annotation file name with record_name + "." + extension, which fails for a pathlib.Path. Convert record_name with os.fspath at the start, as rdrecord, rdsamp and rdheader already accept path-like objects. --- changelog.d/rdann-path.rst | 2 ++ src/wfdb/io/annotation.py | 3 ++- tests/test_annotation.py | 10 ++++++++++ tests/test_archive.py | 16 ++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 changelog.d/rdann-path.rst diff --git a/changelog.d/rdann-path.rst b/changelog.d/rdann-path.rst new file mode 100644 index 00000000..2bb39d3c --- /dev/null +++ b/changelog.d/rdann-path.rst @@ -0,0 +1,2 @@ +**Accept pathlib.Path record names in rdann** + ``rdann`` now accepts a ``pathlib.Path`` (or other path-like object) as ``record_name``, as ``rdrecord``, ``rdsamp`` and ``rdheader`` already do. It used to fail with ``TypeError: unsupported operand type(s) for +``. diff --git a/src/wfdb/io/annotation.py b/src/wfdb/io/annotation.py index 5914785c..a6b94df8 100644 --- a/src/wfdb/io/annotation.py +++ b/src/wfdb/io/annotation.py @@ -1892,7 +1892,7 @@ def rdann( Parameters ---------- - record_name : str + record_name : str or os.PathLike The record name of the WFDB annotation file. ie. for file '100.atr', record_name='100'. The path to the file can be a cloud URL. To read the annotation file from a local .wfdb archive, pass the path of @@ -1942,6 +1942,7 @@ def rdann( >>> ann = wfdb.rdann('100.wfdb', 'atr') """ + record_name = os.fspath(record_name) is_archive = is_archive_path(record_name) if is_archive: record._check_archive_read_args(record_name, pn_dir) diff --git a/tests/test_annotation.py b/tests/test_annotation.py index 37a9a8b6..ef0f55d7 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -1,4 +1,5 @@ import os +import pathlib import re import numpy as np @@ -357,3 +358,12 @@ def test_contained_labels_from_symbols(): ] assert labels.loc[1, "symbol"] == "N" assert labels["n_occurrences"].sum() == len(ann.sample) + + +def test_rdann_path(): + """rdann accepts a pathlib.Path as the record name""" + expected = wfdb.rdann("sample-data/100", "atr") + annotation = wfdb.rdann(pathlib.Path("sample-data/100"), "atr") + assert annotation.record_name == "100" + np.testing.assert_array_equal(annotation.sample, expected.sample) + assert annotation.symbol == expected.symbol diff --git a/tests/test_archive.py b/tests/test_archive.py index fc4cade6..a8ad081a 100644 --- a/tests/test_archive.py +++ b/tests/test_archive.py @@ -704,6 +704,22 @@ def test_rdann_archive(tmp_path): assert ann.aux_note == expected.aux_note +def test_rdann_archive_path(tmp_path): + """rdann accepts a pathlib.Path to an archive.""" + archive_path = tmp_path / "100.wfdb" + create_archive( + record_files("sample-data/100") + ["sample-data/100.atr"], + os.fspath(archive_path), + ) + + ann = wfdb.rdann(archive_path, "atr") + + assert ann.record_name == "100" + np.testing.assert_array_equal( + ann.sample, wfdb.rdann("sample-data/100", "atr").sample + ) + + def test_rdann_archive_header_named_differently(tmp_path): """Annotation files are named after the record's header, not the archive.""" archive_path = os.fspath(tmp_path / "renamed.wfdb")