Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ Bug

- Fix bug with :func:`mne.setup_volume_source_space` when ``volume_label`` was supplied where voxels slightly (in a worst case, about 37% times ``pos`` in distance) outside the voxel-grid-based bounds of regions were errantly included, by `Eric Larson`_

- Fix bug with :func:`mne.io.read_raw_ctf` when reference magnetometers have the compensation grade marked by `Eric Larson`_

- Fix bug with `mne.SourceSpaces.export_volume` with ``use_lut=False`` where no values were written by `Eric Larson`_

- Fix bug with :func:`mne.preprocessing.annotate_movement` where bad data segments, specified in ``raw.annotations``, would be handled incorrectly by `Luke Bloy`_
Expand Down
11 changes: 7 additions & 4 deletions mne/io/ctf/ctf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import numpy as np

from .._digitization import _format_dig_points
from ...utils import verbose, logger, _clean_names, fill_doc, _check_option
from ...utils import (verbose, logger, _clean_names, fill_doc, _check_option,
_validate_type)

from ..base import BaseRaw
from ..utils import _mult_cal_one, _blk_read_lims
Expand Down Expand Up @@ -90,9 +91,11 @@ class RawCTF(BaseRaw):
def __init__(self, directory, system_clock='truncate', preload=False,
verbose=None, clean_names=False): # noqa: D102
# adapted from mne_ctf2fiff.c
if not isinstance(directory, str) or \
not directory.endswith('.ds'):
raise TypeError('directory must be a directory ending with ".ds"')
_validate_type(directory, 'path-like', 'directory')
directory = str(directory)
if not directory.endswith('.ds'):
raise TypeError('directory must be a directory ending with ".ds", '
f'got {directory}')
if not op.isdir(directory):
raise ValueError('directory does not exist: "%s"' % directory)
_check_option('system_clock', system_clock, ['ignore', 'truncate'])
Expand Down
20 changes: 6 additions & 14 deletions mne/io/ctf/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,12 @@ def _at_origin(x):


def _check_comp_ch(cch, kind, desired=None):
if 'reference' in kind.lower():
if cch['grad_order_no'] != 0:
raise RuntimeError('%s channel with non-zero compensation grade %s'
% (kind, cch['grad_order_no']))
else:
if desired is None:
desired = cch['grad_order_no']
if cch['grad_order_no'] != desired:
raise RuntimeError('%s channel with inconsistent compensation '
'grade %s, should be %s'
% (kind, cch['grad_order_no'], desired))
if desired is None:
desired = cch['grad_order_no']
if cch['grad_order_no'] != desired:
raise RuntimeError('%s channel with inconsistent compensation '
'grade %s, should be %s'
% (kind, cch['grad_order_no'], desired))
return desired


Expand Down Expand Up @@ -218,17 +213,14 @@ def _convert_channel_info(res4, t, use_eeg_pos):
# Set the coil type
if cch['sensor_type_index'] == CTF.CTFV_REF_MAG_CH:
ch['kind'] = FIFF.FIFFV_REF_MEG_CH
_check_comp_ch(cch, 'Reference magnetometer')
ch['coil_type'] = FIFF.FIFFV_COIL_CTF_REF_MAG
nref += 1
ch['logno'] = nref
elif cch['sensor_type_index'] == CTF.CTFV_REF_GRAD_CH:
ch['kind'] = FIFF.FIFFV_REF_MEG_CH
if off_diag:
_check_comp_ch(cch, 'Reference off-diagonal gradiometer')
ch['coil_type'] = FIFF.FIFFV_COIL_CTF_OFFDIAG_REF_GRAD
else:
_check_comp_ch(cch, 'Reference gradiometer')
ch['coil_type'] = FIFF.FIFFV_COIL_CTF_REF_GRAD
nref += 1
ch['logno'] = nref
Expand Down
45 changes: 44 additions & 1 deletion mne/io/ctf/tests/test_ctf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
from numpy.testing import assert_allclose, assert_array_equal
import pytest

import mne
from mne import (pick_types, read_annotations, create_info,
events_from_annotations)
events_from_annotations, make_forward_solution)
from mne.transforms import apply_trans
from mne.io import read_raw_fif, read_raw_ctf, RawArray
from mne.io.compensator import get_current_comp
from mne.io.ctf.constants import CTF
from mne.io.tests.test_raw import _test_raw_reader
from mne.tests.test_annotations import _assert_annotations_equal
from mne.utils import (run_tests_if_main, _clean_names, catch_logging,
Expand Down Expand Up @@ -383,4 +385,45 @@ def test_read_ctf_annotations_smoke_test():
_assert_annotations_equal(raw.annotations, annot, 1e-6)


def _read_res4_mag_comp(dsdir):
res = mne.io.ctf.res4._read_res4(dsdir)
for ch in res['chs']:
if ch['sensor_type_index'] == CTF.CTFV_REF_MAG_CH:
ch['grad_order_no'] = 1
return res


def _bad_res4_grad_comp(dsdir):
res = mne.io.ctf.res4._read_res4(dsdir)
for ch in res['chs']:
if ch['sensor_type_index'] == CTF.CTFV_MEG_CH:
ch['grad_order_no'] = 1
break
return res


@testing.requires_testing_data
def test_read_ctf_mag_bad_comp(tmpdir, monkeypatch):
"""Test CTF reader with mag comps and bad comps."""
path = op.join(ctf_dir, ctf_fname_continuous)
raw_orig = read_raw_ctf(path)
assert raw_orig.compensation_grade == 0
monkeypatch.setattr(mne.io.ctf.ctf, '_read_res4', _read_res4_mag_comp)
raw_mag_comp = read_raw_ctf(path)
assert raw_mag_comp.compensation_grade == 0
sphere = mne.make_sphere_model()
src = mne.setup_volume_source_space(pos=50., exclude=5., bem=sphere)
assert src[0]['nuse'] == 26
for grade in (0, 1):
raw_orig.apply_gradient_compensation(grade)
raw_mag_comp.apply_gradient_compensation(grade)
args = (None, src, sphere, True, False)
fwd_orig = make_forward_solution(raw_orig.info, *args)
fwd_mag_comp = make_forward_solution(raw_mag_comp.info, *args)
assert_allclose(fwd_orig['sol']['data'], fwd_mag_comp['sol']['data'])
monkeypatch.setattr(mne.io.ctf.ctf, '_read_res4', _bad_res4_grad_comp)
with pytest.raises(RuntimeError, match='inconsistent compensation grade'):
read_raw_ctf(path)


run_tests_if_main()