Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix chpi write #1067

Merged
merged 16 commits into from
Sep 28, 2022
5 changes: 5 additions & 0 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The following authors had contributed before. Thank you for sticking around!
* `Robert Luke`_
* `Stefan Appelhoff`_
* `Dominik Welke`_
* `Eduard Ort`_

Detailed list of changes
~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -74,6 +75,7 @@ Detailed list of changes

- Until now, :class:`mne_bids.BIDSPath` prepends extensions with a period "." automatically. We intend to remove this undocumented side-effect and now emit a ``FutureWarning`` if an ``extension`` that does not start with a ``.`` is provided. Starting with MNE-BIDS 0.12, an exception will be raised in this case, by `Richard Höchenberger`_ (:gh:`1061`)


🛠 Requirements
^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -108,6 +110,9 @@ Detailed list of changes

- Whenever :func:`~mne_bids.read_raw_bids` encounters a channel type that currently doesn't translate into an appropriate MNE channel type, the channel type will now be set to ``'misc``. Previously, seemingly arbitrary channel types would be applied, e.g. ``'eeg'`` for GSR and temperature channels, by `Richard Höchenberger`_ (:gh:`1052`)

- Fix the incorrect setting of the fields ``ContinuousHeadLocalization`` and ``HeadCoilFrequency`` for Neuromag MEG recordings, by `Eduard Ort`_ (:gh:`1067`)


:doc:`Find out what was new in previous releases <whats_new_previous_releases>`

.. include:: authors.rst
31 changes: 19 additions & 12 deletions mne_bids/tests/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import numpy as np
from numpy.testing import assert_almost_equal

from pkg_resources import parse_version

import mne
from mne.io.constants import FIFF
from mne.utils import requires_nibabel, object_diff, requires_version
Expand Down Expand Up @@ -713,21 +715,26 @@ def test_handle_chpi_reading(tmp_path):
meg_json_data = json.load(f)

# cHPI frequency mismatch
meg_json_data_freq_mismatch = meg_json_data.copy()
meg_json_data_freq_mismatch['HeadCoilFrequency'][0] = 123
_write_json(meg_json_path, meg_json_data_freq_mismatch, overwrite=True)
if parse_version(mne.__version__) <= parse_version('1.1'):
assert 'ContinuousHeadLocalization' not in meg_json_data
assert 'HeadCoilFrequency' not in meg_json_data
else:
meg_json_data_freq_mismatch = meg_json_data.copy()
meg_json_data_freq_mismatch['HeadCoilFrequency'][0] = 123
_write_json(meg_json_path, meg_json_data_freq_mismatch, overwrite=True)

with pytest.warns(RuntimeWarning, match='Defaulting to .* mne.Raw object'):
raw_read = read_raw_bids(bids_path)
with pytest.warns(RuntimeWarning,
match='Defaulting to .* mne.Raw object'):
raw_read = read_raw_bids(bids_path)

# cHPI "off" according to sidecar, but present in the data
meg_json_data_chpi_mismatch = meg_json_data.copy()
meg_json_data_chpi_mismatch['ContinuousHeadLocalization'] = False
_write_json(meg_json_path, meg_json_data_chpi_mismatch, overwrite=True)
# cHPI "off" according to sidecar, but present in the data
meg_json_data_chpi_mismatch = meg_json_data.copy()
meg_json_data_chpi_mismatch['ContinuousHeadLocalization'] = False
_write_json(meg_json_path, meg_json_data_chpi_mismatch, overwrite=True)

raw_read = read_raw_bids(bids_path)
assert raw_read.info['hpi_subsystem'] is None
assert raw_read.info['hpi_meas'] == []
raw_read = read_raw_bids(bids_path)
assert raw_read.info['hpi_subsystem'] is None
assert raw_read.info['hpi_meas'] == []


@pytest.mark.filterwarnings(warning_str['nasion_not_found'])
Expand Down
23 changes: 15 additions & 8 deletions mne_bids/tests/test_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,6 @@ def test_fif(_bids_validate, tmp_path):
def test_chpi(_bids_validate, tmp_path, format):
"""Test writing of cHPI information."""
data_path = testing.data_path()
kit_data_path = op.join(base_path, 'kit', 'tests', 'data')

if format == 'fif_no_chpi':
fif_raw_fname = op.join(data_path, 'MEG', 'sample',
Expand All @@ -744,6 +743,7 @@ def test_chpi(_bids_validate, tmp_path, format):
ctf_raw_fname = op.join(data_path, 'CTF', 'testdata_ctf.ds')
raw = _read_raw_ctf(ctf_raw_fname)
elif format == 'kit':
kit_data_path = op.join(base_path, 'kit', 'tests', 'data')
kit_raw_fname = op.join(kit_data_path, 'test.sqd')
kit_hpi_fname = op.join(kit_data_path, 'test_mrk.sqd')
kit_electrode_fname = op.join(kit_data_path, 'test.elp')
Expand All @@ -763,18 +763,25 @@ def test_chpi(_bids_validate, tmp_path, format):
if parse_version(mne.__version__) <= parse_version('0.23'):
assert 'ContinuousHeadLocalization' not in meg_json_data
assert 'HeadCoilFrequency' not in meg_json_data
elif format in ['fif_no_chpi', 'kit']:
elif format in ['fif_no_chpi', 'fif']:
if parse_version(mne.__version__) <= parse_version('1.1'):
assert 'ContinuousHeadLocalization' not in meg_json_data
assert 'HeadCoilFrequency' not in meg_json_data
else:
if format == 'fif_no_chpi':
assert meg_json_data['ContinuousHeadLocalization'] is False
assert meg_json_data['HeadCoilFrequency'] == []
elif format == 'fif':
assert meg_json_data['ContinuousHeadLocalization'] is True
assert_array_almost_equal(meg_json_data['HeadCoilFrequency'],
[83., 143., 203., 263., 323.])
elif format == 'kit':
# no cHPI info is contained in the sample data
assert meg_json_data['ContinuousHeadLocalization'] is False
assert meg_json_data['HeadCoilFrequency'] == []
elif format == 'fif':
assert meg_json_data['ContinuousHeadLocalization'] is True
assert_array_almost_equal(meg_json_data['HeadCoilFrequency'],
[83., 143., 203., 263., 323.])
elif format == 'ctf':
assert meg_json_data['ContinuousHeadLocalization'] is True
assert_array_equal(meg_json_data['HeadCoilFrequency'],
np.array([]))
assert meg_json_data['HeadCoilFrequency'] == []


@pytest.mark.filterwarnings(warning_str['channel_unit_changed'])
Expand Down
37 changes: 22 additions & 15 deletions mne_bids/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
from mne.io.constants import FIFF
from mne.io.pick import channel_type, _picks_to_idx
from mne.io import BaseRaw, read_fiducials
from mne.channels.channels import _unit2human
from mne.channels.channels import (_unit2human, _get_meg_system)
from mne.chpi import get_chpi_info
from mne.utils import (check_version, has_nibabel, logger, warn, Bunch,
_validate_type, get_subjects_dir, verbose,
ProgressBar)
Expand Down Expand Up @@ -825,35 +826,41 @@ def _sidecar_json(raw, task, manufacturer, fname, datatype,
}

# Compile cHPI information, if any.
from mne.io.ctf import RawCTF
from mne.io.kit.kit import RawKIT

chpi = False
hpi_freqs = np.array([])
system, _ = _get_meg_system(raw.info)
chpi = None
hpi_freqs = []
if (datatype == 'meg' and
parse_version(mne.__version__) > parse_version('0.23')):
# We need to handle different data formats differently
if isinstance(raw, RawCTF):
if system == 'CTF_275':
try:
mne.chpi.extract_chpi_locs_ctf(raw)
chpi = True
except RuntimeError:
chpi = False
logger.info('Could not find cHPI information in raw data.')
elif isinstance(raw, RawKIT):
elif system == 'KIT':
try:
mne.chpi.extract_chpi_locs_kit(raw)
chpi = True
except (RuntimeError, ValueError):
chpi = False
logger.info('Could not find cHPI information in raw data.')
else:
hpi_freqs, _, _ = mne.chpi.get_chpi_info(info=raw.info,
on_missing='ignore')
if hpi_freqs.size > 0:
chpi = True
elif system in ['122m', '306m']:
# XXX: Remove this version check when support for mne <1.2
# is dropped
if parse_version(mne.__version__) > parse_version('1.1'):
n_active_hpi = mne.chpi.get_active_chpi(raw,
on_missing='ignore')
chpi = bool(n_active_hpi.sum() > 0)
if chpi:
hpi_freqs, _, _ = get_chpi_info(info=raw.info,
on_missing='ignore')
hpi_freqs = list(hpi_freqs)

elif datatype == 'meg':
logger.info('Cannot check for & write continuous head localization '
'information: requires MNE-Python >= 0.24')
chpi = None

# Define datatype-specific JSON dictionaries
ch_info_json_common = [
Expand All @@ -875,7 +882,7 @@ def _sidecar_json(raw, task, manufacturer, fname, datatype,

if chpi is not None:
ch_info_json_meg.append(('ContinuousHeadLocalization', chpi))
ch_info_json_meg.append(('HeadCoilFrequency', list(hpi_freqs)))
ch_info_json_meg.append(('HeadCoilFrequency', hpi_freqs))

if emptyroom_fname is not None:
ch_info_json_meg.append(('AssociatedEmptyRoom', str(emptyroom_fname)))
Expand Down