Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into openmeeg
Browse files Browse the repository at this point in the history
* upstream/main:
  Revert "Add error message when conversion of EEG locs to [circle deploy] (mne-tools#11104)
  MRG: Fixes for mne-tools#11090 (mne-tools#11108)
  add test for edf units param (mne-tools#11105)
  BUG: Improve logic for bti (mne-tools#11102)
  add spectrum class (mne-tools#10184)
  ENH : add units parameter to read_raw_edf in case units is missing from the file (mne-tools#11099)
  ENH: Add temperature and galvanic (mne-tools#11090)
  • Loading branch information
larsoner committed Aug 29, 2022
2 parents 4358e01 + bc30e0e commit 49c9474
Show file tree
Hide file tree
Showing 51 changed files with 2,588 additions and 779 deletions.
4 changes: 4 additions & 0 deletions doc/_includes/channel_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@ ias Internal Active Shielding data

syst System status channel information
(Triux systems only)

temperature Temperature Degrees Celsius

gsr Galvanic skin response Siemens
============= ========================================= =================
3 changes: 3 additions & 0 deletions doc/_static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,6 @@ ul.icon-bullets {
img.hidden {
visibility: hidden;
}
td.justify {
text-align-last: justify;
}
7 changes: 5 additions & 2 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ Enhancements
- Add ``starting_affine`` keyword argument to :func:`mne.transforms.compute_volume_registration` to initialize an alignment with an affine (:gh:`11020` by `Alex Rockhill`_)
- 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`_)
- Add support for ``temperature`` and ``gsr`` (galvanic skin response, i.e., electrodermal activity) channel types (:gh:`11090`, :gh:`11108` by `Eric Larson`_ and `Richard Höchenberger`_)
- 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
Expand All @@ -45,12 +47,13 @@ Bugs
- Fix bug in :class:`mne.viz.Brain` constructor where the first argument was named ``subject_id`` instead of ``subject`` (:gh:`11049` by `Eric Larson`_)
- Fix bug in :ref:`mne coreg` where the MEG helmet position was not updated during ICP fitting (:gh:`11084` by `Eric Larson`_)
- Document ``height`` and ``weight`` keys of ``subject_info`` entry in :class:`mne.Info` (:gh:`11019` by :newcontrib:`Sena Er`)
- Fixed bug in :func:`mne.viz.plot_filter` when plotting filters created using ``output='ba'`` mode with ``compensation`` turned on. (by `Marian Dovgialo`_)
- Fix bug in :func:`mne.viz.plot_filter` when plotting filters created using ``output='ba'`` mode with ``compensation`` turned on. (:gh:`11040` by `Marian Dovgialo`_)
- Fix bug in :func:`mne.io.read_raw_bti` where EEG, EMG, and H/VEOG channels were not detected properly, and many non-ECG channels were called ECG. The logic has been improved, and any channels of unknown type are now labeled as ``misc`` (:gh:`11102` by `Eric Larson`_)
- Fix bug in :func:`mne.viz.plot_topomap` when providing ``sphere="eeglab"`` (:gh:`11081` by `Mathieu Scheltienne`_)
- Applying a montage where EEG locations are not in head space (or unknown space) without fiducials will now raise an error message. (:gh:`11080` by `Marijn van Vliet`_)

API changes
~~~~~~~~~~~
- The ``bands`` parameter of :meth:`mne.Epochs.plot_psd_topomap` now accepts :class:`dict` input; legacy :class:`tuple` input is supported, but discouraged for new code (:gh:`11050` by `Daniel McCloy`_)
- The ``show_toolbar`` argument to :class:`mne.viz.Brain` is being removed by deprecation (:gh:`11049` by `Eric Larson`_)
- New classes :class:`~mne.time_frequency.Spectrum` and :class:`~mne.time_frequency.EpochsSpectrum`, created via new methods :meth:`Raw.compute_psd()<mne.io.Raw.compute_psd>`, :meth:`Epochs.compute_psd()<mne.Epochs.compute_psd>`, and :meth:`Evoked.compute_psd()<mne.Evoked.compute_psd>` (:gh:`10184` by `Daniel McCloy`_)
- The PSD functions that operate on Raw/Epochs/Evoked instances (``mne.time_frequency.psd_welch`` and ``mne.time_frequency.psd_multitaper``) are deprecated; for equivalent functionality create :class:`~mne.time_frequency.Spectrum` or :class:`~mne.time_frequency.EpochsSpectrum` objects instead and then run ``spectrum.get_data(return_freqs=True)`` (:gh:`10184` by `Daniel McCloy`_)
2 changes: 2 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@
'Transform': 'mne.transforms.Transform',
'Coregistration': 'mne.coreg.Coregistration',
'Figure3D': 'mne.viz.Figure3D',
'Spectrum': 'mne.time_frequency.Spectrum',
'EpochsSpectrum': 'mne.time_frequency.EpochsSpectrum',
# dipy
'dipy.align.AffineMap': 'dipy.align.imaffine.AffineMap',
'dipy.align.DiffeomorphicMap': 'dipy.align.imwarp.DiffeomorphicMap',
Expand Down
11 changes: 11 additions & 0 deletions doc/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -2285,6 +2285,17 @@ @article{LuckGaspelin2017
doi = {10.1111/psyp.12639},
}

@article{Welch1967,
title = {The Use of Fast {{Fourier}} Transform for the Estimation of Power Spectra: {{A}} Method Based on Time Averaging over Short, Modified Periodograms},
author = {Welch, Peter D.},
year = {1967},
journal = {IEEE Transactions on Audio and Electroacoustics},
volume = {15},
number = {2},
pages = {70--73},
doi = {10.1109/TAU.1967.1161901},
}

@article{MaksymenkoEtAl2017,
title = {Strategies for statistical thresholding of source localization maps in magnetoencephalography and estimating source extent},
volume = {290},
Expand Down
3 changes: 3 additions & 0 deletions doc/time_frequency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Time-Frequency
AverageTFR
EpochsTFR
CrossSpectralDensity
Spectrum
EpochsSpectrum

Functions that operate on mne-python objects:

Expand All @@ -36,6 +38,7 @@ Functions that operate on mne-python objects:
tfr_stockwell
read_tfrs
write_tfrs
read_spectrum

Functions that operate on ``np.ndarray`` objects:

Expand Down
26 changes: 17 additions & 9 deletions mne/channels/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ def equalize_channels(instances, copy=True, verbose=None):
FIFF.FIFF_UNIT_T_M: 'T/m',
FIFF.FIFF_UNIT_MOL: 'M',
FIFF.FIFF_UNIT_NONE: 'NA',
FIFF.FIFF_UNIT_CEL: 'C'}
FIFF.FIFF_UNIT_CEL: 'C',
FIFF.FIFF_UNIT_S: 'S'}


def _check_set(ch, projs, ch_type):
Expand Down Expand Up @@ -328,7 +329,7 @@ def set_channel_types(self, mapping, verbose=None):
ecg, eeg, emg, eog, exci, ias, misc, resp, seeg, dbs, stim, syst,
ecog, hbo, hbr, fnirs_cw_amplitude, fnirs_fd_ac_amplitude,
fnirs_fd_phase, fnirs_od
fnirs_fd_phase, fnirs_od, temperature, gsr
.. versionadded:: 0.9.0
"""
Expand Down Expand Up @@ -586,15 +587,16 @@ def set_meas_date(self, meas_date):


class UpdateChannelsMixin(object):
"""Mixin class for Raw, Evoked, Epochs, AverageTFR."""
"""Mixin class for Raw, Evoked, Epochs, Spectrum, AverageTFR."""

@verbose
def pick_types(self, meg=False, eeg=False, stim=False, eog=False,
ecg=False, emg=False, ref_meg='auto', misc=False,
ecg=False, emg=False, ref_meg='auto', *, misc=False,
resp=False, chpi=False, exci=False, ias=False, syst=False,
seeg=False, dipole=False, gof=False, bio=False,
ecog=False, fnirs=False, csd=False, dbs=False, include=(),
exclude='bads', selection=None, verbose=None):
ecog=False, fnirs=False, csd=False, dbs=False,
temperature=False, gsr=False,
include=(), exclude='bads', selection=None, verbose=None):
"""Pick some channels by type and names.
Parameters
Expand All @@ -620,7 +622,8 @@ def pick_types(self, meg=False, eeg=False, stim=False, eog=False,
ref_meg=ref_meg, misc=misc, resp=resp, chpi=chpi, exci=exci,
ias=ias, syst=syst, seeg=seeg, dipole=dipole, gof=gof, bio=bio,
ecog=ecog, fnirs=fnirs, csd=csd, dbs=dbs, include=include,
exclude=exclude, selection=selection)
exclude=exclude, selection=selection, temperature=temperature,
gsr=gsr)

self._pick_drop_channels(idx)

Expand Down Expand Up @@ -789,6 +792,7 @@ def _pick_drop_channels(self, idx, *, verbose=None):
# avoid circular imports
from ..io import BaseRaw
from ..time_frequency import AverageTFR, EpochsTFR
from ..time_frequency.spectrum import BaseSpectrum

msg = 'adding, dropping, or reordering channels'
if isinstance(self, BaseRaw):
Expand All @@ -813,8 +817,12 @@ def _pick_drop_channels(self, idx, *, verbose=None):
if mat is not None:
setattr(self, key, mat[idx][:, idx])

# All others (Evoked, Epochs, Raw) have chs axis=-2
axis = -3 if isinstance(self, (AverageTFR, EpochsTFR)) else -2
if isinstance(self, BaseSpectrum):
axis = self._dims.index('channel')
elif isinstance(self, (AverageTFR, EpochsTFR)):
axis = -3
else: # All others (Evoked, Epochs, Raw) have chs axis=-2
axis = -2
if hasattr(self, '_data'): # skip non-preloaded Raw
self._data = self._data.take(idx, axis=axis)
else:
Expand Down
8 changes: 0 additions & 8 deletions mne/channels/montage.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,19 +668,11 @@ def transform_to_head(montage):
# Get fiducial points and their coord_frame
native_head_t = compute_native_head_t(montage)
montage = montage.copy() # to avoid inplace modification

if native_head_t['from'] != FIFF.FIFFV_COORD_HEAD:
for d in montage.dig:
if d['coord_frame'] == native_head_t['from']:
d['r'] = apply_trans(native_head_t, d['r'])
d['coord_frame'] = FIFF.FIFFV_COORD_HEAD
elif d['kind'] == FIFF.FIFFV_POINT_EEG:
raise RuntimeError(
f'Could not transform EEG channel {d["ident"]} position '
f'from {_verbose_frames[d["coord_frame"]]} to head '
'coordinates. Fiducial points are either missing or '
'specified in a different coordinate frame than the EEG '
'channel locations.')
return montage


Expand Down
11 changes: 0 additions & 11 deletions mne/channels/tests/test_montage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1210,17 +1210,6 @@ def test_transform_to_head_and_compute_dev_head_t():
montage_polhemus
))

# Test errors when transforming without fiducials explicitly where points
# are tagged to be not in head or unknown coord space.
montage_without_fids = make_dig_montage(
ch_pos={"ch_1": np.array([1, 2, 3]),
"ch_2": np.array([4, 5, 6]),
"ch_3": np.array([7, 8, 9])},
coord_frame="mri") # MRI coordinate space
with pytest.raises(RuntimeError, match='Could not transform EEG channel'):
with pytest.warns(RuntimeWarning, match='Fiducial point .* not found'):
transform_to_head(montage_without_fids)


def test_set_montage_with_mismatching_ch_names():
"""Test setting a DigMontage with mismatching ch_names."""
Expand Down
2 changes: 2 additions & 0 deletions mne/cov.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ def plot_topomap(self, info, ch_type=None, vmin=None,
----------
%(info_not_none)s
%(ch_type_topomap)s
.. versionadded:: 0.21
%(vmin_vmax_topomap)s
%(cmap_topomap)s
%(sensors_topomap)s
Expand Down
2 changes: 1 addition & 1 deletion mne/decoding/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from .. import pick_types
from ..filter import filter_data
from ..time_frequency.psd import psd_array_multitaper
from ..time_frequency import psd_array_multitaper
from ..utils import fill_doc, _check_option, _validate_type, verbose
from ..io.pick import (pick_info, _pick_data_channels, _picks_by_type,
_picks_to_idx)
Expand Down
16 changes: 10 additions & 6 deletions mne/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,27 @@
exci='k', ias='k', syst='k', seeg='saddlebrown', dbs='seagreen',
dipole='k', gof='k', bio='k', ecog='k', hbo='#AA3377', hbr='b',
fnirs_cw_amplitude='k', fnirs_fd_ac_amplitude='k',
fnirs_fd_phase='k', fnirs_od='k', csd='k', whitened='k'),
fnirs_fd_phase='k', fnirs_od='k', csd='k', whitened='k',
gsr='#666633', temperature='#663333'),
si_units=dict(mag='T', grad='T/m', eeg='V', eog='V', ecg='V', emg='V',
misc='AU', seeg='V', dbs='V', dipole='Am', gof='GOF',
bio='V', ecog='V', hbo='M', hbr='M', ref_meg='T',
fnirs_cw_amplitude='V', fnirs_fd_ac_amplitude='V',
fnirs_fd_phase='rad', fnirs_od='V', csd='V/m²',
whitened='Z'),
whitened='Z', gsr='S', temperature='C'),
units=dict(mag='fT', grad='fT/cm', eeg='µV', eog='µV', ecg='µV', emg='µV',
misc='AU', seeg='mV', dbs='µV', dipole='nAm', gof='GOF',
bio='µV', ecog='µV', hbo='µM', hbr='µM', ref_meg='fT',
fnirs_cw_amplitude='V', fnirs_fd_ac_amplitude='V',
fnirs_fd_phase='rad', fnirs_od='V', csd='mV/m²',
whitened='Z'),
whitened='Z', gsr='S', temperature='C'),
# scalings for the units
scalings=dict(mag=1e15, grad=1e13, eeg=1e6, eog=1e6, emg=1e6, ecg=1e6,
misc=1.0, seeg=1e3, dbs=1e6, ecog=1e6, dipole=1e9, gof=1.0,
bio=1e6, hbo=1e6, hbr=1e6, ref_meg=1e15,
fnirs_cw_amplitude=1.0, fnirs_fd_ac_amplitude=1.0,
fnirs_fd_phase=1., fnirs_od=1.0, csd=1e3, whitened=1.),
fnirs_fd_phase=1., fnirs_od=1.0, csd=1e3, whitened=1.,
gsr=1., temperature=1.),
# rough guess for a good plot
scalings_plot_raw=dict(mag=1e-12, grad=4e-11, eeg=20e-6, eog=150e-6,
ecg=5e-4, emg=1e-3, ref_meg=1e-12, misc='auto',
Expand All @@ -39,7 +41,8 @@
hbr=10e-6, whitened=10., fnirs_cw_amplitude=2e-2,
fnirs_fd_ac_amplitude=2e-2, fnirs_fd_phase=2e-1,
fnirs_od=2e-2, csd=200e-4,
dipole=1e-7, gof=1e2),
dipole=1e-7, gof=1e2,
gsr=1., temperature=1.),
scalings_cov_rank=dict(mag=1e12, grad=1e11, eeg=1e5, # ~100x scalings
seeg=1e1, dbs=1e4, ecog=1e4, hbo=1e4, hbr=1e4),
ylim=dict(mag=(-600., 600.), grad=(-200., 200.), eeg=(-200., 200.),
Expand All @@ -55,7 +58,8 @@
fnirs_fd_phase='fNIRS (FD phase)',
fnirs_od='fNIRS (OD)', hbr='Deoxyhemoglobin',
gof='Goodness of fit', csd='Current source density',
stim='Stimulus',
stim='Stimulus', gsr='Galvanic skin response',
temperature='Temperature',
),
mask_params=dict(marker='o',
markerfacecolor='w',
Expand Down
Loading

0 comments on commit 49c9474

Please sign in to comment.