Coverage for nilearn/decomposition/tests/test_base.py: 0%
67 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
1import numpy as np
2import pytest
3from numpy.testing import assert_array_almost_equal
4from scipy import linalg
6from nilearn.conftest import _rng
7from nilearn.decomposition._base import _fast_svd, _mask_and_reduce
8from nilearn.decomposition.tests.conftest import (
9 N_SAMPLES,
10 N_SUBJECTS,
11 RANDOM_STATE,
12)
15# We need to use n_features > 500 to trigger the randomized_svd
16@pytest.mark.parametrize("n_features", [30, 100, 550])
17def test_fast_svd(n_features):
18 """Test fast singular value decomposition."""
19 n_samples = 100
20 k = 10
22 # generate a matrix X of approximate effective rank `rank` and no noise
23 # component (very structured signal):
24 U = _rng().normal(size=(n_samples, k))
25 V = _rng().normal(size=(k, n_features))
26 X = np.dot(U, V)
28 assert X.shape == (n_samples, n_features)
30 # compute the singular values of X using the slow exact method
31 _, _, V_ = linalg.svd(X, full_matrices=False)
33 Ur, _, Vr = _fast_svd(X, k, random_state=0)
35 assert Vr.shape == (k, n_features)
36 assert Ur.shape == (n_samples, k)
37 # check the singular vectors too (while not checking the sign)
38 assert_array_almost_equal(
39 np.abs(np.diag(np.corrcoef(V_[:k], Vr)))[:k], np.ones(k)
40 )
43@pytest.mark.timeout(0)
44@pytest.mark.parametrize("data_type", ["nifti", "surface"])
45@pytest.mark.parametrize(
46 "n_components,reduction_ratio,expected_shape_0",
47 [
48 (None, "auto", N_SUBJECTS * N_SAMPLES),
49 (3, "auto", N_SUBJECTS * 3),
50 (None, 0.4, N_SUBJECTS * 2),
51 ],
52)
53def test_mask_reducer_multiple_image(
54 data_type,
55 n_components,
56 reduction_ratio,
57 expected_shape_0,
58 decomposition_masker,
59 decomposition_images,
60):
61 """Mask and reduce images with several values of input arguments."""
62 data = _mask_and_reduce(
63 masker=decomposition_masker,
64 imgs=decomposition_images,
65 n_components=n_components,
66 reduction_ratio=reduction_ratio,
67 )
68 if data_type == "nifti":
69 expected_shape = (
70 expected_shape_0,
71 np.prod(decomposition_masker.mask_img_.shape),
72 )
73 elif data_type == "surface":
74 expected_shape = (
75 expected_shape_0,
76 decomposition_images[0].mesh.n_vertices,
77 )
79 assert data.shape == expected_shape
82@pytest.mark.parametrize("data_type", ["nifti", "surface"])
83def test_mask_reducer_single_image_same_with_multiple_jobs(
84 data_type, decomposition_masker, decomposition_img
85):
86 """Mask and reduce a 3D image and check results is the same \
87 when split over several CPUs.
88 """
89 n_components = 3
90 data_single = _mask_and_reduce(
91 masker=decomposition_masker,
92 imgs=decomposition_img,
93 n_components=n_components,
94 )
95 if data_type == "nifti":
96 assert data_single.shape == (
97 n_components,
98 np.prod(decomposition_masker.mask_img_.shape),
99 )
100 elif data_type == "surface":
101 # For surface images, the shape is (n_components, n_vertices)
102 assert data_single.shape == (
103 n_components,
104 decomposition_masker.mask_img_.shape[0],
105 )
107 # Test n_jobs > 1
108 data = _mask_and_reduce(
109 masker=decomposition_masker,
110 imgs=decomposition_img,
111 n_components=n_components,
112 n_jobs=2,
113 random_state=RANDOM_STATE,
114 )
115 if data_type == "nifti":
116 assert data.shape == (
117 n_components,
118 np.prod(decomposition_masker.mask_img_.shape),
119 )
120 elif data_type == "surface":
121 assert data.shape == (
122 n_components,
123 decomposition_masker.mask_img_.shape[0],
124 )
125 assert_array_almost_equal(data_single, data)
128@pytest.mark.parametrize("data_type", ["nifti", "surface"])
129def test_mask_reducer_reduced_data_is_orthogonal(
130 data_type, decomposition_masker, decomposition_img
131):
132 """Test that the reduced data is orthogonal."""
133 n_components = 3
134 data = _mask_and_reduce(
135 masker=decomposition_masker,
136 imgs=decomposition_img,
137 n_components=n_components,
138 random_state=RANDOM_STATE,
139 )
141 if data_type == "nifti":
142 assert data.shape == (
143 n_components,
144 np.prod(decomposition_masker.mask_img_.shape),
145 )
146 elif data_type == "surface":
147 assert data.shape == (
148 n_components,
149 decomposition_masker.mask_img_.shape[-1],
150 )
152 cov = data.dot(data.T)
153 cov_diag = np.zeros((3, 3))
154 for i in range(3):
155 cov_diag[i, i] = cov[i, i]
157 assert_array_almost_equal(cov, cov_diag)
160@pytest.mark.parametrize("data_type", ["nifti", "surface"])
161def test_mask_reducer_reduced_reproducible(
162 data_type,
163 decomposition_masker,
164 decomposition_img,
165):
166 """Check that same input image give same results."""
167 n_components = 3
168 data1 = _mask_and_reduce(
169 masker=decomposition_masker,
170 imgs=decomposition_img,
171 n_components=n_components,
172 random_state=RANDOM_STATE,
173 )
174 data2 = _mask_and_reduce(
175 masker=decomposition_masker,
176 imgs=[decomposition_img] * 2,
177 n_components=n_components,
178 random_state=RANDOM_STATE,
179 )
181 if data_type == "nifti":
182 assert data1.shape == (
183 n_components,
184 np.prod(decomposition_masker.mask_img_.shape),
185 )
186 elif data_type == "surface":
187 assert data1.shape == (
188 n_components,
189 decomposition_masker.mask_img_.shape[-1],
190 )
192 assert_array_almost_equal(np.tile(data1, (2, 1)), data2)