Source code for meeg_utils.io.read
"""Reading recordings."""
from __future__ import annotations
import json
from pathlib import Path
import mne
from loguru import logger
from mne.io import BaseRaw
from mne_bids import BIDSPath, get_bids_path_from_fname, read_raw_bids
[docs]
def read(path: str | Path | BIDSPath, *, preload: bool = True, **kwargs) -> BaseRaw:
"""Read a recording into memory, using BIDS metadata when available.
BIDS recordings are read with :func:`mne_bids.read_raw_bids`, so channel
types, bad channels (``channels.tsv``), events, line frequency and
head-coordinate information come from the BIDS sidecars. A plain path
that lies inside a BIDS dataset is read the same way; any other file is
read with :func:`mne.io.read_raw`.
Parameters
----------
path : str | Path | BIDSPath
The recording.
preload : bool
Load the data into memory (needed for processing); ``False`` reads
only the header and annotations.
**kwargs
Passed to :func:`mne_bids.read_raw_bids` (as ``extra_params`` entries
when reading BIDS data) or :func:`mne.io.read_raw`.
Returns
-------
Raw
The recording.
Raises
------
FileNotFoundError
If the recording does not exist.
"""
bids_path = path if isinstance(path, BIDSPath) else as_bids_path(Path(path))
if bids_path is not None:
if bids_path.fpath is None or not Path(bids_path.fpath).exists():
raise FileNotFoundError(f"BIDS recording not found: {bids_path}")
raw = read_raw_bids(bids_path, extra_params=kwargs or None, verbose=False)
if preload:
raw.load_data(verbose=False)
else:
path = Path(path)
if not path.exists():
raise FileNotFoundError(f"Recording not found: {path}")
raw = mne.io.read_raw(path, preload=preload, verbose=False, **kwargs)
if raw.info["line_freq"] is None:
logger.warning(
f"The power line frequency of {path} is unknown; steps that need it "
"(e.g. line-noise removal) will ask for it explicitly."
)
return raw
def as_bids_path(path: Path) -> BIDSPath | None:
"""Return the BIDSPath of a raw file inside a BIDS dataset, else None.
Files inside BIDS derivatives are not raw BIDS data (they lack the raw
sidecars) and are read as plain files.
"""
root = _find_bids_root(path)
if root is None or _is_derivative(root):
return None
try:
bids_path = get_bids_path_from_fname(path, check=False, verbose=False)
except Exception: # not a BIDS file name
return None
if bids_path.subject is None:
return None
return bids_path.update(root=root)
def _find_bids_root(path: Path) -> Path | None:
"""Return the closest parent with a dataset_description.json, if any."""
for parent in path.resolve().parents:
if (parent / "dataset_description.json").is_file():
return parent
return None
def _is_derivative(root: Path) -> bool:
try:
description = json.loads((root / "dataset_description.json").read_text(encoding="utf-8"))
except (OSError, ValueError):
return False
return str(description.get("DatasetType", "raw")).lower() == "derivative"