?PhysioIO now answers: a package help page gives one paragraph on what the
package is for, the main entry points grouped by task, and where to go next.@examples added or corrected across 10 help pages. Each runs
offline in seconds, writes nothing outside tempdir(), and is executed by
R CMD check; anything needing a device, a download or an optional backend is
fenced with the reason stated.attachBIDSPhysio() now states both stream offsets explicitly. The container
refuses a partially specified clock instead of completing it with zeros, so
the main recording's origin is a claim this function makes rather than an
assumption made downstream.
Type checks accept the canonical MultiPhysioExperiment. They previously
tested for MultiRatePhysioExperiment only, which would have rejected a
container built by the current constructor.
readEDF() EDF+ annotation fix: a data record's annotation channel holds
one or more Time-stamped Annotation Lists, each terminated by a NUL (0x00),
with the channel NUL-padded to its fixed length. The reader stripped every NUL
before parsing, which destroyed the TAL boundaries, and then split on
0x14 0x14, which is not a TAL delimiter. Records holding more than one event
TAL were therefore fused into a single annotation whose value was every
label concatenated. The Sleep-EDF Expanded hypnograms, which store a whole
night of scoring in one data record, collapsed from 154 annotations to 1
(and 126 to 1 on the second night). The channel is now split on its NUL
separators and each TAL parsed individually: both hypnograms reproduce
mne.read_annotations() exactly (154 and 126 events, onsets and durations
identical). Files that carry one TAL per record — the BCI2000/eegmmidb EDF+
recordings, for instance — parsed correctly before and are unchanged
(re-verified against MNE).
The defect could not be reached by a round trip, because writeEDF() writes
no annotation channel at all; like the readWFDB() baseline defect fixed in
0.2.7, it needed a comparison against the format's reference implementation to
surface. A regression test now builds a synthetic single-record EDF+ holding
three event TALs and asserts they stay separate.
readWFDB() data-correctness fix: a signal line whose gain field has no
explicit (baseline) annex — the style of the original MIT-BIH headers
(... 212 200 11 1024 995 ...) — was read with baseline = gain instead of
the header(5) default baseline = ADC zero, because the extracting sub()
returns its input unchanged on a non-match. Every physical value gained a
constant (adczero - gain)/gain offset: +4.12 mV on MIT-BIH (gain 200,
ADC zero 1024) and exactly -1 physical unit on Fantasia-style headers
(bare gain, ADC zero 0). Headers that carry (baseline) (BIDMC, GRABMyo,
Non-EEG, and everything written by writeWFDB(), which always emits the
annex — the reason round-trip tests never caught this) were unaffected.
Downstream, the offset degraded amplitude-sensitive processing: the default
Pan-Tompkins detector missed beats on offset MIT-BIH signals that it finds
cleanly in correct units. Adds bare-gain / explicit-baseline / no-ADC-zero
regression tests, and replaces the synthetic extdata record-100 fixture
with a genuine first-5000-sample excerpt of PhysioNet MIT-BIH record 100
(ODC-By v1.0) that keeps the original bare-gain header style so the
baseline-default rule stays regression-tested against real bytes.io-repositories.R, extending the repository retrieval set:
mendeleyDatasetFiles(dataset_id, version) — the public file manifest of a Mendeley Data dataset (names, sizes, SHA-256 hashes, content ids). Works without authentication.downloadMendeley(dataset_id, dest, files, version, access_token, ...) — download a dataset's files, verifying each against its SHA-256. Honest scope: Mendeley Data exposes dataset/file metadata publicly but requires an OAuth access token (via access_token or the MENDELEY_TOKEN environment variable) to download file bytes — verified empirically (the token endpoint offers no anonymous grant; the anonymous file endpoints return an error body). Without a token the function errors early and points at mendeleyDatasetFiles().downloadFromDOI() now also routes Mendeley DOIs (10.17632/<id>.<version>).name, path, size, checksum, algo, checksum_ok (was md5/md5_ok for figshare/Zenodo in 0.2.5). This accommodates Mendeley's SHA-256 alongside figshare/Zenodo MD5 under one uniform return contract; algo records the checksum algorithm per row.downloadPhysioNet() beyond PhysioNet. New exported functions in io-repositories.R:
downloadFigshare(article_id, dest, files, version, ...) — download the files of a public figshare article over the figshare REST API, with optional MD5 verification against the checksum figshare reports.figshareCollectionArticles(collection_id) — list the member articles of a figshare collection (e.g. GaitRec, c.4788012) so each can be fetched with downloadFigshare().downloadZenodo(record_id, dest, files, ...) — download the files of a public Zenodo record, handling both the legacy file-array and the newer InvenioRDM files.entries response shapes, with MD5 verification.downloadFromDOI(doi, dest, ...) — convenience dispatcher routing a figshare or Zenodo DOI (or a full doi.org URL) to the appropriate downloader.
All use base utils::download.file over the repositories' public REST APIs with no authentication (public records only), return a tidy per-file data frame (name, path, size, md5, md5_ok), and bump the download timeout for large files. Tested offline (metadata-shape parsers for both repositories, DOI routing) and live (a real figshare file MD5-verified against its published checksum, a Zenodo file verified self-consistently, and the GaitRec collection listing).mne-interop ("Interoperating with MNE-Python") documenting the hasMNE() / toMNE() / fromMNE() bridge and the ecosystem's interop-first, selectively-native strategy for brain-imaging integration (borrow MNE for individual-MRI source imaging + the long tail; keep native the reproducibility-substrate / PhysioAgent ops; use MNE as a validation oracle). Documentation only — the chunks require MNE-Python via reticulate and are not executed or tangled, so R CMD check and r-universe never initialise reticulate.test-bridge-mne.R: MNE's PSD and an independent base-R periodogram agree on per-channel alpha power (spatial correlation > 0.99) on a synthetic alpha gradient. Like the other bridge tests it skips unless reticulate can reach a Python with mne (safe on CRAN / r-universe); a header comment documents how to run the MNE suite (RETICULATE_PYTHON=…).readEDF() and readBrainVision() now record a W3C-PROV provenance activity for the read (the DAG root), capturing the source file path, so a loaded object carries its origin and the read step is visible to the reproducibility substrate run-tracing. This is separate from the existing on-disk provenance serialization (io-prov.R). No change to parsing.readFIF(backend = "native") now decodes channel names safely on real
Neuromag/MNE files. FIFF strings are Latin-1 and NUL-terminated within a
fixed field; the previous code stripped every NUL (appending post-terminator
padding) and left the string in an unknown encoding, so a non-ASCII byte or
trailing padding made trimws() abort with "invalid UTF-8". The name is now
truncated at the first NUL and decoded Latin-1 to UTF-8, matching MNE. Adds a
bundled MNE-written parity fixture and Latin-1 regression tests.initPhysioSchema() now stores the experiment metadata column as VARCHAR
instead of DuckDB's JSON type. The JSON type requires DuckDB's json
extension, which DuckDB attempts to auto-download on first use; this failed in
offline build environments (such as the r-universe source builder), breaking
the bids-database vignette and the database example. Metadata is serialized
and parsed with jsonlite in R, so a plain string column is fully equivalent
and removes the extension dependency. Existing databases are unaffected;
metadata continues to round-trip identically.Initial release of PhysioIO as a standalone package in the x-biosignal
ecosystem, split out from the original PhysioExperiment monolith. PhysioIO
provides input, output, and database integration for
SummarizedExperiment-derived physiological signal containers, covering the
file formats and storage backends common in EEG, PSG, ECG, and BCI research.
PhysioExperiment object with channel metadata
and recording information preserved:
readEDF() and writeEDF(), including per-channel
sampling-rate resampling, channel and time-window subsetting, and header
parsing (labels, transducers, physical dimensions, patient metadata).readBDF() and writeBDF().readGDF() and writeGDF() for BCI workflows..vhdr / .vmrk / .eeg) via readBrainVision() and
writeBrainVision(), one of the official BIDS-EEG formats.readBIDS() and writeBIDS(), with
dataset navigation and validation helpers listBIDSSubjects(),
listBIDSSessions(), and validateBIDS().readCSV() / writeCSV() (wide and long layouts, optional
time column or sampling-rate-derived time), plus companion readers/writers
for events (readEventsCSV(), writeEventsCSV()) and electrode positions
(readElectrodePositionsCSV(), writeElectrodePositionsCSV())..mat I/O via readMAT() and writeMAT() (backed by R.matlab,
with a pluggable backend option for testing).readPhysio() and writePhysio() for fast native
serialisation of PhysioExperiment objects.readClinicalMetadataCSV() loads assessment tables keyed on
subject_id / visit_id for linking sessions to clinical variables.validateClinicalMetadata() checks required columns and value ranges.mapClinicalCodes() recodes categorical clinical fields.writePhysioHDF5() / readPhysioHDF5() write and read chunked,
compressed HDF5 datasets, with reads returning delayed HDF5Array-backed
assays.isHDF5Backed() and realizeHDF5() inspect and materialise delayed
assays; writeAssayHDF5() exports an individual assay to HDF5.connectDatabase() / disconnectDatabase() manage connections and
initPhysioSchema() builds the relational schema (experiments, channels,
signals, events).registerExperiment() catalogues a PhysioExperiment (with optional
signal storage), and loadExperiment() reconstructs it from the database.queryExperiments() filters the catalogue by subject, task, and date
range; deleteExperiment() removes entries; dbStats() summarises the
database contents.