Coverage for nilearn/regions/tests/test_parcellations.py: 0%
223 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-20 10:58 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-20 10:58 +0200
1"""Test the parcellations tools module."""
3import warnings
5import numpy as np
6import pandas as pd
7import pytest
8from nibabel import Nifti1Image
10from nilearn.conftest import _affine_eye
11from nilearn.regions.parcellations import (
12 Parcellations,
13 _check_parameters_transform,
14)
15from nilearn.surface import SurfaceImage
16from nilearn.surface.tests.test_surface import flat_mesh
18METHODS = [
19 "kmeans",
20 "ward",
21 "complete",
22 "average",
23 "rena",
24 "hierarchical_kmeans",
25]
28@pytest.fixture
29def test_image():
30 data = np.zeros((10, 11, 12, 5))
31 data[9, 10, 2] = 1
32 data[4, 9, 3] = 2
33 return Nifti1Image(data, affine=_affine_eye())
36@pytest.fixture
37def test_image_2():
38 data = np.ones((10, 11, 12, 10))
39 data[6, 7, 8] = 2
40 data[9, 10, 11] = 3
41 return Nifti1Image(data, affine=_affine_eye())
44def test_error_parcellation_method_none(test_image):
45 with pytest.raises(
46 ValueError, match="Parcellation method is specified as None. "
47 ):
48 Parcellations(method=None, verbose=0).fit(test_image)
51@pytest.mark.parametrize("method", ["kmens", "avg", "completed"])
52def test_errors_raised_in_check_parameters_fit(method, test_image):
53 """Test whether an error is raised or not given a false method type."""
54 with pytest.raises(
55 ValueError,
56 match=(f"The method you have selected is not implemented '{method}'"),
57 ):
58 Parcellations(method=method, verbose=0).fit(test_image)
61@pytest.mark.parametrize("method", METHODS)
62@pytest.mark.parametrize("n_parcel", [5, 10, 15])
63def test_parcellations_fit_on_single_nifti_image(method, n_parcel, test_image):
64 """Test return attributes for each method."""
65 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
66 parcellator.fit(test_image)
68 labels_img = parcellator.labels_img_
69 # Test that object returns attribute labels_img_
70 assert labels_img is not None
71 # After inverse_transform, shape must match with
72 # original input data
73 assert labels_img.shape == test_image.shape[:3]
74 # Test object returns attribute masker_
75 assert parcellator.masker_ is not None
76 assert parcellator.mask_img_ is not None
77 if method not in ["kmeans", "rena", "hierarchical_kmeans"]:
78 # Test that object returns attribute connectivity_
79 # only for AgglomerativeClustering methods
80 assert parcellator.connectivity_ is not None
83def test_parcellations_warnings(img_4d_zeros_eye):
84 parcellator = Parcellations(method="kmeans", n_parcels=7, verbose=0)
86 with pytest.warns(UserWarning):
87 parcellator.fit(img_4d_zeros_eye)
90def test_parcellations_no_warnings(img_4d_zeros_eye):
91 parcellator = Parcellations(method="kmeans", n_parcels=1, verbose=0)
92 with warnings.catch_warnings(record=True) as record:
93 parcellator.fit(img_4d_zeros_eye)
94 assert all(r.category is not UserWarning for r in record)
97def test_parcellations_no_int64_warnings(img_4d_zeros_eye):
98 parcellator = Parcellations(method="kmeans", n_parcels=1, verbose=0)
99 with warnings.catch_warnings(record=True) as record:
100 parcellator.fit(img_4d_zeros_eye)
101 for r in record:
102 if issubclass(r.category, UserWarning):
103 assert "image contains 64-bit ints" not in str(r.message)
106@pytest.mark.parametrize("method", METHODS)
107def test_parcellations_fit_on_multi_nifti_images(
108 method, test_image, affine_eye
109):
110 fmri_imgs = [test_image] * 3
112 parcellator = Parcellations(method=method, n_parcels=5, verbose=0)
113 parcellator.fit(fmri_imgs)
115 assert parcellator.labels_img_ is not None
117 # Smoke test with explicit mask image
118 mask_img = np.ones((10, 11, 12))
119 mask_img = Nifti1Image(mask_img, affine_eye)
120 parcellator = Parcellations(
121 method=method, n_parcels=5, mask=mask_img, verbose=0
122 )
123 parcellator.fit(fmri_imgs)
126@pytest.mark.parametrize("method", METHODS)
127@pytest.mark.parametrize("n_parcel", [5])
128def test_parcellations_transform_single_nifti_image(
129 method, n_parcel, test_image_2
130):
131 """Test with NiftiLabelsMasker extraction of timeseries data \
132 after building a parcellations image.
133 """
134 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
135 parcellator.fit(test_image_2)
136 # transform to signals
137 signals = parcellator.transform(test_image_2)
139 # Test if the signals extracted are of same shape as inputs
140 # Here, we simply return numpy array for single subject input
141 assert signals.shape == (test_image_2.shape[3], n_parcel)
142 # Test for single subject but in a list.
143 signals = parcellator.transform([test_image_2])
144 assert signals.shape == (test_image_2.shape[3], n_parcel)
147@pytest.mark.parametrize("verbose", [True, False, -1, 0, 1, 2])
148def test_parcellations_transform_verbose(test_image_2, verbose):
149 """Test verbose mostly for coverage purpose."""
150 parcellator = Parcellations(method="kmeans", n_parcels=5, verbose=verbose)
151 parcellator.fit(test_image_2)
152 parcellator.transform(test_image_2)
155@pytest.mark.parametrize("method", METHODS)
156@pytest.mark.parametrize("n_parcel", [5])
157def test_parcellations_transform_multi_nifti_images(
158 method, n_parcel, test_image_2
159):
160 fmri_imgs = [test_image_2] * 3
162 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
163 parcellator.fit(fmri_imgs)
165 # transform multi images to signals.
166 # In return, we have length equal to the number of images
167 signals = parcellator.transform(fmri_imgs)
169 assert signals[0].shape == (test_image_2.shape[3], n_parcel)
170 assert signals[1].shape == (test_image_2.shape[3], n_parcel)
171 assert signals[2].shape == (test_image_2.shape[3], n_parcel)
172 assert len(signals) == len(fmri_imgs)
175def test_check_parameters_transform(test_image_2, rng):
176 # single confound
177 confounds = rng.standard_normal(size=(10, 3))
178 # Tests to check whether imgs, confounds returned are
179 # list or not. Pre-check in parameters to work for list
180 # of multi images and multi confounds
181 imgs, confounds, single_subject = _check_parameters_transform(
182 test_image_2, confounds
183 )
185 assert isinstance(imgs, (list, tuple))
186 assert isinstance(confounds, (list, tuple))
187 assert single_subject
189 # confounds as pandas DataFrame
190 imgs, confounds, single_subject = _check_parameters_transform(
191 test_image_2, pd.DataFrame(np.array(confounds)[0])
192 )
194 assert isinstance(confounds, (list, tuple))
196 # multi images
197 fmri_imgs = [test_image_2] * 3
198 confounds_list = [confounds] * 3
199 imgs, confounds, _ = _check_parameters_transform(fmri_imgs, confounds_list)
201 assert imgs == fmri_imgs
202 assert confounds_list == confounds
204 # Test the error when length of images and confounds are not same
205 msg = (
206 "Number of confounds given does not match with the "
207 "given number of images"
208 )
209 not_match_confounds_list = [confounds] * 2
210 with pytest.raises(ValueError, match=msg):
211 _check_parameters_transform(fmri_imgs, not_match_confounds_list)
214@pytest.mark.timeout(0)
215@pytest.mark.parametrize("method", METHODS)
216@pytest.mark.parametrize("n_parcel", [5])
217def test_parcellations_transform_with_multi_confounds_multi_images(
218 method, n_parcel, test_image_2, rng
219):
220 fmri_imgs = [test_image_2] * 3
221 confounds = rng.standard_normal(size=(10, 3))
222 confounds_list = [confounds] * 3
224 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
226 parcellator.fit(fmri_imgs)
227 signals = parcellator.transform(fmri_imgs, confounds=confounds_list)
229 assert isinstance(signals, list)
230 # n_parcels=5, length of data=10
231 assert signals[0].shape == (10, n_parcel)
234@pytest.mark.timeout(0)
235@pytest.mark.parametrize("method", METHODS)
236@pytest.mark.parametrize("n_parcel", [5])
237def test_fit_transform(method, n_parcel, test_image_2):
238 fmri_imgs = [test_image_2] * 3
240 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
241 parcellator.fit_transform(fmri_imgs)
243 assert parcellator.labels_img_ is not None
244 if method not in ["kmeans", "rena", "hierarchical_kmeans"]:
245 assert parcellator.connectivity_ is not None
246 assert parcellator.masker_ is not None
249@pytest.mark.timeout(0)
250@pytest.mark.parametrize("method", METHODS)
251@pytest.mark.parametrize("n_parcel", [5])
252def test_fit_transform_with_confounds(method, n_parcel, test_image_2, rng):
253 fmri_imgs = [test_image_2] * 3
254 confounds = rng.standard_normal(size=(10, 3))
255 confounds_list = [confounds] * 3
257 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
258 signals = parcellator.fit_transform(fmri_imgs, confounds=confounds_list)
260 assert isinstance(signals, list)
261 assert signals[0].shape == (10, n_parcel)
264@pytest.mark.parametrize("method", METHODS)
265@pytest.mark.parametrize("n_parcel", [5])
266def test_inverse_transform_single_nifti_image(method, n_parcel, test_image_2):
267 parcellate = Parcellations(method=method, n_parcels=n_parcel, verbose=0)
268 parcellate.fit(test_image_2)
270 assert parcellate.labels_img_ is not None
272 fmri_reduced = parcellate.transform(test_image_2)
274 assert isinstance(fmri_reduced, np.ndarray)
275 # Shape matching with (scans, regions)
276 assert fmri_reduced.shape == (10, n_parcel)
278 fmri_compressed = parcellate.inverse_transform(fmri_reduced)
280 # A single Nifti image for single subject input
281 assert isinstance(fmri_compressed, Nifti1Image)
282 # returns shape of fmri_img
283 assert fmri_compressed.shape == test_image_2.shape
285 # fmri_reduced in a list
286 fmri_compressed = parcellate.inverse_transform([fmri_reduced])
288 # A single Nifti image for single subject input
289 assert isinstance(fmri_compressed, Nifti1Image)
290 # returns shape of fmri_img
291 assert fmri_compressed.shape == test_image_2.shape
294def test_transform_single_3d_input_images(affine_eye):
295 """Test fit_transform single 3D image."""
296 data = np.ones((10, 11, 12))
297 data[6, 7, 8] = 2
298 data[9, 10, 11] = 3
299 img = Nifti1Image(data, affine=affine_eye)
301 parcellate = Parcellations(method="ward", n_parcels=20, verbose=0)
303 X = parcellate.fit_transform(img)
305 assert isinstance(X, np.ndarray)
306 assert X.shape == (1, 20)
309def test_transform_list_3d_input_images(affine_eye):
310 """Test fit_transform list 3D image."""
311 data = np.ones((10, 11, 12))
312 data[6, 7, 8] = 2
313 data[9, 10, 11] = 3
314 img = Nifti1Image(data, affine=affine_eye)
315 imgs = [img] * 2
317 parcellate = Parcellations(method="ward", n_parcels=20, verbose=0)
318 X = parcellate.fit_transform(imgs)
320 assert isinstance(X, list)
321 # (number of samples, number of features)
322 assert np.concatenate(X).shape == (2, 20)
324 # inverse transform
325 imgs_ = parcellate.inverse_transform(X)
327 assert isinstance(imgs_, list)
330@pytest.mark.parametrize("method", METHODS)
331@pytest.mark.parametrize("n_parcels", [5, 25])
332def test_parcellation_all_methods_with_surface(method, n_parcels, rng):
333 """Test if all parcellation methods work on surface."""
334 n_samples = 35
335 mesh = {
336 "left": flat_mesh(10, 8),
337 "right": flat_mesh(9, 7),
338 }
339 data = {
340 "left": rng.standard_normal(
341 size=(mesh["left"].coordinates.shape[0], n_samples)
342 ),
343 "right": rng.standard_normal(
344 size=(mesh["right"].coordinates.shape[0], n_samples)
345 ),
346 }
347 surf_img = SurfaceImage(mesh=mesh, data=data)
348 parcellate = Parcellations(method=method, n_parcels=n_parcels)
349 # fit and transform the data
350 X_transformed = parcellate.fit_transform(surf_img)
351 # inverse transform the transformed data
352 X_inverse = parcellate.inverse_transform(X_transformed)
354 # make sure the n_features in transformed data were reduced to n_clusters
355 assert X_transformed.shape == (n_samples, n_parcels)
357 # make sure the inverse transformed data has the same shape as the original
358 assert X_inverse.shape == surf_img.shape
361@pytest.mark.parametrize("method", METHODS)
362def test_parcellation_with_surface_and_confounds(method, rng):
363 """Test if parcellation works on surface with confounds."""
364 n_samples = 36
365 mesh = {
366 "left": flat_mesh(10, 8),
367 "right": flat_mesh(9, 7),
368 }
369 data = {
370 "left": rng.standard_normal(
371 size=(mesh["left"].coordinates.shape[0], n_samples)
372 ),
373 "right": rng.standard_normal(
374 size=(mesh["right"].coordinates.shape[0], n_samples)
375 ),
376 }
377 surf_img = SurfaceImage(mesh=mesh, data=data)
378 confounds = rng.standard_normal(size=(n_samples, 3))
379 parcellate = Parcellations(method=method, n_parcels=5)
380 X_transformed = parcellate.fit_transform(surf_img, confounds=[confounds])
382 assert X_transformed.shape == (n_samples, 5)
385@pytest.mark.parametrize("method", METHODS)
386def test_parcellation_with_multi_surface(method, rng):
387 """Test if parcellation works with surface data from multiple
388 'subjects'.
389 """
390 n_samples = 36
391 mesh = {
392 "left": flat_mesh(10, 8),
393 "right": flat_mesh(9, 7),
394 }
395 data = {
396 "left": rng.standard_normal(
397 size=(mesh["left"].coordinates.shape[0], n_samples)
398 ),
399 "right": rng.standard_normal(
400 size=(mesh["right"].coordinates.shape[0], n_samples)
401 ),
402 }
403 surf_img = SurfaceImage(mesh=mesh, data=data)
404 surf_imgs = [surf_img] * 3
405 parcellate = Parcellations(method=method, n_parcels=5)
406 X_transformed = parcellate.fit_transform(surf_imgs)
408 assert X_transformed[0].shape == (n_samples, 5)
409 assert len(X_transformed) == 3
412@pytest.mark.parametrize("method", METHODS)
413def test_parcellation_with_surface_mask(method, rng):
414 """Test if parcellation works with surface data and a mask."""
415 n_samples = 36
416 mesh = {
417 "left": flat_mesh(10, 8),
418 "right": flat_mesh(9, 7),
419 }
420 data = {
421 "left": rng.standard_normal(
422 size=(mesh["left"].coordinates.shape[0], n_samples)
423 ),
424 "right": rng.standard_normal(
425 size=(mesh["right"].coordinates.shape[0], n_samples)
426 ),
427 }
428 surf_img = SurfaceImage(mesh=mesh, data=data)
429 mask_data = {
430 "left": np.ones(mesh["left"].coordinates.shape[0]).astype(bool),
431 "right": np.ones(mesh["right"].coordinates.shape[0]).astype(bool),
432 }
433 surf_mask = SurfaceImage(mesh=mesh, data=mask_data)
434 parcellate = Parcellations(method=method, n_parcels=5, mask=surf_mask)
435 X_transformed = parcellate.fit_transform(surf_img)
437 assert X_transformed.shape == (n_samples, 5)