From 140d83e7c5a1dfba4c556fed82367f0df0adada1 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Fri, 26 Aug 2022 19:51:22 +0100 Subject: [PATCH 1/5] ENH : add units parameter to read_raw_edf in case units is missing from the file --- mne/io/edf/edf.py | 25 +++++++++++++++++++++---- mne/utils/docs.py | 8 ++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index f7f820f8902..b47f0540b9e 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -19,7 +19,7 @@ from ...utils import verbose, logger, warn from ..utils import _blk_read_lims, _mult_cal_one -from ..base import BaseRaw +from ..base import BaseRaw, _get_scaling from ..meas_info import _empty_info, _unique_channel_names from ..constants import FIFF from ...filter import resample @@ -83,6 +83,7 @@ class RawEDF(BaseRaw): .. versionadded:: 1.1 %(preload)s + %(units_edf_io)s %(verbose)s See Also @@ -132,7 +133,7 @@ class RawEDF(BaseRaw): @verbose def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', exclude=(), infer_types=False, preload=False, include=None, - verbose=None): + units=None, *, verbose=None): logger.info('Extracting EDF parameters from {}...'.format(input_fname)) input_fname = os.path.abspath(input_fname) info, edf_info, orig_units = _get_info(input_fname, stim_channel, eog, @@ -140,6 +141,19 @@ def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', preload, include) logger.info('Creating raw.info structure...') + if units is not None and isinstance(units, str): + units = {ch_name: units for ch_name in info['ch_names']} + + for k, (this_ch, this_unit) in enumerate(orig_units.items()): + if this_unit != "" and this_unit in units: + raise ValueError(f'Unit for channel {this_ch} is present in the file.' + ' . Cannot overwrite it with the units argument.') + if this_unit == "" and this_ch in units: + orig_units[this_ch] = units[this_ch] + ch_type = edf_info["ch_types"][k] + scaling = _get_scaling(ch_type.lower(), orig_units[this_ch]) + edf_info["units"][k] /= scaling + # Raw attributes last_samps = [edf_info['nsamples'] - 1] super().__init__(info, preload, filenames=[input_fname], @@ -147,6 +161,8 @@ def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', orig_format='int', orig_units=orig_units, verbose=verbose) + import ipdb; ipdb.set_trace() + # Read annotations from file and set it onset, duration, desc = list(), list(), list() if len(edf_info['tal_idx']) > 0: @@ -1282,7 +1298,7 @@ def _find_tal_idx(ch_names): @fill_doc def read_raw_edf(input_fname, eog=None, misc=None, stim_channel='auto', exclude=(), infer_types=False, include=None, preload=False, - verbose=None): + units=None, *, verbose=None): """Reader function for EDF or EDF+ files. Parameters @@ -1322,6 +1338,7 @@ def read_raw_edf(input_fname, eog=None, misc=None, stim_channel='auto', .. versionadded:: 1.1 %(preload)s + %(units_edf_io)s %(verbose)s Returns @@ -1384,7 +1401,7 @@ def read_raw_edf(input_fname, eog=None, misc=None, stim_channel='auto', return RawEDF(input_fname=input_fname, eog=eog, misc=misc, stim_channel=stim_channel, exclude=exclude, infer_types=infer_types, preload=preload, include=include, - verbose=verbose) + units=units, verbose=verbose) @fill_doc diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 8f89057ad77..617c876844f 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -3504,6 +3504,14 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75): channel-type-specific default unit. """ +docdict['units_edf_io'] = """ + units : dict | str + The units of the channels as stored in the .edf file. This argument + is useful only if the units are missing from the original file. + If a dict, it must map a channel name to its unit, and if str + it is assumed that all channels have the same units. +""" + docdict['units_topomap'] = """ units : dict | str | None The unit of the channel type used for colorbar label. If From 6a52456b60a8182580c9c844a012d0de1560925d Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Fri, 26 Aug 2022 19:55:20 +0100 Subject: [PATCH 2/5] cleanup --- mne/io/edf/edf.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index b47f0540b9e..a41041a8529 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -146,8 +146,9 @@ def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', for k, (this_ch, this_unit) in enumerate(orig_units.items()): if this_unit != "" and this_unit in units: - raise ValueError(f'Unit for channel {this_ch} is present in the file.' - ' . Cannot overwrite it with the units argument.') + raise ValueError(f'Unit for channel {this_ch} is present in ' + 'the file. Cannot overwrite it with the units ' + 'argument.') if this_unit == "" and this_ch in units: orig_units[this_ch] = units[this_ch] ch_type = edf_info["ch_types"][k] @@ -161,8 +162,6 @@ def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', orig_format='int', orig_units=orig_units, verbose=verbose) - import ipdb; ipdb.set_trace() - # Read annotations from file and set it onset, duration, desc = list(), list(), list() if len(edf_info['tal_idx']) > 0: From 245de6a40add064f37e7e8f7df0bcb6307fa26b8 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Fri, 26 Aug 2022 23:10:55 +0100 Subject: [PATCH 3/5] update what's new + flake8 --- doc/changes/latest.inc | 1 + mne/io/edf/edf.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 05b238a21b5..9eb4026e2eb 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -34,6 +34,7 @@ Enhancements - The ``trans`` parameter in :func:`mne.make_field_map` now accepts a :class:`~pathlib.Path` object, and uses standardised loading logic (:gh:`10784` by :newcontrib:`Andrew Quinn`) - Add HTML representation for `~mne.Evoked` in Jupyter Notebooks (:gh:`11075` by `Valerii Chirkov`_ and `Andrew Quinn`_) - Allow :func:`mne.beamformer.make_dics` to take ``pick_ori='vector'`` to compute vector source estimates (:gh:`19080` by `Alex Rockhill`_) +- Add ``units`` parameter to :func:`mne.io.read_raw_edf` in case units are missing from the file (:gh:`11099` by `Alex Gramfort`_) - Add ``on_missing`` functionality to all of our classes that have a ``drop_channels`` method, to control what happens when channel names are not in the object (:gh:`11077` by `Andrew Quinn`_) Bugs diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index a41041a8529..6e8d18081a8 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -147,8 +147,8 @@ def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', for k, (this_ch, this_unit) in enumerate(orig_units.items()): if this_unit != "" and this_unit in units: raise ValueError(f'Unit for channel {this_ch} is present in ' - 'the file. Cannot overwrite it with the units ' - 'argument.') + 'the file. Cannot overwrite it with the ' + 'units argument.') if this_unit == "" and this_ch in units: orig_units[this_ch] = units[this_ch] ch_type = edf_info["ch_types"][k] From 1f030fe3084d78a76ce4c82df860a063953e4e02 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Fri, 26 Aug 2022 23:24:42 +0100 Subject: [PATCH 4/5] adding units to bdf too --- mne/io/edf/edf.py | 11 +++++++---- mne/utils/docs.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index 6e8d18081a8..f71b782cb8c 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -83,7 +83,7 @@ class RawEDF(BaseRaw): .. versionadded:: 1.1 %(preload)s - %(units_edf_io)s + %(units_edf_bdf_io)s %(verbose)s See Also @@ -143,6 +143,8 @@ def __init__(self, input_fname, eog=None, misc=None, stim_channel='auto', if units is not None and isinstance(units, str): units = {ch_name: units for ch_name in info['ch_names']} + elif units is None: + units = dict() for k, (this_ch, this_unit) in enumerate(orig_units.items()): if this_unit != "" and this_unit in units: @@ -1337,7 +1339,7 @@ def read_raw_edf(input_fname, eog=None, misc=None, stim_channel='auto', .. versionadded:: 1.1 %(preload)s - %(units_edf_io)s + %(units_edf_bdf_io)s %(verbose)s Returns @@ -1406,7 +1408,7 @@ def read_raw_edf(input_fname, eog=None, misc=None, stim_channel='auto', @fill_doc def read_raw_bdf(input_fname, eog=None, misc=None, stim_channel='auto', exclude=(), infer_types=False, include=None, preload=False, - verbose=None): + units=None, *, verbose=None): """Reader function for BDF files. Parameters @@ -1446,6 +1448,7 @@ def read_raw_bdf(input_fname, eog=None, misc=None, stim_channel='auto', .. versionadded:: 1.1 %(preload)s + %(units_edf_bdf_io)s %(verbose)s Returns @@ -1501,7 +1504,7 @@ def read_raw_bdf(input_fname, eog=None, misc=None, stim_channel='auto', return RawEDF(input_fname=input_fname, eog=eog, misc=misc, stim_channel=stim_channel, exclude=exclude, infer_types=infer_types, preload=preload, include=include, - verbose=verbose) + units=units, verbose=verbose) @fill_doc diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 617c876844f..b952fe42d98 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -3504,9 +3504,9 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75): channel-type-specific default unit. """ -docdict['units_edf_io'] = """ +docdict['units_edf_bdf_io'] = """ units : dict | str - The units of the channels as stored in the .edf file. This argument + The units of the channels as stored in the file. This argument is useful only if the units are missing from the original file. If a dict, it must map a channel name to its unit, and if str it is assumed that all channels have the same units. From 707744e9a3e7caf9169ddbfafbb3f5a1fce0e3de Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Fri, 26 Aug 2022 23:38:41 +0100 Subject: [PATCH 5/5] fix docstring --- mne/utils/docs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mne/utils/docs.py b/mne/utils/docs.py index b952fe42d98..4cfcd849cf4 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -3505,11 +3505,11 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75): """ docdict['units_edf_bdf_io'] = """ - units : dict | str - The units of the channels as stored in the file. This argument - is useful only if the units are missing from the original file. - If a dict, it must map a channel name to its unit, and if str - it is assumed that all channels have the same units. +units : dict | str + The units of the channels as stored in the file. This argument + is useful only if the units are missing from the original file. + If a dict, it must map a channel name to its unit, and if str + it is assumed that all channels have the same units. """ docdict['units_topomap'] = """