diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 27cadffa..bcb91389 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -21,3 +21,5 @@ jobs: run: | uv run ruff format --check --diff . uv run ruff check . + - name: Check changelog entries + run: uv run towncrier build --draft > /dev/null diff --git a/AGENTS.md b/AGENTS.md index cb6fae2c..930fa193 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,7 @@ WFDB-Python is a library for reading, writing and processing physiologic signals - `src/wfdb/`: the package (`io`, `plot`, `processing`) - `tests/`: pytest tests, which read sample files from `sample-data/` relative to the repository root, so run them from there - `docs/`: Sphinx documentation, including the changelog in `docs/changes.rst` +- `changelog.d/`: changelog entries for the next release, one file per change ## Commands @@ -27,6 +28,6 @@ uv run --only-group docs make -C docs html SPHINXOPTS="-W --keep-going" - Docstrings use the numpy style. Start the docstring directly after the opening quotes with a summary that fits on one line, followed by a blank line and the details (if any). - The docs build treats warnings as errors, so check it after changing a docstring. - Tests are plain pytest functions or classes (no `unittest`). Tests that need PhysioNet are marked `network` and are skipped if PhysioNet cannot be reached. Where possible, a test has a `local` variant and a `physionet` variant through the `source` fixture. -- Add a changelog entry to `docs/changes.rst` for user-visible changes. +- Add a changelog entry for user-visible changes as a new file in `changelog.d/` (see `changelog.d/README.rst`), not in `docs/changes.rst`. - The version is set in `pyproject.toml` only. Do not bump it as part of a feature or fix. - Commits that only reformat code are listed in `.git-blame-ignore-revs`. diff --git a/DEVELOPING.md b/DEVELOPING.md index 6a1e11ab..57238b38 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -37,7 +37,7 @@ Ruff is pinned to an exact version because new releases can change formatting an 1. Bump the version in [pyproject.toml](./pyproject.toml). -2. Add a summary of the changes to [the changelog](https://github.com/MIT-LCP/wfdb-python/blob/main/docs/changes.rst). You may also need to update [the documentation](https://github.com/MIT-LCP/wfdb-python/tree/main/docs). For example, if function arguments have been updated, this change will need to be captured. Open a pull request to merge these changes to the main branch. +2. Run `uv run towncrier build --date "Mon YYYY"` to collect the entries in [changelog.d](./changelog.d) into [the changelog](https://github.com/MIT-LCP/wfdb-python/blob/main/docs/changes.rst) (this also deletes the entries), and check the result. The version is taken from `pyproject.toml`, so bump it first. You may also need to update [the documentation](https://github.com/MIT-LCP/wfdb-python/tree/main/docs). For example, if function arguments have been updated, this change will need to be captured. Open a pull request to merge these changes to the main branch. 3. After the pull requests above have been merged, go to https://github.com/MIT-LCP/wfdb-python/releases and click "Draft new release" to create a new tag/release of the package. Set the tag to the new version number and draft the release notes (or click "Generate release notes"!). diff --git a/changelog.d/README.rst b/changelog.d/README.rst new file mode 100644 index 00000000..b1e78f2a --- /dev/null +++ b/changelog.d/README.rst @@ -0,0 +1,18 @@ +Changelog entries +================= + +Each user-visible change gets its own file in this directory, instead of an +entry in ``docs/changes.rst``. Separate files avoid merge conflicts between +pull requests. At release time, ``towncrier build`` collects them into +``docs/changes.rst`` and deletes them. + +Name the file after the change, with an ``.rst`` extension and no other dots +(for example ``rr2ann-fixes.rst``). Write the entry as a bold heading followed +by an indented paragraph, as in ``docs/changes.rst``:: + + **Fix rr2ann with times** + ``rr2ann`` with ``as_time=True`` now rounds times to the nearest sample. + +To preview the next release's changelog, run:: + + uv run towncrier build --draft diff --git a/changelog.d/ann2rr-beats-only.rst b/changelog.d/ann2rr-beats-only.rst new file mode 100644 index 00000000..b64ade3b --- /dev/null +++ b/changelog.d/ann2rr-beats-only.rst @@ -0,0 +1,2 @@ +**Use only beat annotations in ann2rr** + ``ann2rr`` now computes intervals between beat (QRS) annotations only, as the WFDB ``ann2rr`` program does, so rhythm changes, comments and other non-beat annotations no longer split an RR interval in two. The first interval is still measured from the start of the record. To include all annotations as before, pass ``all_annotations=True`` (the equivalent of ``ann2rr -A``). diff --git a/changelog.d/comparitor-double-match.rst b/changelog.d/comparitor-double-match.rst new file mode 100644 index 00000000..a3ed783d --- /dev/null +++ b/changelog.d/comparitor-double-match.rst @@ -0,0 +1,2 @@ +**Fix test annotations matched twice in compare_annotations** + ``compare_annotations`` (``Comparitor.compare``) could match one test annotation to two reference annotations, which gave a negative number of false positives and a positive predictivity above 1. Each test annotation is now matched at most once. Results are unchanged when no annotation was matched twice. diff --git a/changelog.d/compute-hr-check-order.rst b/changelog.d/compute-hr-check-order.rst new file mode 100644 index 00000000..71bf6e2a --- /dev/null +++ b/changelog.d/compute-hr-check-order.rst @@ -0,0 +1,2 @@ +**Check that QRS indices are increasing in compute_hr** + ``compute_hr`` now raises a ``ValueError`` if ``qrs_inds`` is not strictly increasing. Repeated beats used to give a ``ZeroDivisionError`` (or ``inf`` for NumPy arrays), and beats out of order gave a negative heart rate. diff --git a/changelog.d/compute-hr-last-interval.rst b/changelog.d/compute-hr-last-interval.rst new file mode 100644 index 00000000..7a858672 --- /dev/null +++ b/changelog.d/compute-hr-last-interval.rst @@ -0,0 +1,2 @@ +**Use the last RR interval in compute_hr** + ``compute_hr`` now uses the interval between the last two beats for the heart rate after the last beat, instead of repeating the previous rate. With exactly two beats it now returns their rate after the second beat instead of only NaN. diff --git a/changelog.d/correct-peaks-early.rst b/changelog.d/correct-peaks-early.rst new file mode 100644 index 00000000..1a8f3edb --- /dev/null +++ b/changelog.d/correct-peaks-early.rst @@ -0,0 +1,2 @@ +**Fix correct_peaks for peaks near the start of the signal** + ``correct_peaks`` no longer moves peaks within ``search_radius`` samples of the start of the signal to the wrong place, often before the start (for example ``-6`` instead of ``0``). The offset for these peaks was subtracted instead of added. diff --git a/changelog.d/correct-peaks-window.rst b/changelog.d/correct-peaks-window.rst new file mode 100644 index 00000000..5a4588c5 --- /dev/null +++ b/changelog.d/correct-peaks-window.rst @@ -0,0 +1,2 @@ +**Fix search window in correct_peaks** + ``correct_peaks`` now searches ``search_radius`` samples on both sides of each peak, including the last sample of the signal. It used to stop one sample short on the right, so a peak exactly ``search_radius`` samples after the original index, or on the last sample, could not be found. diff --git a/changelog.d/csv-to-wfdb-parameters.rst b/changelog.d/csv-to-wfdb-parameters.rst new file mode 100644 index 00000000..4705f1d4 --- /dev/null +++ b/changelog.d/csv-to-wfdb-parameters.rst @@ -0,0 +1,2 @@ +**Fix parameter handling in csv_to_wfdb** + ``csv_to_wfdb`` now builds the same ``Record`` whether it writes files or returns the record (``record_only=True``), so all header parameters (such as ``dat_file_name``, ``counter_freq``, ``base_counter``, ``skew``, ``byte_offset``, ``adc_res`` and ``adc_zero``) are written instead of being ignored. Single values of any numeric type (including NumPy scalars and floats) are used for all channels, and an explicit ``0`` is no longer replaced by the default. The initial value and checksum are computed from the digital samples, and the default ADC resolution now follows ``fmt`` in both modes. The ``samps_per_frame``, ``init_value`` and ``checksum`` arguments are deprecated and ignored. The parameter descriptions in the docstring have been corrected. diff --git a/changelog.d/csv2ann-record-name.rst b/changelog.d/csv2ann-record-name.rst new file mode 100644 index 00000000..5336d8ba --- /dev/null +++ b/changelog.d/csv2ann-record-name.rst @@ -0,0 +1,2 @@ +**Fix record name and add write directory in csv2ann** + ``csv2ann`` now takes the record name from the CSV file name without its directory and extension, as ``csv_to_wfdb`` does. Paths with a directory (such as ``data/x.csv``) used to fail when writing, and ``./x.csv`` gave an empty name. The annotation file is written to the new ``write_dir`` argument, which defaults to the current directory. ``csv2ann`` is now included in the API documentation. diff --git a/changelog.d/dat-packing-memory.rst b/changelog.d/dat-packing-memory.rst new file mode 100644 index 00000000..c8ffd527 --- /dev/null +++ b/changelog.d/dat-packing-memory.rst @@ -0,0 +1,2 @@ +**Reduce memory use when writing format 16, 24 and 32 signals** + ``wrsamp`` and ``wr_dat_file`` now convert signals in formats 16, 24 and 32 to bytes directly, instead of splitting every sample into separate byte arrays. For a 4-hour, 8-channel record at 500 Hz, the peak memory used to pack a format 16 signal drops from about 2 GB to 110 MB, and packing is about 13 times faster. The written files are unchanged. diff --git a/changelog.d/drop-old-python.rst b/changelog.d/drop-old-python.rst new file mode 100644 index 00000000..7de7dfd6 --- /dev/null +++ b/changelog.d/drop-old-python.rst @@ -0,0 +1,2 @@ +**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/changelog.d/find-local-peaks-window.rst b/changelog.d/find-local-peaks-window.rst new file mode 100644 index 00000000..4ae8680c --- /dev/null +++ b/changelog.d/find-local-peaks-window.rst @@ -0,0 +1,2 @@ +**Include the right edge of the window in find_local_peaks** + ``find_local_peaks`` now compares each sample with ``radius`` samples on both sides, as documented, and is about 3 times faster. The sample ``radius`` positions to the right, and the last sample of the signal, used to be left out. ``XQRS`` uses ``find_local_peaks`` to find candidate peaks, so its detections may change slightly. diff --git a/changelog.d/header-units.rst b/changelog.d/header-units.rst new file mode 100644 index 00000000..9c6db612 --- /dev/null +++ b/changelog.d/header-units.rst @@ -0,0 +1,2 @@ +**Read units containing any non-whitespace characters** + Header files with units containing characters such as ``.`` or ``*`` (for example ``a.u.`` or ``uV*s``) are now read correctly. The units used to be cut off at the first such character, and the rest of the signal line (ADC resolution, ADC zero and so on) ended up in the signal name. diff --git a/changelog.d/minimum-dependency-versions.rst b/changelog.d/minimum-dependency-versions.rst new file mode 100644 index 00000000..98d38e9e --- /dev/null +++ b/changelog.d/minimum-dependency-versions.rst @@ -0,0 +1,2 @@ +**Raise minimum versions of dependencies** + The minimum versions of ``soundfile`` (0.12.1), ``matplotlib`` (3.8.0) and ``requests`` (2.31.0) have been raised. The minimum versions of the other dependencies are unchanged. diff --git a/changelog.d/multiseg-variable-layout.rst b/changelog.d/multiseg-variable-layout.rst new file mode 100644 index 00000000..661794e7 --- /dev/null +++ b/changelog.d/multiseg-variable-layout.rst @@ -0,0 +1,2 @@ +**Write variable layout multi-segment records** + ``MultiRecord.wrsamp`` can now write variable layout multi-segment records, such as those read with ``rdrecord(..., m2s=False)``. Previously, writing the layout header failed because its signals have no samples (format ``0``, file name ``~``). diff --git a/changelog.d/remote-flac-requests.rst b/changelog.d/remote-flac-requests.rst new file mode 100644 index 00000000..cc864ea9 --- /dev/null +++ b/changelog.d/remote-flac-requests.rst @@ -0,0 +1,2 @@ +**Read remote FLAC files with fewer requests** + Remote files read in small pieces, such as FLAC signal files on PhysioNet, now use larger requests as reading goes on (up to 8 MB) instead of one request for every 32 kB. Reading a 7.4 MB MIMIC-IV signal file now takes 8 requests instead of 229, which saves several minutes per file. diff --git a/changelog.d/request-timeouts.rst b/changelog.d/request-timeouts.rst new file mode 100644 index 00000000..2c7d6803 --- /dev/null +++ b/changelog.d/request-timeouts.rst @@ -0,0 +1,2 @@ +**Add timeouts to requests for remote files** + 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. diff --git a/changelog.d/resample-leading-nan.rst b/changelog.d/resample-leading-nan.rst new file mode 100644 index 00000000..528c13d3 --- /dev/null +++ b/changelog.d/resample-leading-nan.rst @@ -0,0 +1,2 @@ +**Fix resample_sig for signals starting with NaN** + ``resample_sig`` (and the functions that use it, such as ``resample_singlechan`` and ``resample_multichan``) returned only NaN values if the signal started with NaN. Leading NaN values now take the first valid value before resampling, as trailing ones already took the last. diff --git a/changelog.d/resample-mark-nan.rst b/changelog.d/resample-mark-nan.rst new file mode 100644 index 00000000..c672337e --- /dev/null +++ b/changelog.d/resample-mark-nan.rst @@ -0,0 +1,2 @@ +**Keep missing values missing in resample_sig** + ``resample_sig`` (and ``resample_singlechan`` and ``resample_multichan``) now return NaN for output samples with a NaN input value within half an output sample spacing (or within one input sample, when upsampling). NaN values are still filled before resampling, so the rest of the signal is unaffected, but the filled values are no longer returned as if they were real data. diff --git a/changelog.d/rr2ann-fixes.rst b/changelog.d/rr2ann-fixes.rst new file mode 100644 index 00000000..2cbdadd1 --- /dev/null +++ b/changelog.d/rr2ann-fixes.rst @@ -0,0 +1,2 @@ +**Fix rr2ann with times and with a symbol column** + ``rr2ann`` with ``as_time=True`` now rounds times to the nearest sample instead of truncating them, so (for example) 0.29 seconds at 100 Hz gives sample 29 instead of 28. Intervals in samples with a second column of symbols no longer raise an error, and fractional intervals are summed before rounding. diff --git a/changelog.d/ruff.rst b/changelog.d/ruff.rst new file mode 100644 index 00000000..ae87336c --- /dev/null +++ b/changelog.d/ruff.rst @@ -0,0 +1,2 @@ +**Use Ruff for formatting and linting** + The ``dev`` extra now includes ``ruff`` instead of ``black`` and ``pylint``, and the code is formatted with a line length of 88 (the default). diff --git a/changelog.d/sig-cohesion-speed.rst b/changelog.d/sig-cohesion-speed.rst new file mode 100644 index 00000000..c4a64a03 --- /dev/null +++ b/changelog.d/sig-cohesion-speed.rst @@ -0,0 +1,2 @@ +**Speed up the digital range check when writing records** + ``wrsamp`` now uses NumPy to check that digital samples are within the range of each format, instead of Python's built-in ``min`` and ``max``. Writing a 4-hour, 8-channel record at 500 Hz in format 16 now takes about 0.5 seconds instead of 3. The written files are unchanged. diff --git a/changelog.d/smooth-frames-invalid.rst b/changelog.d/smooth-frames-invalid.rst new file mode 100644 index 00000000..37e1d06d --- /dev/null +++ b/changelog.d/smooth-frames-invalid.rst @@ -0,0 +1,2 @@ +**Fix smoothing of frames with invalid samples** + 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. diff --git a/changelog.d/template.rst.jinja b/changelog.d/template.rst.jinja new file mode 100644 index 00000000..b36d8633 --- /dev/null +++ b/changelog.d/template.rst.jinja @@ -0,0 +1,4 @@ + +{% for section, _ in sections.items() %}{% for category, val in definitions.items() if category in sections[section] %}{% for text, values in sections[section][category].items() %}{{ text | replace("\n ", "\n ") }} + +{% endfor %}{% endfor %}{% endfor %} diff --git a/changelog.d/uv-build.rst b/changelog.d/uv-build.rst new file mode 100644 index 00000000..43626b00 --- /dev/null +++ b/changelog.d/uv-build.rst @@ -0,0 +1,2 @@ +**Build with uv_build** + 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). diff --git a/changelog.d/wfdb-archive-files.rst b/changelog.d/wfdb-archive-files.rst new file mode 100644 index 00000000..e4e9b42f --- /dev/null +++ b/changelog.d/wfdb-archive-files.rst @@ -0,0 +1,2 @@ +**Read and write .wfdb archive files** + A record can be stored in a single ``.wfdb`` archive file (a ZIP file containing the header and signal files). ``rdrecord``, ``rdsamp``, ``rdheader`` and ``rdann`` read a record or its annotations from an archive when given the path of the archive, and ``wrsamp`` writes a record into an archive with the new ``wfdb_archive`` argument. Multi-segment records are supported, with all their segments stored in the same archive. diff --git a/changelog.d/wrann-aux-note-check.rst b/changelog.d/wrann-aux-note-check.rst new file mode 100644 index 00000000..f0d4fb15 --- /dev/null +++ b/changelog.d/wrann-aux-note-check.rst @@ -0,0 +1,2 @@ +**Reject aux notes that can't be written in wrann** + ``wrann`` now raises a ``ValueError`` if an ``aux_note`` is longer than 255 characters or contains characters outside Latin-1, instead of writing a corrupt annotation file. A longer note used to be read back as many extra annotations, and other characters were silently replaced. diff --git a/changelog.d/wrann-aux-note-none.rst b/changelog.d/wrann-aux-note-none.rst new file mode 100644 index 00000000..15594291 --- /dev/null +++ b/changelog.d/wrann-aux-note-none.rst @@ -0,0 +1,2 @@ +**Allow None for annotations without notes in wrann** + ``wrann`` now accepts ``None`` in ``aux_note`` for annotations without a note, as the docstring says, and writes it the same way as an empty string. It used to raise a ``TypeError``. diff --git a/changelog.d/xqrs-backsearch.rst b/changelog.d/xqrs-backsearch.rst new file mode 100644 index 00000000..305217e7 --- /dev/null +++ b/changelog.d/xqrs-backsearch.rst @@ -0,0 +1,2 @@ +**Speed up XQRS backsearch** + ``XQRS`` no longer rechecks peaks that an earlier backsearch already rejected. Long stretches without QRS complexes (for example a disconnected lead) made detection take quadratic time, up to hours on long records. The detected QRS locations are unchanged. diff --git a/docs/changes.rst b/docs/changes.rst index 26dd3411..29ba2e79 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,89 +5,7 @@ This page lists recent changes in the `wfdb` package (since version 4.0.0) that .. _development repository: https://github.com/MIT-LCP/wfdb-python -Unreleased ----------- - -**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. - -**Raise minimum versions of dependencies** - The minimum versions of ``soundfile`` (0.12.1), ``matplotlib`` (3.8.0) and ``requests`` (2.31.0) have been raised. The minimum versions of the other dependencies are unchanged. - -**Read units containing any non-whitespace characters** - Header files with units containing characters such as ``.`` or ``*`` (for example ``a.u.`` or ``uV*s``) are now read correctly. The units used to be cut off at the first such character, and the rest of the signal line (ADC resolution, ADC zero and so on) ended up in the signal name. - -**Add timeouts to requests for remote files** - 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. - -**Read remote FLAC files with fewer requests** - Remote files read in small pieces, such as FLAC signal files on PhysioNet, now use larger requests as reading goes on (up to 8 MB) instead of one request for every 32 kB. Reading a 7.4 MB MIMIC-IV signal file now takes 8 requests instead of 229, which saves several minutes per file. - -**Fix smoothing of frames with invalid samples** - 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. - -**Build with uv_build** - 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). - -**Speed up XQRS backsearch** - ``XQRS`` no longer rechecks peaks that an earlier backsearch already rejected. Long stretches without QRS complexes (for example a disconnected lead) made detection take quadratic time, up to hours on long records. The detected QRS locations are unchanged. - -**Fix test annotations matched twice in compare_annotations** - ``compare_annotations`` (``Comparitor.compare``) could match one test annotation to two reference annotations, which gave a negative number of false positives and a positive predictivity above 1. Each test annotation is now matched at most once. Results are unchanged when no annotation was matched twice. - -**Fix record name and add write directory in csv2ann** - ``csv2ann`` now takes the record name from the CSV file name without its directory and extension, as ``csv_to_wfdb`` does. Paths with a directory (such as ``data/x.csv``) used to fail when writing, and ``./x.csv`` gave an empty name. The annotation file is written to the new ``write_dir`` argument, which defaults to the current directory. ``csv2ann`` is now included in the API documentation. - -**Speed up the digital range check when writing records** - ``wrsamp`` now uses NumPy to check that digital samples are within the range of each format, instead of Python's built-in ``min`` and ``max``. Writing a 4-hour, 8-channel record at 500 Hz in format 16 now takes about 0.5 seconds instead of 3. The written files are unchanged. - -**Fix parameter handling in csv_to_wfdb** - ``csv_to_wfdb`` now builds the same ``Record`` whether it writes files or returns the record (``record_only=True``), so all header parameters (such as ``dat_file_name``, ``counter_freq``, ``base_counter``, ``skew``, ``byte_offset``, ``adc_res`` and ``adc_zero``) are written instead of being ignored. Single values of any numeric type (including NumPy scalars and floats) are used for all channels, and an explicit ``0`` is no longer replaced by the default. The initial value and checksum are computed from the digital samples, and the default ADC resolution now follows ``fmt`` in both modes. The ``samps_per_frame``, ``init_value`` and ``checksum`` arguments are deprecated and ignored. The parameter descriptions in the docstring have been corrected. - -**Reduce memory use when writing format 16, 24 and 32 signals** - ``wrsamp`` and ``wr_dat_file`` now convert signals in formats 16, 24 and 32 to bytes directly, instead of splitting every sample into separate byte arrays. For a 4-hour, 8-channel record at 500 Hz, the peak memory used to pack a format 16 signal drops from about 2 GB to 110 MB, and packing is about 13 times faster. The written files are unchanged. - -**Use the last RR interval in compute_hr** - ``compute_hr`` now uses the interval between the last two beats for the heart rate after the last beat, instead of repeating the previous rate. With exactly two beats it now returns their rate after the second beat instead of only NaN. - -**Check that QRS indices are increasing in compute_hr** - ``compute_hr`` now raises a ``ValueError`` if ``qrs_inds`` is not strictly increasing. Repeated beats used to give a ``ZeroDivisionError`` (or ``inf`` for NumPy arrays), and beats out of order gave a negative heart rate. - -**Fix rr2ann with times and with a symbol column** - ``rr2ann`` with ``as_time=True`` now rounds times to the nearest sample instead of truncating them, so (for example) 0.29 seconds at 100 Hz gives sample 29 instead of 28. Intervals in samples with a second column of symbols no longer raise an error, and fractional intervals are summed before rounding. - -**Use Ruff for formatting and linting** - The ``dev`` extra now includes ``ruff`` instead of ``black`` and ``pylint``, and the code is formatted with a line length of 88 (the default). - -**Fix search window in correct_peaks** - ``correct_peaks`` now searches ``search_radius`` samples on both sides of each peak, including the last sample of the signal. It used to stop one sample short on the right, so a peak exactly ``search_radius`` samples after the original index, or on the last sample, could not be found. - -**Fix correct_peaks for peaks near the start of the signal** - ``correct_peaks`` no longer moves peaks within ``search_radius`` samples of the start of the signal to the wrong place, often before the start (for example ``-6`` instead of ``0``). The offset for these peaks was subtracted instead of added. - -**Fix resample_sig for signals starting with NaN** - ``resample_sig`` (and the functions that use it, such as ``resample_singlechan`` and ``resample_multichan``) returned only NaN values if the signal started with NaN. Leading NaN values now take the first valid value before resampling, as trailing ones already took the last. - -**Keep missing values missing in resample_sig** - ``resample_sig`` (and ``resample_singlechan`` and ``resample_multichan``) now return NaN for output samples with a NaN input value within half an output sample spacing (or within one input sample, when upsampling). NaN values are still filled before resampling, so the rest of the signal is unaffected, but the filled values are no longer returned as if they were real data. - -**Reject aux notes that can't be written in wrann** - ``wrann`` now raises a ``ValueError`` if an ``aux_note`` is longer than 255 characters or contains characters outside Latin-1, instead of writing a corrupt annotation file. A longer note used to be read back as many extra annotations, and other characters were silently replaced. - -**Allow None for annotations without notes in wrann** - ``wrann`` now accepts ``None`` in ``aux_note`` for annotations without a note, as the docstring says, and writes it the same way as an empty string. It used to raise a ``TypeError``. - -**Read and write .wfdb archive files** - A record can be stored in a single ``.wfdb`` archive file (a ZIP file containing the header and signal files). ``rdrecord``, ``rdsamp``, ``rdheader`` and ``rdann`` read a record or its annotations from an archive when given the path of the archive, and ``wrsamp`` writes a record into an archive with the new ``wfdb_archive`` argument. Multi-segment records are supported, with all their segments stored in the same archive. - -**Write variable layout multi-segment records** - ``MultiRecord.wrsamp`` can now write variable layout multi-segment records, such as those read with ``rdrecord(..., m2s=False)``. Previously, writing the layout header failed because its signals have no samples (format ``0``, file name ``~``). - -**Use only beat annotations in ann2rr** - ``ann2rr`` now computes intervals between beat (QRS) annotations only, as the WFDB ``ann2rr`` program does, so rhythm changes, comments and other non-beat annotations no longer split an RR interval in two. The first interval is still measured from the start of the record. To include all annotations as before, pass ``all_annotations=True`` (the equivalent of ``ann2rr -A``). - -**Include the right edge of the window in find_local_peaks** - ``find_local_peaks`` now compares each sample with ``radius`` samples on both sides, as documented, and is about 3 times faster. The sample ``radius`` positions to the right, and the last sample of the signal, used to be left out. ``XQRS`` uses ``find_local_peaks`` to find candidate peaks, so its detections may change slightly. +.. towncrier release notes start Version 4.3.1 (Feb 2026) ----------------------------- diff --git a/pyproject.toml b/pyproject.toml index e2d1f7fc..6a098462 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dev = [ "pytest >= 8.0.0", "pytest-xdist >= 3.5.0", "ruff == 0.17.3", + "towncrier == 26.9.0", ] # Heavy dependencies are mocked in docs/conf.py, so the project itself is not needed docs = [ @@ -40,6 +41,23 @@ docs = [ "sphinx==9.0.4", ] +[tool.towncrier] +directory = "changelog.d" +filename = "docs/changes.rst" +template = "changelog.d/template.rst.jinja" +start_string = ".. towncrier release notes start\n" +title_format = "Version {version} ({project_date})" +underlines = ["-"] +issue_format = "" +# Fail on any other file, so that a misnamed entry is not silently skipped +ignore = ["README.rst", "template.rst.jinja"] + +# A single unnamed category, so that fragments are plain .rst files +[[tool.towncrier.type]] +directory = "rst" +name = "" +showcontent = true + [tool.ruff] target-version = "py311" extend-exclude = ["*.ipynb"]