Coverage for nilearn/regions/tests/test_signal_extraction.py: 0%
309 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 for "region" module."""
3import warnings
5import numpy as np
6import pytest
7from nibabel import Nifti1Image
8from numpy.testing import assert_almost_equal, assert_equal
10from nilearn._utils.data_gen import (
11 generate_fake_fmri,
12 generate_labeled_regions,
13 generate_maps,
14 generate_timeseries,
15)
16from nilearn._utils.exceptions import DimensionError
17from nilearn._utils.testing import write_imgs_to_path
18from nilearn.conftest import _affine_eye, _shape_3d_default
19from nilearn.image import get_data, new_img_like
20from nilearn.maskers import NiftiLabelsMasker
21from nilearn.regions.signal_extraction import (
22 _check_shape_and_affine_compatibility,
23 _trim_maps,
24 img_to_signals_labels,
25 img_to_signals_maps,
26 signals_to_img_labels,
27 signals_to_img_maps,
28)
30_3D_EXPECTED_ERROR_MSG = (
31 "Input data has incompatible dimensionality: "
32 "Expected dimension is 3D and you provided "
33 "a 4D image"
34)
36_4D_EXPECTED_ERROR_MSG = (
37 "Input data has incompatible dimensionality: "
38 "Expected dimension is 4D and you provided "
39 "a 3D image"
40)
42SHAPE_ERROR_MSG = "Images have incompatible shapes."
44AFFINE_ERROR_MSG = "Images have different affine matrices."
46EPS = np.finfo(np.float64).eps
48INF = 1000 * np.finfo(np.float32).eps
50N_REGIONS = 8
52N_TIMEPOINTS = 17
55def _make_label_data(shape=None):
56 if shape is None:
57 shape = _shape_3d_default()
58 labels_data = np.zeros(shape, dtype="int32")
59 h0, h1, h2 = (s // 2 for s in shape)
60 labels_data[:h0, :h1, :h2] = 1
61 labels_data[:h0, :h1, h2:] = 2
62 labels_data[:h0, h1:, :h2] = 3
63 labels_data[:h0, h1:, h2:] = 4
64 labels_data[h0:, :h1, :h2] = 5
65 labels_data[h0:, :h1, h2:] = 6
66 labels_data[h0:, h1:, :h2] = 7
67 labels_data[h0:, h1:, h2:] = 8
68 return labels_data
71def _create_mask_with_3_regions_from_labels_data(labels_data, affine):
72 """Create a mask containing only 3 regions."""
73 mask_data = (labels_data == 1) + (labels_data == 2) + (labels_data == 5)
74 return Nifti1Image(mask_data.astype(np.int8), affine)
77@pytest.fixture
78def labels_data():
79 return _make_label_data()
82@pytest.fixture
83def labels_img():
84 return Nifti1Image(_make_label_data(_shape_3d_default()), _affine_eye())
87@pytest.fixture
88def mask_img():
89 mask_data = np.zeros(_shape_3d_default())
90 mask_data[1:-1, 1:-1, 1:-1] = 1
91 return Nifti1Image(mask_data, _affine_eye())
94@pytest.fixture
95def signals():
96 return generate_timeseries(n_timepoints=N_TIMEPOINTS, n_features=N_REGIONS)
99@pytest.fixture
100def fmri_img():
101 return generate_fake_fmri(shape=_shape_3d_default(), affine=_affine_eye())[
102 0
103 ]
106@pytest.fixture
107def labeled_regions():
108 labels = list(range(N_REGIONS + 1)) # 0 is background
109 return generate_labeled_regions(
110 shape=_shape_3d_default(), n_regions=N_REGIONS, labels=labels
111 )
114def _all_voxel_of_each_region_have_same_values(
115 data, labels_data, n_regions, signals
116):
117 for n in range(1, n_regions + 1):
118 sigs = data[labels_data == n, :]
119 assert_almost_equal(sigs[0, :], signals[:, n - 1])
120 assert abs(sigs - sigs[0, :]).max() < EPS
123def test_check_shape_and_affine_compatibility_without_dim(img_3d_zeros_eye):
124 """Ensure correct behavior for valid data without dim."""
125 with warnings.catch_warnings():
126 warnings.simplefilter("error")
127 _check_shape_and_affine_compatibility(
128 img1=img_3d_zeros_eye, img2=img_3d_zeros_eye
129 )
132def test_check_shape_and_affine_compatibility_with_dim(
133 img_3d_zeros_eye, img_4d_zeros_eye
134):
135 """Ensure correct behavior for valid data without dim."""
136 with warnings.catch_warnings():
137 warnings.simplefilter("error")
138 _check_shape_and_affine_compatibility(
139 img1=img_4d_zeros_eye, img2=img_3d_zeros_eye, dim=3
140 )
143@pytest.mark.parametrize(
144 "test_shape, test_affine, msg",
145 [
146 ((8, 9, 11), _affine_eye(), SHAPE_ERROR_MSG),
147 (_shape_3d_default(), 2 * _affine_eye(), AFFINE_ERROR_MSG),
148 ],
149)
150def test_check_shape_and_affine_compatibility_error(
151 img_3d_zeros_eye, test_shape, test_affine, msg
152):
153 img2 = Nifti1Image(np.zeros(test_shape), test_affine)
155 with pytest.raises(ValueError, match=msg):
156 _check_shape_and_affine_compatibility(img1=img_3d_zeros_eye, img2=img2)
159def test_errors_3d(img_3d_zeros_eye, img_4d_zeros_eye):
160 """Verify that 3D images are refused."""
161 wrong_dim_image = img_3d_zeros_eye
163 with pytest.raises(DimensionError, match=_4D_EXPECTED_ERROR_MSG):
164 img_to_signals_labels(
165 imgs=wrong_dim_image, labels_img=img_3d_zeros_eye
166 )
168 with pytest.raises(DimensionError, match=_4D_EXPECTED_ERROR_MSG):
169 img_to_signals_maps(imgs=img_4d_zeros_eye, maps_img=wrong_dim_image)
172def test_errors_4d_labels(img_4d_zeros_eye):
173 """Verify that 4D images are refused."""
174 wrong_dim_label_img = img_4d_zeros_eye
176 with pytest.raises(DimensionError, match=_3D_EXPECTED_ERROR_MSG):
177 img_to_signals_labels(
178 imgs=img_4d_zeros_eye, labels_img=wrong_dim_label_img
179 )
181 with pytest.raises(DimensionError, match=_3D_EXPECTED_ERROR_MSG):
182 signals_to_img_labels(
183 signals=img_4d_zeros_eye, labels_img=wrong_dim_label_img
184 )
187def test_errors_4d_masks(img_3d_zeros_eye, img_4d_zeros_eye):
188 """Verify that 4D images are refused."""
189 wrong_dim_mask_img = img_4d_zeros_eye
191 with pytest.raises(DimensionError, match=_3D_EXPECTED_ERROR_MSG):
192 img_to_signals_labels(
193 imgs=img_4d_zeros_eye,
194 labels_img=img_3d_zeros_eye,
195 mask_img=wrong_dim_mask_img,
196 )
198 with pytest.raises(DimensionError, match=_3D_EXPECTED_ERROR_MSG):
199 signals_to_img_labels(
200 signals=img_4d_zeros_eye,
201 labels_img=img_3d_zeros_eye,
202 mask_img=wrong_dim_mask_img,
203 )
205 with pytest.raises(DimensionError, match=_3D_EXPECTED_ERROR_MSG):
206 img_to_signals_maps(
207 imgs=img_4d_zeros_eye,
208 maps_img=img_4d_zeros_eye,
209 mask_img=wrong_dim_mask_img,
210 )
213@pytest.mark.parametrize(
214 "shape, affine, error_msg",
215 [
216 ((8, 9, 11), _affine_eye(), SHAPE_ERROR_MSG),
217 (_shape_3d_default(), 2 * _affine_eye(), AFFINE_ERROR_MSG),
218 ],
219)
220def test_img_to_signals_labels_bad_labels_input(
221 img_4d_zeros_eye, shape, affine, error_msg
222):
223 bad_img = Nifti1Image(np.zeros(shape), affine)
225 with pytest.raises(ValueError, match=error_msg):
226 img_to_signals_labels(imgs=img_4d_zeros_eye, labels_img=bad_img)
229@pytest.mark.parametrize(
230 "shape, affine, error_msg",
231 [
232 ((8, 9, 11), _affine_eye(), SHAPE_ERROR_MSG),
233 (_shape_3d_default(), 2 * _affine_eye(), AFFINE_ERROR_MSG),
234 ],
235)
236def test_img_to_signals_labels_bad_mask_input(
237 img_4d_zeros_eye, img_3d_zeros_eye, shape, affine, error_msg
238):
239 bad_img = Nifti1Image(np.zeros(shape), affine)
241 with pytest.raises(ValueError, match=error_msg):
242 img_to_signals_labels(
243 imgs=img_4d_zeros_eye,
244 labels_img=img_3d_zeros_eye,
245 mask_img=bad_img,
246 )
249def test_img_to_signals_labels_error_strategy(
250 img_4d_zeros_eye, img_3d_zeros_eye
251):
252 with pytest.raises(ValueError, match="Invalid strategy"):
253 img_to_signals_labels(
254 imgs=img_4d_zeros_eye, labels_img=img_3d_zeros_eye, strategy="foo"
255 )
258@pytest.mark.parametrize(
259 "shape, affine, error_msg",
260 [
261 ((8, 9, 11), _affine_eye(), SHAPE_ERROR_MSG),
262 (_shape_3d_default(), 2 * _affine_eye(), AFFINE_ERROR_MSG),
263 ],
264)
265def test_signals_to_img_labels_bad_label_input(
266 img_4d_zeros_eye, img_3d_zeros_eye, shape, affine, error_msg
267):
268 bad_img = Nifti1Image(np.zeros(shape), affine)
270 with pytest.raises(ValueError, match=error_msg):
271 signals_to_img_labels(
272 signals=img_4d_zeros_eye,
273 labels_img=bad_img,
274 mask_img=img_3d_zeros_eye,
275 )
278@pytest.mark.parametrize(
279 "shape, affine, error_msg",
280 [
281 ((8, 9, 11), _affine_eye(), SHAPE_ERROR_MSG),
282 (_shape_3d_default(), 2 * _affine_eye(), AFFINE_ERROR_MSG),
283 ],
284)
285def test_signals_to_img_labels_bad_mask_input(
286 img_4d_zeros_eye, img_3d_zeros_eye, shape, affine, error_msg
287):
288 bad_img = Nifti1Image(np.zeros(shape), affine)
290 with pytest.raises(ValueError, match=error_msg):
291 signals_to_img_labels(
292 signals=img_4d_zeros_eye,
293 labels_img=img_3d_zeros_eye,
294 mask_img=bad_img,
295 )
298@pytest.mark.parametrize(
299 "shape, affine, error_msg",
300 [
301 ((8, 9, 11, 7), _affine_eye(), SHAPE_ERROR_MSG),
302 ((*_shape_3d_default(), 7), 2 * _affine_eye(), AFFINE_ERROR_MSG),
303 ],
304)
305def test_img_to_signals_maps_bad_maps(
306 img_4d_zeros_eye, shape, affine, error_msg
307):
308 bad_img = Nifti1Image(np.zeros(shape), affine)
310 with pytest.raises(ValueError, match=error_msg):
311 img_to_signals_maps(
312 imgs=img_4d_zeros_eye,
313 maps_img=bad_img,
314 )
317@pytest.mark.parametrize(
318 "shape, affine, error_msg",
319 [
320 ((8, 9, 11), _affine_eye(), SHAPE_ERROR_MSG),
321 (_shape_3d_default(), 2 * _affine_eye(), AFFINE_ERROR_MSG),
322 ],
323)
324def test_img_to_signals_maps_bad_masks(
325 img_4d_zeros_eye, shape, affine, error_msg
326):
327 bad_img = Nifti1Image(np.zeros(shape), affine)
329 with pytest.raises(ValueError, match=error_msg):
330 img_to_signals_maps(
331 imgs=img_4d_zeros_eye, maps_img=img_4d_zeros_eye, mask_img=bad_img
332 )
335def test_signals_extraction_with_labels_without_mask(
336 signals, labels_data, labels_img, shape_3d_default, tmp_path
337):
338 """Test conversion between signals and images \
339 using regions defined by labels.
340 """
341 data_img = signals_to_img_labels(signals=signals, labels_img=labels_img)
343 assert data_img.shape == (*shape_3d_default, N_TIMEPOINTS)
344 data = get_data(data_img)
345 assert np.all(data.std(axis=-1) > 0)
346 # There must be non-zero data (safety net)
347 assert abs(data).max() > 1e-9
349 _all_voxel_of_each_region_have_same_values(
350 data, labels_data, N_REGIONS, signals
351 )
353 # and back
354 signals_r, labels_r = img_to_signals_labels(
355 imgs=data_img, labels_img=labels_img
356 )
358 assert_almost_equal(signals_r, signals)
359 assert labels_r == list(range(1, 9))
361 filenames = write_imgs_to_path(data_img, file_path=tmp_path)
362 signals_r, labels_r = img_to_signals_labels(
363 imgs=filenames, labels_img=labels_img
364 )
366 assert_almost_equal(signals_r, signals)
367 assert labels_r == list(range(1, 9))
370def test_signals_extraction_with_labels_without_mask_return_masked_atlas(
371 signals, labels_img
372):
373 """Test masked_atlas is correct in conversion between signals and images \
374 using regions defined by labels.
375 """
376 data_img = signals_to_img_labels(signals=signals, labels_img=labels_img)
378 # test return_masked_atlas
379 (
380 _,
381 _,
382 masked_atlas_r,
383 ) = img_to_signals_labels(
384 imgs=data_img,
385 labels_img=labels_img,
386 return_masked_atlas=True,
387 )
389 labels_data = get_data(labels_img)
390 labels_data_r = get_data(masked_atlas_r)
392 # masked_atlas_r should be the same as labels_img
393 assert_equal(labels_data_r, labels_data)
395 # labels should be the same as before
396 # the labels_img does not contain background
397 assert list(np.unique(labels_data_r)) == list(range(1, 9))
400def test_signals_extraction_with_labels_with_mask(
401 signals, labels_img, labels_data, mask_img, shape_3d_default, tmp_path
402):
403 """Test conversion between signals and images \
404 using regions defined by labels with a mask.
405 """
406 data_img = signals_to_img_labels(
407 signals=signals, labels_img=labels_img, mask_img=mask_img
408 )
410 assert data_img.shape == (*shape_3d_default, N_TIMEPOINTS)
411 # There must be non-zero data (safety net)
412 data = get_data(data_img)
413 assert abs(data).max() > 1e-9
415 # Zero outside of the mask
416 assert np.all(data[np.logical_not(get_data(mask_img))].std(axis=-1) < EPS)
418 filenames = write_imgs_to_path(labels_img, mask_img, file_path=tmp_path)
419 data_img = signals_to_img_labels(
420 signals=signals, labels_img=filenames[0], mask_img=filenames[1]
421 )
423 assert data_img.shape == (*shape_3d_default, N_TIMEPOINTS)
424 data = get_data(data_img)
425 assert abs(data).max() > 1e-9
426 # Zero outside of the mask
427 assert np.all(data[np.logical_not(get_data(mask_img))].std(axis=-1) < EPS)
429 # mask labels before checking
430 masked_labels_data = labels_data.copy()
431 masked_labels_data[np.logical_not(get_data(mask_img))] = 0
432 _all_voxel_of_each_region_have_same_values(
433 data, masked_labels_data, N_REGIONS, signals
434 )
436 # and back
437 signals_r, labels_r = img_to_signals_labels(
438 imgs=data_img, labels_img=labels_img, mask_img=mask_img
439 )
441 assert_almost_equal(signals_r, signals)
442 assert labels_r == list(range(1, 9))
445def test_signals_extraction_with_labels_with_mask_return_masked_atlas(
446 signals, labels_img, mask_img
447):
448 """Test masked_atlas is correct in conversion between signals and images \
449 using regions defined by labels and a mask.
450 """
451 data_img = signals_to_img_labels(
452 signals=signals, labels_img=labels_img, mask_img=mask_img
453 )
455 # test return_masked_atlas
456 # create a mask_img with only 3 regions
457 mask_img = _create_mask_with_3_regions_from_labels_data(
458 get_data(labels_img), labels_img.affine
459 )
461 (
462 _,
463 _,
464 masked_atlas_r,
465 ) = img_to_signals_labels(
466 imgs=data_img,
467 labels_img=labels_img,
468 mask_img=mask_img,
469 return_masked_atlas=True,
470 )
472 labels_data_r = get_data(masked_atlas_r)
474 # labels should be masked and only contain 3 regions
475 # and the background
476 assert list(np.unique(labels_data_r)) == [0, 1, 2, 5]
479def test_signal_extraction_with_maps(affine_eye, shape_3d_default, rng):
480 # Generate signal imgs
481 maps_img, mask_img = generate_maps(shape_3d_default, N_REGIONS)
482 maps_data = get_data(maps_img)
483 data = np.zeros((*shape_3d_default, N_TIMEPOINTS))
484 signals = np.zeros((N_TIMEPOINTS, maps_data.shape[-1]))
485 for n in range(maps_data.shape[-1]):
486 signals[:, n] = rng.standard_normal(size=N_TIMEPOINTS)
487 data[maps_data[..., n] > 0, :] = signals[:, n]
488 imgs = Nifti1Image(data, affine_eye)
490 # Get signals
491 signals_r, _ = img_to_signals_maps(
492 imgs=imgs, maps_img=maps_img, mask_img=mask_img
493 )
494 assert_almost_equal(signals, signals_r)
496 # Recover image
497 img_r = signals_to_img_maps(signals, maps_img, mask_img=mask_img)
498 assert_almost_equal(get_data(img_r), get_data(imgs))
500 # Same thing without mask
501 signals_r, _ = img_to_signals_maps(imgs, maps_img)
502 assert_almost_equal(signals, signals_r)
503 img_r = signals_to_img_maps(signals, maps_img)
504 assert_almost_equal(get_data(img_r), get_data(imgs))
507def test_signal_extraction_with_maps_and_labels(
508 labeled_regions, fmri_img, shape_3d_default
509):
510 labels = list(range(N_REGIONS + 1))
511 labels_data = get_data(labeled_regions)
512 # Convert to maps
513 maps_data = np.zeros((*shape_3d_default, N_REGIONS))
514 for n, l in enumerate(labels):
515 if n == 0:
516 continue
517 maps_data[labels_data == l, n - 1] = 1
519 maps_img = Nifti1Image(maps_data, labeled_regions.affine)
521 # Extract signals from maps and labels: results must be identical.
522 maps_signals, maps_labels = img_to_signals_maps(fmri_img, maps_img)
523 labels_signals, labels_labels = img_to_signals_labels(
524 imgs=fmri_img, labels_img=labeled_regions
525 )
526 assert_almost_equal(maps_signals, labels_signals)
528 # Same thing with a mask, containing only 3 regions.
529 mask_img = _create_mask_with_3_regions_from_labels_data(
530 labels_data, labeled_regions.affine
531 )
532 labels_signals, labels_labels = img_to_signals_labels(
533 imgs=fmri_img, labels_img=labeled_regions, mask_img=mask_img
534 )
535 maps_signals, maps_labels = img_to_signals_maps(
536 fmri_img, maps_img, mask_img=mask_img
537 )
539 assert_almost_equal(maps_signals, labels_signals)
540 assert maps_signals.shape[1] == N_REGIONS
541 assert maps_labels == list(range(len(maps_labels)))
542 assert labels_signals.shape == (N_TIMEPOINTS, N_REGIONS)
543 assert labels_labels == labels[1:]
545 # Inverse operation (mostly smoke test)
546 labels_img_r = signals_to_img_labels(
547 labels_signals, labeled_regions, mask_img=mask_img
548 )
549 assert labels_img_r.shape == (*shape_3d_default, N_TIMEPOINTS)
551 maps_img_r = signals_to_img_maps(maps_signals, maps_img, mask_img=mask_img)
552 assert maps_img_r.shape == (*shape_3d_default, N_TIMEPOINTS)
555def test_img_to_signals_labels_warnings(labeled_regions, fmri_img):
556 labels_data = get_data(labeled_regions)
558 mask_img = _create_mask_with_3_regions_from_labels_data(
559 labels_data, labeled_regions.affine
560 )
562 # apply img_to_signals_labels with a masking,
563 # containing only 3 regions, but
564 # not keeping the masked labels
565 with pytest.warns(
566 UserWarning,
567 match="After applying mask to the labels image, "
568 "the following labels were "
569 r"removed: \{3, 4, 6, 7, 8\}. "
570 "Out of 9 labels, the "
571 "masked labels image only contains "
572 "4 labels "
573 r"\(including background\).",
574 ):
575 labels_signals, labels_labels = img_to_signals_labels(
576 imgs=fmri_img,
577 labels_img=labeled_regions,
578 mask_img=mask_img,
579 keep_masked_labels=False,
580 )
582 # only 3 regions must be kept, others must be removed
583 assert labels_signals.shape == (N_TIMEPOINTS, 3)
584 assert len(labels_labels) == 3
586 # apply img_to_signals_labels with a masking,
587 # containing only 3 regions, and
588 # keeping the masked labels
589 # test if the warning is raised
591 with pytest.warns(
592 DeprecationWarning,
593 match='Applying "mask_img" before '
594 "signal extraction may result in empty region signals in "
595 "the output. These are currently kept. "
596 "Starting from version 0.13, the default behavior will be "
597 "changed to remove them by setting "
598 '"keep_masked_labels=False". '
599 '"keep_masked_labels" parameter will be removed '
600 "in version 0.15.",
601 ):
602 labels_signals, labels_labels = img_to_signals_labels(
603 imgs=fmri_img,
604 labels_img=labeled_regions,
605 mask_img=mask_img,
606 keep_masked_labels=True,
607 )
609 # all regions must be kept
610 assert labels_signals.shape == (N_TIMEPOINTS, 8)
611 assert len(labels_labels) == 8
613 # test return_masked_atlas deprecation warning
614 with pytest.warns(
615 DeprecationWarning,
616 match='After version 0.13. "img_to_signals_labels" will also return '
617 'the "masked_atlas". Meanwhile "return_masked_atlas" parameter can be '
618 "used to toggle this behavior. In version 0.15, "
619 '"return_masked_atlas" parameter will be removed.',
620 ):
621 img_to_signals_labels(
622 imgs=fmri_img,
623 labels_img=labeled_regions,
624 mask_img=mask_img,
625 keep_masked_labels=False,
626 return_masked_atlas=False,
627 )
630def test_img_to_signals_maps_warnings(
631 labeled_regions, fmri_img, shape_3d_default
632):
633 labels = list(range(N_REGIONS + 1))
634 labels_data = get_data(labeled_regions)
635 # Convert to maps
636 maps_data = np.zeros((*shape_3d_default, N_REGIONS))
637 for n, l in enumerate(labels):
638 if n == 0:
639 continue
640 maps_data[labels_data == l, n - 1] = 1
642 maps_img = Nifti1Image(maps_data, labeled_regions.affine)
644 mask_img = _create_mask_with_3_regions_from_labels_data(
645 labels_data, labeled_regions.affine
646 )
648 # apply img_to_signals_maps with a masking,
649 # containing only 3 regions, but
650 # not keeping the masked maps
651 with pytest.warns(
652 UserWarning,
653 match="After applying mask to the maps image, "
654 "maps with the following indices were "
655 r"removed: \{2, 3, 5, 6, 7\}. "
656 "Out of 8 maps, the "
657 "masked map image only contains "
658 "3 maps.",
659 ):
660 maps_signals, maps_labels = img_to_signals_maps(
661 fmri_img, maps_img, mask_img=mask_img, keep_masked_maps=False
662 )
664 # only 3 regions must be kept, others must be removed
665 assert maps_signals.shape == (N_TIMEPOINTS, 3)
666 assert len(maps_labels) == 3
668 # apply img_to_signals_labels with a masking,
669 # containing only 3 regions, and
670 # keeping the masked labels
671 # test if the warning is raised
672 with pytest.warns(
673 DeprecationWarning,
674 match='Applying "mask_img" before '
675 "signal extraction may result in empty region signals in the "
676 "output. These are currently kept. "
677 "Starting from version 0.13, the default behavior will be "
678 "changed to remove them by setting "
679 '"keep_masked_maps=False". '
680 '"keep_masked_maps" parameter will be removed '
681 "in version 0.15.",
682 ):
683 maps_signals, maps_labels = img_to_signals_maps(
684 fmri_img, maps_img, mask_img=mask_img, keep_masked_maps=True
685 )
687 # all regions must be kept
688 assert maps_signals.shape == (N_TIMEPOINTS, 8)
689 assert len(maps_labels) == 8
692def test_signal_extraction_nans_in_regions_are_replaced_with_zeros():
693 shape = (4, 5, 6)
694 labels = list(range(N_REGIONS + 1)) # 0 is background
695 labels_img = generate_labeled_regions(shape, N_REGIONS, labels=labels)
696 labels_data = get_data(labels_img)
697 fmri_img, _ = generate_fake_fmri(
698 shape=shape, affine=labels_img.affine, length=N_TIMEPOINTS
699 )
701 mask_img = _create_mask_with_3_regions_from_labels_data(
702 labels_data, labels_img.affine
703 )
705 region1 = labels_data == 2
706 indices = tuple(ind[:1] for ind in np.where(region1))
707 get_data(fmri_img)[indices] = np.nan
709 labels_signals, labels_labels = img_to_signals_labels(
710 imgs=fmri_img, labels_img=labels_img, mask_img=mask_img
711 )
713 assert np.all(labels_signals[:, labels_labels.index(2)] == 0.0)
716def test_trim_maps(shape_3d_default):
717 # maps
718 maps_data = np.zeros((*shape_3d_default, N_REGIONS), dtype=np.float32)
719 h0, h1, h2 = (s // 2 for s in shape_3d_default)
720 maps_data[:h0, :h1, :h2, 0] = 1
721 maps_data[:h0, :h1, h2:, 1] = 1.1
722 maps_data[:h0, h1:, :h2, 2] = 1
723 maps_data[:h0, h1:, h2:, 3] = 0.5
724 maps_data[h0:, :h1, :h2, 4] = 1
725 maps_data[h0:, :h1, h2:, 5] = 1.4
726 maps_data[h0:, h1:, :h2, 6] = 1
727 maps_data[h0:, h1:, h2:, 7] = 1
729 # mask intersecting all regions
730 mask_data = np.zeros(shape_3d_default, dtype=np.int8)
731 mask_data[1:-1, 1:-1, 1:-1] = 1
733 maps_i, maps_i_mask, maps_i_indices = _trim_maps(maps_data, mask_data)
735 assert maps_i.flags["F_CONTIGUOUS"]
736 assert len(maps_i_indices) == maps_i.shape[-1]
737 assert maps_i.shape == maps_data.shape
738 maps_i_correct = maps_data.copy()
739 maps_i_correct[np.logical_not(mask_data), :] = 0
740 assert_almost_equal(maps_i_correct, maps_i)
741 assert_equal(mask_data, maps_i_mask)
742 assert_equal(np.asarray(list(range(8))), maps_i_indices)
744 # mask intersecting half of the regions
745 mask_data = np.zeros(shape_3d_default, dtype=np.int8)
746 mask_data[1:2, 1:-1, 1:-1] = 1
747 maps_data[1, 1, 1, 0] = 0 # remove one point inside mask
749 maps_i, maps_i_mask, maps_i_indices = _trim_maps(maps_data, mask_data)
751 assert maps_i.flags["F_CONTIGUOUS"]
752 assert len(maps_i_indices) == maps_i.shape[-1]
753 assert maps_i.shape == (maps_data.shape[:3] + (4,))
754 maps_i_correct = maps_data[..., :4].copy()
755 maps_i_correct[np.logical_not(mask_data), :] = 0
756 assert_almost_equal(maps_i_correct, maps_i)
757 mask_data[1, 1, 1] = 0 # for test to succeed
758 assert_equal(mask_data, maps_i_mask)
759 mask_data[1, 1, 1] = 1 # reset, just in case.
760 assert_equal(np.asarray(list(range(4))), maps_i_indices)
763@pytest.mark.parametrize(
764 "target_dtype",
765 (float, np.float32, np.float64, int, np.uint),
766)
767def test_img_to_signals_labels_non_float_type(target_dtype, rng):
768 fake_fmri_data = rng.uniform(size=(10, 10, 10, N_TIMEPOINTS)) > 0.5
769 fake_affine = np.eye(4, 4).astype(np.float64)
770 fake_fmri_img_orig = Nifti1Image(
771 fake_fmri_data.astype(np.float64), fake_affine
772 )
773 fake_fmri_img_target_dtype = new_img_like(
774 fake_fmri_img_orig, fake_fmri_data.astype(target_dtype)
775 )
777 fake_mask_data = np.zeros((10, 10, 10), dtype=np.uint8)
778 fake_mask_data[1:8, 1:8, 1:8] = 1
779 fake_mask = Nifti1Image(fake_mask_data, fake_affine)
781 masker = NiftiLabelsMasker(fake_mask)
782 masker.fit()
784 timeseries_int = masker.transform(fake_fmri_img_target_dtype)
785 timeseries_float = masker.transform(fake_fmri_img_orig)
787 assert np.sum(timeseries_int) != 0
788 assert np.allclose(timeseries_int, timeseries_float)