Skip to content

Fix parameter handling in csv_to_wfdb - #600

Merged
tompollard merged 2 commits into
MIT-LCP:mainfrom
TheNetherWatcher:fix/csv-to-wfdb
Oct 31, 2026
Merged

tompollard merged 2 commits into
MIT-LCP:mainfrom
TheNetherWatcher:fix/csv-to-wfdb

Conversation

@TheNetherWatcher

@TheNetherWatcher TheNetherWatcher commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Closes #592, closes #593, closes #594.

Following the suggestion in #592, csv_to_wfdb now builds one Record in both modes. With record_only=False it writes that record with Record.wrsamp(write_dir=...); with record_only=True it returns it.

#592: parameters that had no effect

  • dat_file_name is used if given (the default is still <record_name>.dat).
  • counter_freq, base_counter, skew, byte_offset, adc_res, adc_zero and block_size are written to the header (byte_offset also writes the leading bytes, as wr_dat_file already does).
  • samps_per_frame, init_value and checksum follow from the CSV samples, so as suggested they're deprecated: passing them gives a DeprecationWarning and they're ignored. (samps_per_frame other than 1 also never reached the header before, because wrsamp writes d_signal unexpanded, see wrsamp incorrectly saves samps_per_frame when expanded=False #333.)

#593: defaults and broadcasting

  • New _per_channel helper: any str, numbers.Number or NumPy scalar is repeated for all channels, so adc_gain=200.5 and baseline=np.int64(3) work.
  • Defaults are applied only when the argument is None, so baseline=0 with adc_zero=1024 stays 0.
  • baseline defaults to adc_zero per channel, so adc_zero=[1, 2] gives baseline=[1, 2] instead of a list of lists.
  • init_value and checksum are computed from the digital samples (Record.adc() / calc_checksum()) in both modes, and the remaining defaults come from set_defaults() as in wrsamp.

#594: docstring

  • Corrected file_name, fs, header (ch_0, ch_1, ...) and the descriptions copied from the header spec, and documented the defaults and deprecations.

Compatibility

  • With default arguments, the written 100.hea and 100.dat for sample-data/100.csv are byte-identical to main.
  • With record_only=True, the returned record now matches what would be written: adc_res defaults to the resolution of fmt (16 for the default format, was 12), init_value is the rounded digital value (was truncated), and checksum is over the digital samples (was over the physical values). The returned record still has d_signal=None, as before.

Tests
Seven new tests in TestCsvToWfdb: record vs written header, scalar broadcasting, zero and list defaults, parameters reaching the files, and the three deprecated arguments. All seven fail on main. ruff check is clean, the docs build passes with -W, and pytest -m "not network" passes (89 tests).

This touches the same file as #599 (csv2ann), but a different function. Both add a changelog entry at the same place and append tests to tests/io/test_convert.py, so whichever is merged second will have small conflicts there. I'll rebase it.

@tompollard tompollard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good! Building one Record for both modes works well. With default arguments the written files are byte-identical to main, and the returned record now matches the written header. It also fixes CSVs with missing values, which crash on main.

Two small things before merging:

  1. bool is treated as a number. isinstance(True, numbers.Number) is True, so adc_gain=True writes True(0)/mV into the header without an error (main raised a TypeError). Could _per_channel reject bool?

  2. The deprecation warning is hidden in some cases. Python only shows DeprecationWarning when the call comes from the main script, so code calling csv_to_wfdb from inside another module won't see it. Since these arguments are now ignored (e.g. init_value=[1, 2] with record_only=True now gives the computed values), could you use FutureWarning instead, which is always shown?

Smaller, optional:

  • A test with a missing value (NaN) in the CSV, since that's a fix with no test yet.
  • A CSV with a header but no rows now fails with Missing field required: init_value when writing. A clearer error would help, but it isn't essential.
  • In docs/changes.rst, please drop the backticks from the heading, since they don't render inside bold text.

Happy to merge once the first two are addressed.

@TheNetherWatcher

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review! All done:

  1. _per_channel now raises TypeError for bool (and np.bool_), naming the argument, e.g. adc_gain.
  2. The warning for ignored arguments is now a FutureWarning.

The optional ones too:

pytest -m "not network" passes (92 tests), and ruff check and the docs build are clean.

@tompollard

Copy link
Copy Markdown
Member

Thanks again, looks good! I think we'll have a small conflict to address when #599 is merged.

Build one Record in both modes and write it with Record.wrsamp, so header
parameters are no longer dropped. Broadcast any scalar, treat only None
as missing, compute init_value and checksum from the digital samples,
deprecate samps_per_frame, init_value and checksum, and correct the
parameter descriptions.
Reject bool in _per_channel, warn about ignored arguments with
FutureWarning so it is always shown, raise a clear error for a CSV
without samples, and test missing values.
@TheNetherWatcher

Copy link
Copy Markdown
Contributor Author

Thanks! I've rebased onto main now that #599 is in. The conflicts were only the changelog and test_convert.py, and I kept both sides: the new csv_to_wfdb tests stay in TestCsvToWfdb, followed by TestCsv2Ann. Tests (96) and the docs build pass.

@tompollard
tompollard merged commit c449e76 into MIT-LCP:main Oct 31, 2026
18 checks passed
@TheNetherWatcher
TheNetherWatcher deleted the fix/csv-to-wfdb branch November 1, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants