Skip to content

Commit 6ccd112

Browse files
authored
MAINT: Properly check nibabel (mne-tools#11578)
1 parent 5ca0141 commit 6ccd112

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+191
-226
lines changed

.github/workflows/compat_minimal.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ jobs:
1919
run:
2020
shell: bash
2121
env:
22-
# TODO: Revert nibabel here pending https://github.com/mne-tools/mne-python/issues/11564
23-
CONDA_DEPENDENCIES: 'numpy scipy matplotlib nibabel'
22+
CONDA_DEPENDENCIES: 'numpy scipy matplotlib'
2423
DEPS: 'minimal'
2524
DISPLAY: ':99.0'
2625
MNE_DONTWRITE_HOME: true

mne/_freesurfer.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
_ensure_trans, read_ras_mni_t, Transform)
1818
from .surface import read_surface, _read_mri_surface
1919
from .utils import (verbose, _validate_type, _check_fname, _check_option,
20-
get_subjects_dir, _require_version, logger)
20+
get_subjects_dir, _import_nibabel, logger)
2121

2222

2323
def _check_subject_dir(subject, subjects_dir):
@@ -33,9 +33,7 @@ def _check_subject_dir(subject, subjects_dir):
3333

3434
def _get_aseg(aseg, subject, subjects_dir):
3535
"""Check that the anatomical segmentation file exists and load it."""
36-
_require_version('nibabel', 'load aseg', '2.1.0')
37-
import nibabel as nib
38-
36+
nib = _import_nibabel('load aseg')
3937
subjects_dir = Path(get_subjects_dir(subjects_dir, raise_error=True))
4038
if not aseg.endswith('aseg'):
4139
raise RuntimeError(
@@ -50,18 +48,6 @@ def _get_aseg(aseg, subject, subjects_dir):
5048
return aseg, aseg_data
5149

5250

53-
def _import_nibabel(why='use MRI files'):
54-
try:
55-
import nibabel as nib
56-
except ImportError as exp:
57-
msg = 'nibabel is required to %s, got:\n%s' % (why, exp)
58-
else:
59-
msg = ''
60-
if msg:
61-
raise ImportError(msg)
62-
return nib
63-
64-
6551
def _reorient_image(img, axcodes='RAS'):
6652
"""Reorient an image to a given orientation.
6753
@@ -84,7 +70,7 @@ def _reorient_image(img, axcodes='RAS'):
8470
-----
8571
.. versionadded:: 0.24
8672
"""
87-
import nibabel as nib
73+
nib = _import_nibabel('reorient MRI image')
8874
orig_data = np.array(img.dataobj).astype(np.float32)
8975
# reorient data to RAS
9076
ornt = nib.orientations.axcodes2ornt(
@@ -229,8 +215,7 @@ def get_volume_labels_from_aseg(mgz_fname, return_colors=False,
229215
230216
.. versionadded:: 0.9.0
231217
"""
232-
import nibabel as nib
233-
218+
nib = _import_nibabel('load MRI atlas data')
234219
mgz_fname = _check_fname(
235220
mgz_fname, overwrite="read", must_exist=True, name="mgz_fname"
236221
)
@@ -486,7 +471,7 @@ def estimate_head_mri_t(subject, subjects_dir=None, verbose=None):
486471

487472
def _ensure_image_in_surface_RAS(image, subject, subjects_dir):
488473
"""Check if the image is in Freesurfer surface RAS space."""
489-
import nibabel as nib
474+
nib = _import_nibabel('load a volume image')
490475
if not isinstance(image, nib.spatialimages.SpatialImage):
491476
image = nib.load(image)
492477
image = nib.MGHImage(image.dataobj.astype(np.float32), image.affine)
@@ -614,8 +599,8 @@ def _read_mri_info(path, units='m', return_img=False, use_nibabel=False):
614599
# This is equivalent but 100x slower, so only use nibabel if we need to
615600
# (later):
616601
if use_nibabel:
617-
import nibabel
618-
hdr = nibabel.load(path).header
602+
nib = _import_nibabel()
603+
hdr = nib.load(path).header
619604
n_orig = hdr.get_vox2ras()
620605
t_orig = hdr.get_vox2ras_tkr()
621606
dims = hdr.get_data_shape()

mne/beamformer/tests/test_dics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def _load_forward():
5858

5959
def _simulate_data(fwd, idx): # Somewhere on the frontal lobe by default
6060
"""Simulate an oscillator on the cortex."""
61+
pytest.importorskip('nibabel')
6162
source_vertno = fwd['src'][0]['vertno'][idx]
6263

6364
sfreq = 50. # Hz.

mne/bem.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from .transforms import _ensure_trans, apply_trans, Transform
3737
from .utils import (verbose, logger, run_subprocess, get_subjects_dir, warn,
3838
_pl, _validate_type, _TempDir, _check_freesurfer_home,
39-
_check_fname, has_nibabel, _check_option, path_like,
39+
_check_fname, _check_option, path_like, _import_nibabel,
4040
_on_missing, _import_h5io_funcs, _ensure_int,
4141
_path_like, _verbose_safe_false, _check_head_radius)
4242

@@ -1283,7 +1283,7 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
12831283
run_subprocess_env(cmd)
12841284
del tempdir # clean up directory
12851285
if op.isfile(T1_mgz):
1286-
new_info = _extract_volume_info(T1_mgz) if has_nibabel() else dict()
1286+
new_info = _extract_volume_info(T1_mgz)
12871287
if not new_info:
12881288
warn('nibabel is not available or the volume info is invalid.'
12891289
'Volume info not updated in the written surface.')
@@ -1339,8 +1339,8 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
13391339

13401340
def _extract_volume_info(mgz):
13411341
"""Extract volume info from a mgz file."""
1342-
import nibabel
1343-
header = nibabel.load(mgz).header
1342+
nib = _import_nibabel()
1343+
header = nib.load(mgz).header
13441344
version = header['version']
13451345
vol_info = dict()
13461346
if version == 1:
@@ -1855,7 +1855,7 @@ def _prepare_env(subject, subjects_dir):
18551855

18561856

18571857
def _write_echos(mri_dir, flash_echos, angle):
1858-
import nibabel as nib
1858+
nib = _import_nibabel('write echoes')
18591859
from nibabel.spatialimages import SpatialImage
18601860
if _path_like(flash_echos):
18611861
flash_echos = nib.load(flash_echos)
@@ -2077,7 +2077,7 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
20772077
raise ValueError(f'Flash 5 image cannot be found at {flash5}.')
20782078
else:
20792079
logger.info(f"Writing flash 5 image at {flash5}")
2080-
import nibabel as nib
2080+
nib = _import_nibabel('write an MRI image')
20812081
nib.save(flash5_img, flash5)
20822082

20832083
if register:

mne/commands/tests/test_commands.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
mne_prepare_bem_model, mne_sys_info)
2525
from mne.datasets import testing
2626
from mne.io import read_raw_fif, read_info
27-
from mne.utils import (requires_mne, requires_freesurfer,
28-
requires_nibabel, ArgvSetter,
27+
from mne.utils import (requires_mne, requires_freesurfer, ArgvSetter,
2928
_stamp_to_dt, _record_warnings)
3029

3130
base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
@@ -220,11 +219,11 @@ def test_surf2bem():
220219
@pytest.mark.timeout(900) # took ~400 s on a local test
221220
@pytest.mark.slowtest
222221
@pytest.mark.ultraslowtest
223-
@requires_nibabel()
224222
@requires_freesurfer('mri_watershed')
225223
@testing.requires_testing_data
226224
def test_watershed_bem(tmp_path):
227225
"""Test mne watershed bem."""
226+
pytest.importorskip('nibabel')
228227
check_usage(mne_watershed_bem)
229228
# from T1.mgz
230229
Mdc = np.array([[-1, 0, 0], [0, 0, -1], [0, 1, 0]])
@@ -338,6 +337,7 @@ def test_flash_bem(tmp_path):
338337
@testing.requires_testing_data
339338
def test_setup_source_space(tmp_path):
340339
"""Test mne setup_source_space."""
340+
pytest.importorskip('nibabel')
341341
check_usage(mne_setup_source_space, force_help=True)
342342
# Using the sample dataset
343343
use_fname = op.join(tmp_path, "sources-src.fif")
@@ -367,6 +367,7 @@ def test_setup_source_space(tmp_path):
367367
@testing.requires_testing_data
368368
def test_setup_forward_model(tmp_path):
369369
"""Test mne setup_forward_model."""
370+
pytest.importorskip('nibabel')
370371
check_usage(mne_setup_forward_model, force_help=True)
371372
# Using the sample dataset
372373
use_fname = op.join(tmp_path, "model-bem.fif")

mne/coreg.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
# namespace, too)
2727
from ._freesurfer import (_read_mri_info, get_mni_fiducials, # noqa: F401
2828
estimate_head_mri_t) # noqa: F401
29-
from ._freesurfer import _import_nibabel
3029
from .label import read_label, Label
3130
from .source_space import (add_source_space_distances, read_source_spaces, # noqa: E501,F401
3231
write_source_spaces)
@@ -41,8 +40,8 @@
4140
rot_to_quat, _angle_between_quats)
4241
from .channels import make_dig_montage
4342
from .utils import (get_config, get_subjects_dir, logger, pformat, verbose,
44-
warn, has_nibabel, fill_doc, _validate_type,
45-
_check_subject, _check_option)
43+
warn, fill_doc, _validate_type,
44+
_check_subject, _check_option, _import_nibabel)
4645
from .viz._3d import _fiducial_coords
4746

4847
# some path templates
@@ -1194,9 +1193,6 @@ def _scale_xfm(subject_to, xfm_fname, mri_name, subject_from, scale,
11941193
# The nibabel warning should already be there in MRI step, if applicable,
11951194
# as we only get here if T1.mgz is present (and thus a scaling was
11961195
# attempted) so we can silently return here.
1197-
if not has_nibabel():
1198-
return
1199-
12001196
fname_from = os.path.join(
12011197
mri_transforms_dirname.format(
12021198
subjects_dir=subjects_dir, subject=subject_from), xfm_fname)

mne/datasets/tests/test_datasets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def _error_download_2(self, fname, downloader, processor):
153153
@requires_good_network
154154
def test_fetch_parcellations(tmp_path):
155155
"""Test fetching parcellations."""
156+
pytest.importorskip('nibabel')
156157
this_subjects_dir = str(tmp_path)
157158
os.mkdir(op.join(this_subjects_dir, 'fsaverage'))
158159
os.mkdir(op.join(this_subjects_dir, 'fsaverage', 'label'))

mne/forward/tests/test_make_forward.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
get_volume_labels_from_aseg)
2020
from mne.surface import _get_ico_surface
2121
from mne.transforms import Transform
22-
from mne.utils import (requires_mne, requires_nibabel, run_subprocess,
23-
catch_logging, requires_mne_mark,
24-
requires_openmeeg_mark)
22+
from mne.utils import (requires_mne, run_subprocess, catch_logging,
23+
requires_mne_mark, requires_openmeeg_mark)
2524
from mne.forward._make_forward import _create_meg_coils, make_forward_dipole
2625
from mne.forward._compute_forward import _magnetic_dipole_field_vec
2726
from mne.forward import Forward, _do_forward_solution, use_coil_def
@@ -234,6 +233,7 @@ def test_make_forward_solution_bti(fname_src_small):
234233
])
235234
def test_make_forward_solution_ctf(tmp_path, fname_src_small, other):
236235
"""Test CTF w/compensation against MNE-C or OpenMEEG."""
236+
pytest.importorskip('nibabel')
237237
src = read_source_spaces(fname_src_small)
238238
raw = read_raw_fif(fname_ctf_raw)
239239
assert raw.compensation_grade == 3
@@ -377,6 +377,7 @@ def test_make_forward_solution_discrete(tmp_path, small_surf_src):
377377
@pytest.fixture(scope='module', params=[testing._pytest_param()])
378378
def small_surf_src():
379379
"""Create a small surface source space."""
380+
pytest.importorskip('nibabel')
380381
src = setup_source_space('sample', 'oct2', subjects_dir=subjects_dir,
381382
add_dist=False)
382383
assert sum(s['nuse'] for s in src) * 3 == n_src_small
@@ -433,9 +434,9 @@ def test_make_forward_solution_sphere(tmp_path, fname_src_small):
433434

434435
@pytest.mark.slowtest
435436
@testing.requires_testing_data
436-
@requires_nibabel()
437437
def test_forward_mixed_source_space(tmp_path):
438438
"""Test making the forward solution for a mixed source space."""
439+
pytest.importorskip('nibabel')
439440
# get the surface source space
440441
rng = np.random.RandomState(0)
441442
surf = read_source_spaces(fname_src)

mne/gui/_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
from matplotlib.figure import Figure
2222
from matplotlib.patches import Rectangle
2323

24-
from .._freesurfer import _import_nibabel
2524
from ..viz.backends.renderer import _get_renderer
2625
from ..viz.utils import safe_event
2726
from ..surface import _read_mri_surface, _marching_cubes
2827
from ..transforms import apply_trans, _frame_to_str
29-
from ..utils import logger, _check_fname, verbose, warn, get_subjects_dir
28+
from ..utils import (logger, _check_fname, verbose, warn, get_subjects_dir,
29+
_import_nibabel)
3030
from ..viz.backends._utils import _qt_safe_window
3131

3232
_IMG_LABELS = [['I', 'P'], ['I', 'L'], ['P', 'L']]

mne/gui/tests/test_core.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,18 @@
99
import pytest
1010

1111
from mne.datasets import testing
12-
from mne.utils import requires_nibabel, catch_logging, use_log_level
12+
from mne.utils import catch_logging, use_log_level
1313
from mne.viz.utils import _fake_click
1414

1515
data_path = testing.data_path(download=False)
1616
subject = "sample"
1717
subjects_dir = data_path / "subjects"
1818

1919

20-
@requires_nibabel()
2120
@testing.requires_testing_data
2221
def test_slice_browser_io(renderer_interactive_pyvistaqt):
2322
"""Test the input/output of the slice browser GUI."""
24-
import nibabel as nib
23+
nib = pytest.importorskip('nibabel')
2524
from mne.gui._core import SliceBrowser
2625
with pytest.raises(ValueError, match='Base image is not aligned to MRI'):
2726
SliceBrowser(nib.MGHImage(
@@ -34,6 +33,7 @@ def test_slice_browser_io(renderer_interactive_pyvistaqt):
3433
@testing.requires_testing_data
3534
def test_slice_browser_display(renderer_interactive_pyvistaqt):
3635
"""Test that the slice browser GUI displays properly."""
36+
pytest.importorskip('nibabel')
3737
from mne.gui._core import SliceBrowser
3838
# test no seghead, fsaverage doesn't have seghead
3939
with pytest.warns(RuntimeWarning, match='`seghead` not found'):

0 commit comments

Comments
 (0)