Coverage for nilearn/maskers/tests/test_nifti_labels_masker.py: 0%
346 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1"""Test the nifti_region module.
3Functions in this file only test features added by the NiftiLabelsMasker class,
4not the underlying functions (clean(), img_to_signals_labels(), etc.). See
5test_masking.py and test_signal.py for details.
6"""
8import numpy as np
9import pandas as pd
10import pytest
11from nibabel import Nifti1Image
12from numpy.testing import assert_almost_equal, assert_array_equal
13from sklearn.utils.estimator_checks import parametrize_with_checks
15from nilearn._utils.data_gen import (
16 generate_labeled_regions,
17 generate_random_img,
18)
19from nilearn._utils.estimator_checks import (
20 check_estimator,
21 nilearn_check_estimator,
22 return_expected_failed_checks,
23)
24from nilearn._utils.tags import SKLEARN_LT_1_6
25from nilearn.conftest import _img_labels
26from nilearn.image import get_data
27from nilearn.maskers import NiftiLabelsMasker, NiftiMasker
29ESTIMATORS_TO_CHECK = [NiftiLabelsMasker()]
31if SKLEARN_LT_1_6:
33 @pytest.mark.parametrize(
34 "estimator, check, name",
35 check_estimator(estimators=ESTIMATORS_TO_CHECK),
36 )
37 def test_check_estimator_sklearn_valid(estimator, check, name): # noqa: ARG001
38 """Check compliance with sklearn estimators."""
39 check(estimator)
41 @pytest.mark.xfail(reason="invalid checks should fail")
42 @pytest.mark.parametrize(
43 "estimator, check, name",
44 check_estimator(estimators=ESTIMATORS_TO_CHECK, valid=False),
45 )
46 def test_check_estimator_sklearn_invalid(estimator, check, name): # noqa: ARG001
47 """Check compliance with sklearn estimators."""
48 check(estimator)
50else:
52 @parametrize_with_checks(
53 estimators=ESTIMATORS_TO_CHECK,
54 expected_failed_checks=return_expected_failed_checks,
55 )
56 def test_check_estimator_sklearn(estimator, check):
57 """Check compliance with sklearn estimators."""
58 check(estimator)
61@pytest.mark.timeout(0)
62@pytest.mark.parametrize(
63 "estimator, check, name",
64 nilearn_check_estimator(
65 estimators=[NiftiLabelsMasker(labels_img=_img_labels())]
66 ),
67)
68def test_check_estimator_nilearn(estimator, check, name): # noqa: ARG001
69 """Check compliance with sklearn estimators."""
70 check(estimator)
73def test_nifti_labels_masker(
74 affine_eye, shape_3d_default, n_regions, length, img_labels
75):
76 """Check working of shape/affine checks."""
77 shape1 = (*shape_3d_default, length)
79 fmri_img, mask11_img = generate_random_img(
80 shape1,
81 affine=affine_eye,
82 )
84 # No exception raised here
85 masker = NiftiLabelsMasker(img_labels, resampling_target=None)
86 signals = masker.fit_transform(fmri_img)
88 assert signals.shape == (length, n_regions)
90 # No exception should be raised either
91 masker = NiftiLabelsMasker(img_labels, resampling_target=None)
93 masker.fit()
95 # Check attributes defined at fit
96 assert masker.n_elements_ == n_regions
98 # now with mask_img
99 masker = NiftiLabelsMasker(
100 img_labels, mask_img=mask11_img, resampling_target=None
101 )
102 signals = masker.fit_transform(fmri_img)
104 assert signals.shape == (length, n_regions)
107def test_nifti_labels_masker_errors(
108 affine_eye, shape_3d_default, n_regions, length
109):
110 """Check working of shape/affine checks."""
111 masker = NiftiLabelsMasker()
112 with pytest.raises(TypeError, match="input should be a NiftiLike object"):
113 masker.fit()
115 shape1 = (*shape_3d_default, length)
117 shape2 = (12, 10, 14, length)
118 affine2 = np.diag((1, 2, 3, 1))
120 fmri12_img, mask12_img = generate_random_img(
121 shape1,
122 affine=affine2,
123 )
124 fmri21_img, mask21_img = generate_random_img(
125 shape2,
126 affine=affine_eye,
127 )
129 labels11_img = generate_labeled_regions(
130 shape1[:3],
131 affine=affine_eye,
132 n_regions=n_regions,
133 )
135 # check exception when transform() called without prior fit()
136 masker11 = NiftiLabelsMasker(labels11_img, resampling_target=None)
138 # Test all kinds of mismatch between shapes and between affines
139 masker11.fit()
140 with pytest.raises(
141 ValueError, match="Images have different affine matrices."
142 ):
143 masker11.transform(fmri12_img)
144 with pytest.raises(ValueError, match="Images have incompatible shapes."):
145 masker11.transform(fmri21_img)
147 masker11 = NiftiLabelsMasker(
148 labels11_img, mask_img=mask12_img, resampling_target=None
149 )
150 with pytest.raises(
151 ValueError, match="Following field of view errors were detected"
152 ):
153 masker11.fit()
155 masker11 = NiftiLabelsMasker(
156 labels11_img, mask_img=mask21_img, resampling_target=None
157 )
158 with pytest.raises(
159 ValueError, match="Following field of view errors were detected"
160 ):
161 masker11.fit()
164def test_nifti_labels_masker_with_nans_and_infs(
165 affine_eye, shape_3d_default, n_regions, length, img_labels
166):
167 """Deal with NaNs and infs in label image.
169 The masker should replace those NaNs and infs with zeros,
170 while raising a warning.
171 """
172 fmri_img, mask_img = generate_random_img(
173 (*shape_3d_default, length),
174 affine=affine_eye,
175 )
177 # Introduce nans with data type float
178 # See issue: https://github.com/nilearn/nilearn/issues/2580
179 data = get_data(img_labels).astype(np.float32)
180 data[:, :, 7] = np.nan
181 data[:, :, 4] = np.inf
182 img_labels = Nifti1Image(data, affine_eye)
184 masker = NiftiLabelsMasker(img_labels, mask_img=mask_img)
186 with pytest.warns(UserWarning, match="Non-finite values detected."):
187 sig = masker.fit_transform(fmri_img)
189 assert sig.shape == (length, n_regions)
190 assert np.all(np.isfinite(sig))
193def test_nifti_labels_masker_with_nans_and_infs_in_data(
194 affine_eye, shape_3d_default, n_regions, length, img_labels
195):
196 """Apply a NiftiLabelsMasker to 4D data containing NaNs and infs.
198 The masker should replace those NaNs and infs with zeros,
199 while raising a warning.
200 """
201 fmri_img, mask_img = generate_random_img(
202 (*shape_3d_default, length),
203 affine=affine_eye,
204 )
206 # Introduce nans with data type float
207 # See issues:
208 # - https://github.com/nilearn/nilearn/issues/2580 (why floats)
209 # - https://github.com/nilearn/nilearn/issues/2711 (why test)
210 fmri_data = get_data(fmri_img).astype(np.float32)
211 fmri_data[:, :, 7, :] = np.nan
212 fmri_data[:, :, 4, 0] = np.inf
213 fmri_img = Nifti1Image(fmri_data, affine_eye)
215 masker = NiftiLabelsMasker(img_labels, mask_img=mask_img)
217 with pytest.warns(UserWarning, match="Non-finite values detected."):
218 sig = masker.fit_transform(fmri_img)
220 assert sig.shape == (length, n_regions)
221 assert np.all(np.isfinite(sig))
224@pytest.mark.parametrize(
225 "strategy, function",
226 [
227 ("mean", np.mean),
228 ("median", np.median),
229 ("sum", np.sum),
230 ("minimum", np.min),
231 ("maximum", np.max),
232 ("standard_deviation", np.std),
233 ("variance", np.var),
234 ],
235)
236def test_nifti_labels_masker_reduction_strategies(
237 affine_eye, strategy, function
238):
239 """Tests NiftiLabelsMasker strategies.
241 1. whether the usage of different reduction strategies work.
242 2. whether unrecognized strategies raise a ValueError
243 3. whether the default option is backwards compatible (calls "mean")
244 """
245 test_values = [-2.0, -1.0, 0.0, 1.0, 2]
247 img_data = np.array([[test_values, test_values]])
249 labels_data = np.array([[[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]]], dtype=np.int8)
251 img = Nifti1Image(img_data, affine_eye)
252 labels = Nifti1Image(labels_data, affine_eye)
254 # What NiftiLabelsMasker should return for each reduction strategy?
255 expected_result = function(test_values)
257 masker = NiftiLabelsMasker(labels, strategy=strategy)
258 # Here passing [img] within a list because it's a 3D object.
259 result = masker.fit_transform([img]).squeeze()
261 assert result == expected_result
263 default_masker = NiftiLabelsMasker(labels)
265 assert default_masker.strategy == "mean"
268def test_nifti_labels_masker_reduction_strategies_error(affine_eye):
269 """Tests NiftiLabelsMasker invalid strategy."""
270 labels_data = np.array([[[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]]], dtype=np.int8)
272 labels = Nifti1Image(labels_data, affine_eye)
274 with pytest.raises(ValueError, match="Invalid strategy 'TESTRAISE'"):
275 masker = NiftiLabelsMasker(labels, strategy="TESTRAISE")
276 masker.fit()
279def test_nifti_labels_masker_resampling_errors(img_labels):
280 """Test errors of resampling in NiftiLabelsMasker."""
281 with pytest.raises(
282 ValueError,
283 match="invalid value for 'resampling_target' parameter: mask",
284 ):
285 masker = NiftiLabelsMasker(img_labels, resampling_target="mask")
286 masker.fit()
288 with pytest.raises(
289 ValueError,
290 match="invalid value for 'resampling_target' parameter: invalid",
291 ):
292 masker = NiftiLabelsMasker(
293 img_labels,
294 resampling_target="invalid",
295 )
296 masker.fit()
299def test_nifti_labels_masker_resampling_to_data(affine_eye, n_regions, length):
300 """Test resampling to data in NiftiLabelsMasker."""
301 # mask
302 shape2 = (8, 9, 10, length)
303 # maps
304 shape3 = (16, 18, 20)
306 _, mask_img = generate_random_img(
307 shape2,
308 affine=affine_eye,
309 )
311 labels_img = generate_labeled_regions(shape3, n_regions, affine=affine_eye)
313 # Test with data and atlas of different shape:
314 # the atlas should be resampled to the data
315 shape22 = (5, 5, 6, length)
316 affine2 = 2 * affine_eye
317 affine2[-1, -1] = 1
319 fmri_img, _ = generate_random_img(
320 shape22,
321 affine=affine2,
322 )
324 masker = NiftiLabelsMasker(
325 labels_img, mask_img=mask_img, resampling_target="data"
326 )
327 masker.fit_transform(fmri_img)
329 assert_array_equal(masker.labels_img_.affine, affine2)
332@pytest.mark.parametrize("resampling_target", ["data", "labels"])
333def test_nifti_labels_masker_resampling(
334 affine_eye,
335 shape_3d_default,
336 resampling_target,
337 length,
338 img_labels,
339):
340 """Test to return resampled labels having number of labels \
341 equal with transformed shape of 2nd dimension.
343 See https://github.com/nilearn/nilearn/issues/1673
344 """
345 shape = (*shape_3d_default, length)
346 affine = 2 * affine_eye
348 fmri_img, _ = generate_random_img(shape, affine=affine)
350 masker = NiftiLabelsMasker(
351 labels_img=img_labels, resampling_target=resampling_target
352 )
353 if resampling_target == "data":
354 with pytest.warns(
355 UserWarning,
356 match="After resampling the label image "
357 "to the data image, the following "
358 "labels were removed",
359 ):
360 signals = masker.fit_transform(fmri_img)
361 else:
362 signals = masker.fit_transform(fmri_img)
364 resampled_labels_img = masker.labels_img_
365 n_resampled_labels = len(np.unique(get_data(resampled_labels_img)))
367 assert n_resampled_labels - 1 == signals.shape[1]
369 # inverse transform
370 compressed_img = masker.inverse_transform(signals)
372 # Test that compressing the image a second time should yield an image
373 # with the same data as compressed_img.
374 signals2 = masker.fit_transform(fmri_img)
375 # inverse transform again
376 compressed_img2 = masker.inverse_transform(signals2)
378 assert_array_equal(get_data(compressed_img), get_data(compressed_img2))
381def test_nifti_labels_masker_resampling_to_labels(
382 affine_eye, shape_3d_default, n_regions, length
383):
384 """Test resampling to labels in NiftiLabelsMasker."""
385 # fmri
386 shape1 = (*shape_3d_default, length)
387 # mask
388 shape2 = (16, 17, 18, length)
389 # labels
390 shape3 = (13, 14, 15)
392 # With data of the same affine
393 fmri_img, _ = generate_random_img(
394 shape1,
395 affine=affine_eye,
396 )
397 _, mask_img = generate_random_img(
398 shape2,
399 affine=affine_eye,
400 )
402 labels_img = generate_labeled_regions(
403 shape3,
404 n_regions,
405 affine=affine_eye,
406 )
408 masker = NiftiLabelsMasker(
409 labels_img, mask_img=mask_img, resampling_target="labels"
410 )
412 signals = masker.fit_transform(fmri_img)
414 assert_almost_equal(masker.labels_img_.affine, labels_img.affine)
415 assert masker.labels_img_.shape == labels_img.shape
416 assert_almost_equal(masker.mask_img_.affine, masker.labels_img_.affine)
417 assert masker.mask_img_.shape == masker.labels_img_.shape[:3]
419 assert signals.shape == (length, n_regions)
421 fmri11_img_r = masker.inverse_transform(signals)
423 assert_almost_equal(fmri11_img_r.affine, masker.labels_img_.affine)
424 assert fmri11_img_r.shape == (masker.labels_img_.shape[:3] + (length,))
427def test_nifti_labels_masker_resampling_to_clipped_labels(
428 affine_eye, shape_3d_default, n_regions, length
429):
430 """Test with clipped labels.
432 Mask does not contain all labels.
434 Shapes do matter in that case,
435 because there is some resampling taking place.
436 """
437 # fmri
438 shape1 = (*shape_3d_default, length)
439 # mask
440 shape2 = (8, 9, 10, length)
441 # maps
442 shape3 = (16, 18, 20)
444 fmri11_img, _ = generate_random_img(
445 shape1,
446 affine=affine_eye,
447 )
448 _, mask22_img = generate_random_img(
449 shape2,
450 affine=affine_eye,
451 )
453 labels33_img = generate_labeled_regions(
454 shape3, n_regions, affine=affine_eye
455 )
457 masker = NiftiLabelsMasker(
458 labels33_img, mask_img=mask22_img, resampling_target="labels"
459 )
461 signals = masker.fit_transform(fmri11_img)
463 assert_almost_equal(masker.labels_img_.affine, labels33_img.affine)
465 assert masker.labels_img_.shape == labels33_img.shape
466 assert_almost_equal(masker.mask_img_.affine, masker.labels_img_.affine)
467 assert masker.mask_img_.shape == masker.labels_img_.shape[:3]
469 uniq_labels = np.unique(get_data(masker.labels_img_))
470 assert uniq_labels[0] == 0
471 assert len(uniq_labels) - 1 == n_regions
473 assert signals.shape == (length, n_regions)
474 # Some regions have been clipped. Resulting signal must be zero
475 assert (signals.var(axis=0) == 0).sum() < n_regions
477 fmri11_img_r = masker.inverse_transform(signals)
479 assert_almost_equal(fmri11_img_r.affine, masker.labels_img_.affine)
480 assert fmri11_img_r.shape == (masker.labels_img_.shape[:3] + (length,))
483def test_nifti_labels_masker_resampling_to_none(
484 affine_eye, length, shape_3d_default, img_labels
485):
486 """Test resampling to None in NiftiLabelsMasker.
488 All inputs must have same affine to avoid errors.
489 """
490 fmri_img, mask_img = generate_random_img(
491 shape=(*shape_3d_default, length),
492 affine=affine_eye,
493 )
495 masker = NiftiLabelsMasker(
496 img_labels, mask_img=mask_img, resampling_target=None
497 )
498 masker.fit_transform(fmri_img)
500 fmri_img, _ = generate_random_img(
501 (*shape_3d_default, length),
502 affine=affine_eye * 2,
503 )
504 masker = NiftiLabelsMasker(
505 img_labels, mask_img=mask_img, resampling_target=None
506 )
507 with pytest.raises(
508 ValueError, match="Following field of view errors were detected"
509 ):
510 masker.fit_transform(fmri_img)
513def test_standardization(rng, affine_eye, shape_3d_default, img_labels):
514 """Check output properly standardized with 'standardize' parameter."""
515 n_samples = 400
517 signals = rng.standard_normal(size=(np.prod(shape_3d_default), n_samples))
518 means = (
519 rng.standard_normal(size=(np.prod(shape_3d_default), 1)) * 50 + 1000
520 )
521 signals += means
522 img = Nifti1Image(
523 signals.reshape((*shape_3d_default, n_samples)),
524 affine_eye,
525 )
527 # Unstandarized
528 masker = NiftiLabelsMasker(img_labels, standardize=False)
529 unstandarized_label_signals = masker.fit_transform(img)
531 # z-score
532 masker = NiftiLabelsMasker(img_labels, standardize="zscore_sample")
533 trans_signals = masker.fit_transform(img)
535 assert_almost_equal(trans_signals.mean(0), 0)
536 assert_almost_equal(trans_signals.std(0), 1, decimal=3)
538 # psc
539 masker = NiftiLabelsMasker(img_labels, standardize="psc")
540 trans_signals = masker.fit_transform(img)
542 assert_almost_equal(trans_signals.mean(0), 0)
543 assert_almost_equal(
544 trans_signals,
545 (
546 unstandarized_label_signals
547 / unstandarized_label_signals.mean(0)
548 * 100
549 - 100
550 ),
551 )
554def test_nifti_labels_masker_with_mask(
555 shape_3d_default, affine_eye, length, img_labels
556):
557 """Test NiftiLabelsMasker with a separate mask_img parameter."""
558 shape = (*shape_3d_default, length)
559 fmri_img, mask_img = generate_random_img(shape, affine=affine_eye)
561 masker = NiftiLabelsMasker(
562 img_labels, resampling_target=None, mask_img=mask_img
563 )
564 signals = masker.fit_transform(fmri_img)
566 bg_masker = NiftiMasker(mask_img)
567 tmp = bg_masker.fit_transform(img_labels)
568 masked_labels = bg_masker.inverse_transform(tmp)
570 masked_masker = NiftiLabelsMasker(
571 masked_labels, resampling_target=None, mask_img=mask_img
572 )
573 masked_signals = masked_masker.fit_transform(fmri_img)
575 assert np.allclose(signals, masked_signals)
577 # masker.region_atlas_ should be the same as the masked_labels
578 # masked_labels is a 3D image with shape (10,10,10)
579 masked_labels_data = get_data(masked_labels)[:, :, :]
580 assert np.allclose(get_data(masker.region_atlas_), masked_labels_data)
583@pytest.mark.parametrize(
584 "background",
585 [
586 None,
587 "background",
588 "Background",
589 ],
590)
591def test_warning_n_labels_not_equal_n_regions(
592 shape_3d_default, affine_eye, background, n_regions
593):
594 """Check that n_labels provided match n_regions in image."""
595 labels_img = generate_labeled_regions(
596 shape_3d_default[:3],
597 affine=affine_eye,
598 n_regions=n_regions,
599 )
600 region_names = generate_labels(n_regions + 2, background=background)
601 with pytest.warns(
602 UserWarning,
603 match="Too many names for the indices. Dropping excess names values.",
604 ):
605 masker = NiftiLabelsMasker(
606 labels_img,
607 labels=region_names,
608 )
609 masker.fit()
612def test_check_labels_errors(shape_3d_default, affine_eye):
613 """Check that invalid types for labels are caught at fit time."""
614 labels_img = generate_labeled_regions(
615 shape_3d_default,
616 affine=affine_eye,
617 n_regions=2,
618 )
620 with pytest.raises(TypeError, match="'labels' must be a list."):
621 NiftiLabelsMasker(
622 labels_img,
623 labels={"foo", "bar", "baz"},
624 ).fit()
626 with pytest.raises(
627 TypeError, match="All elements of 'labels' must be a string"
628 ):
629 masker = NiftiLabelsMasker(
630 labels_img,
631 labels=[1, 2, 3],
632 )
633 masker.fit()
636@pytest.mark.parametrize(
637 "background",
638 [
639 None,
640 "background",
641 "Background",
642 ], # In case the list of labels includes one for background
643)
644@pytest.mark.parametrize(
645 "dtype",
646 ["int32", "float32"], # In case regions are labeled with floats
647)
648@pytest.mark.parametrize(
649 "affine_data",
650 [
651 None, # no resampling
652 np.diag(
653 (4, 4, 4, 4) # with resampling
654 ), # region_names_ matches signals after resampling drops labels
655 ],
656)
657def test_region_names(
658 shape_3d_default, affine_eye, background, affine_data, dtype, n_regions
659):
660 """Test region_names_ attribute in NiftiLabelsMasker."""
661 resampling = True
662 if affine_data is None:
663 resampling = False
664 affine_data = affine_eye
665 fmri_img, _ = generate_random_img(shape_3d_default, affine=affine_data)
666 labels_img = generate_labeled_regions(
667 shape_3d_default,
668 affine=affine_eye,
669 n_regions=n_regions,
670 dtype=dtype,
671 )
673 masker = NiftiLabelsMasker(
674 labels_img,
675 labels=generate_labels(n_regions, background=background),
676 resampling_target="data",
677 )
679 signals = masker.fit_transform(fmri_img)
681 tmp = generate_labels(n_regions, background=background)
682 if background is None:
683 expected_lut = generate_expected_lut(["Background", *tmp])
684 else:
685 expected_lut = generate_expected_lut(tmp)
686 check_lut(masker, expected_lut)
688 region_names = generate_labels(n_regions, background=background)
689 check_region_names_after_fit(
690 masker,
691 signals,
692 region_names,
693 background,
694 resampling,
695 )
698def generate_expected_lut(region_names):
699 """Generate a look up table based on a list of regions names."""
700 if "background" in region_names:
701 idx = region_names.index("background")
702 region_names[idx] = "Background"
703 return pd.DataFrame(
704 {"name": region_names, "index": list(range(len(region_names)))}
705 )
708def check_region_names_after_fit(
709 masker,
710 signals,
711 region_names,
712 background,
713 resampling=False,
714):
715 """Perform several checks on the expected attributes of the masker.
717 - region_names_ does not include background
718 should have same length as signals
719 - region_ids_ does include background
720 - region_names_ should be the same as the region names
721 passed to the masker minus that for "background"
722 """
723 n_regions = signals.shape[0] if signals.ndim == 1 else signals.shape[1]
725 assert len(masker.region_names_) == n_regions
726 assert len(list(masker.region_ids_.items())) == n_regions + 1
728 # for coverage
729 masker.labels_ # noqa: B018
730 masker._region_id_name # noqa: B018
732 # resampling may drop some labels so we do not check the region names
733 # in this case
734 if not resampling:
735 region_names_after_fit = [
736 masker.region_names_[i] for i in masker.region_names_
737 ]
738 region_names_after_fit.sort()
739 region_names.sort()
740 if background:
741 region_names.pop(region_names.index(background))
742 assert region_names_after_fit == region_names
745def check_lut(masker, expected_lut):
746 """Check content of the look up table."""
747 assert masker.background_label in masker.lut_["index"].to_list()
748 assert "Background" in masker.lut_["name"].to_list()
749 assert masker.lut_["name"].to_list() == expected_lut["name"].to_list()
750 assert masker.lut_["index"].to_list() == expected_lut["index"].to_list()
753@pytest.mark.parametrize(
754 "background",
755 [
756 None,
757 "background",
758 "Background",
759 ],
760)
761@pytest.mark.parametrize(
762 "affine_data",
763 [
764 None, # no resampling
765 np.diag(
766 (4, 4, 4, 4) # with resampling
767 ), # region_names_ matches signals after resampling drops labels
768 ],
769)
770@pytest.mark.parametrize(
771 "masking",
772 [
773 False, # no masking
774 True, # with masking
775 ],
776)
777@pytest.mark.parametrize(
778 "keep_masked_labels",
779 [
780 False,
781 True,
782 ],
783)
784def test_region_names_ids_match_after_fit(
785 shape_3d_default,
786 affine_eye,
787 background,
788 affine_data,
789 n_regions,
790 masking,
791 keep_masked_labels,
792 img_labels,
793):
794 """Test that the same region names and ids correspond after fit."""
795 if affine_data is None:
796 # no resampling
797 affine_data = affine_eye
798 fmri_img, _ = generate_random_img(shape_3d_default, affine=affine_data)
800 region_names = generate_labels(n_regions, background=background)
801 region_ids = list(np.unique(get_data(img_labels)))
803 if masking:
804 # create a mask_img with 3 regions
805 labels_data = get_data(img_labels)
806 mask_data = (
807 (labels_data == 1) + (labels_data == 2) + (labels_data == 5)
808 )
809 mask_img = Nifti1Image(mask_data.astype(np.int8), img_labels.affine)
810 else:
811 mask_img = None
813 masker = NiftiLabelsMasker(
814 img_labels,
815 labels=region_names,
816 resampling_target="data",
817 mask_img=mask_img,
818 keep_masked_labels=keep_masked_labels,
819 )
821 masker.fit_transform(fmri_img)
823 tmp = generate_labels(n_regions, background=background)
824 if background is None:
825 expected_lut = generate_expected_lut(["Background", *tmp])
826 else:
827 expected_lut = generate_expected_lut(tmp)
828 check_lut(masker, expected_lut)
830 check_region_names_ids_match_after_fit(
831 masker, region_names, region_ids, background
832 )
835def check_region_names_ids_match_after_fit(
836 masker, region_names, region_ids, background
837):
838 """Check the region names and ids correspondence.
840 Check that the same region names and ids correspond to each other
841 after fit by comparing with before fit.
842 """
843 # region_ids includes background, so we make
844 # sure that the region_names also include it
845 if not background:
846 region_names.insert(0, "background")
847 # if they don't have the same length, we can't compare them
848 if len(region_names) == len(region_ids):
849 region_id_names = {
850 region_id: region_names[i]
851 for i, region_id in enumerate(region_ids)
852 }
853 for key, region_name in masker.region_names_.items():
854 assert region_id_names[masker.region_ids_[key]] == region_name
857def generate_labels(n_regions, background=""):
858 """Create list of strings to use as labels."""
859 labels = []
860 if background:
861 labels.append(background)
862 labels.extend([f"region_{i + 1!s}" for i in range(n_regions)])
863 return labels
866@pytest.mark.parametrize("background", [None, "background", "Background"])
867def test_region_names_with_non_sequential_labels(
868 shape_3d_default, affine_eye, background
869):
870 """Test for atlases with region id that are not consecutive.
872 See the AAL atlas for an example of this.
873 """
874 labels = [2001, 2002, 2101, 2102, 9170]
875 fmri_img, _ = generate_random_img(shape_3d_default, affine=affine_eye)
876 labels_img = generate_labeled_regions(
877 shape_3d_default[:3],
878 affine=affine_eye,
879 n_regions=len(labels),
880 labels=[0, *labels],
881 )
883 masker = NiftiLabelsMasker(
884 labels_img,
885 labels=generate_labels(len(labels), background=background),
886 resampling_target=None,
887 )
889 signals = masker.fit_transform(fmri_img)
891 expected_lut = pd.DataFrame(
892 {
893 "index": [0, *labels],
894 "name": ["Background"]
895 + [f"region_{i}" for i in range(1, len(labels) + 1)],
896 }
897 )
898 check_lut(masker, expected_lut)
900 region_names = generate_labels(len(labels), background=background)
902 check_region_names_after_fit(masker, signals, region_names, background)
905@pytest.mark.parametrize("background", [None, "background", "Background"])
906def test_more_labels_than_actual_region_in_atlas(
907 shape_3d_default, affine_eye, background, n_regions, img_labels
908):
909 """Test region_names_ property in NiftiLabelsMasker.
911 See fetch_atlas_destrieux_2009 for example.
912 Some labels have no associated voxels.
913 """
914 n_regions_in_labels = n_regions + 5
916 region_names = generate_labels(n_regions_in_labels, background=background)
918 masker = NiftiLabelsMasker(
919 img_labels,
920 labels=region_names,
921 resampling_target="data",
922 )
924 fmri_img, _ = generate_random_img(shape_3d_default, affine=affine_eye)
925 with pytest.warns(
926 UserWarning,
927 match="Too many names for the indices. Dropping excess names values.",
928 ):
929 masker.fit_transform(fmri_img)
932@pytest.mark.parametrize("background", [None, "Background"])
933def test_pass_lut(
934 shape_3d_default, affine_eye, n_regions, img_labels, tmp_path, background
935):
936 """Smoke test to pass LUT directly or as file."""
937 region_names = generate_labels(n_regions, background=background)
938 if background:
939 lut = pd.DataFrame(
940 {"name": region_names, "index": list(range(n_regions + 1))}
941 )
942 else:
943 lut = pd.DataFrame(
944 {
945 "name": ["Background", *region_names],
946 "index": list(range(n_regions + 1)),
947 }
948 )
950 fmri_img, _ = generate_random_img(shape_3d_default, affine=affine_eye)
952 masker = NiftiLabelsMasker(
953 img_labels,
954 lut=lut,
955 )
957 masker.fit_transform(fmri_img)
959 assert masker.lut_["index"].to_list() == lut["index"].to_list()
960 assert masker.lut_["name"].to_list() == lut["name"].to_list()
962 lut_file = tmp_path / "lut.csv"
963 lut.to_csv(lut_file, index=False)
964 masker = NiftiLabelsMasker(
965 img_labels,
966 lut=lut_file,
967 )
969 masker.fit_transform(fmri_img)
972def test_pass_lut_error(shape_3d_default, affine_eye, n_regions, img_labels):
973 """Cannot pass both LUT and labels."""
974 region_names = generate_labels(n_regions, background=None)
975 lut = pd.DataFrame(
976 {
977 "name": ["Background", *region_names],
978 "index": list(range(n_regions + 1)),
979 }
980 )
982 fmri_img, _ = generate_random_img(shape_3d_default, affine=affine_eye)
984 with pytest.raises(
985 ValueError, match="Pass either labels or a lookup table"
986 ):
987 NiftiLabelsMasker(img_labels, lut=lut, labels=region_names).fit()