Coverage for nilearn/glm/tests/test_utils.py: 0%
140 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#!/usr/bin/env python
2import numpy as np
3import pytest
4import scipy.linalg as spl
5import scipy.stats as sps
6from numpy.testing import (
7 assert_almost_equal,
8 assert_array_almost_equal,
9 assert_array_equal,
10)
11from scipy.stats import norm
13from nilearn._utils.data_gen import generate_fake_fmri
14from nilearn.glm._utils import (
15 full_rank,
16 multiple_fast_inverse,
17 multiple_mahalanobis,
18 pad_contrast,
19 positive_reciprocal,
20 z_score,
21)
22from nilearn.glm.first_level import (
23 FirstLevelModel,
24 make_first_level_design_matrix,
25)
26from nilearn.maskers import NiftiMasker
29def test_full_rank(rng):
30 n, p = 10, 5
31 X = rng.standard_normal(size=(n, p))
32 X_, _ = full_rank(X)
34 assert_array_almost_equal(X, X_)
36 X[:, -1] = X[:, :-1].sum(1)
37 X_, cond = full_rank(X)
39 assert cond > 1.0e10
40 assert_array_almost_equal(X, X_)
43def test_z_score_t_values(rng):
44 # Randomly draw samples from the standard Student's t distribution
45 t_val = rng.standard_t(10, size=10)
46 # Estimate the p-values using the Survival Function (SF)
47 p_val = sps.t.sf(t_val, 1e10)
48 # Estimate the p-values using the Cumulative Distribution Function (CDF)
49 cdf_val = sps.t.cdf(t_val, 1e10)
50 # Set a minimum threshold for p-values to avoid infinite z-scores
51 p_val = np.array(np.minimum(np.maximum(p_val, 1.0e-300), 1.0 - 1.0e-16))
52 cdf_val = np.array(
53 np.minimum(np.maximum(cdf_val, 1.0e-300), 1.0 - 1.0e-16)
54 )
55 # Compute z-score from the p-value estimated with the SF
56 z_val_sf = norm.isf(p_val)
57 # Compute z-score from the p-value estimated with the CDF
58 z_val_cdf = norm.ppf(cdf_val)
59 # Create the final array of z-scores, ...
60 z_val = np.zeros(p_val.size)
61 # ... in which z-scores < 0 estimated w/ SF are replaced by z-scores < 0
62 # estimated w/ CDF
63 z_val[np.atleast_1d(z_val_sf < 0)] = z_val_cdf[z_val_sf < 0]
64 # ... and z-scores >=0 estimated from SF are kept.
65 z_val[np.atleast_1d(z_val_sf >= 0)] = z_val_sf[z_val_sf >= 0]
67 # Test 'z_score' function in 'nilearn/glm/contrasts.py'
68 assert_array_almost_equal(z_score(p_val, one_minus_pvalue=cdf_val), z_val)
70 # Test 'z_score' function in 'nilearn/glm/contrasts.py',
71 # when one_minus_pvalue is None
72 assert_array_almost_equal(norm.sf(z_score(p_val)), p_val)
75def test_z_score_f_values(rng):
76 # Randomly draw samples from the F distribution
77 f_val = rng.f(1, 48, size=10)
78 # Estimate the p-values using the Survival Function (SF)
79 p_val = sps.f.sf(f_val, 42, 1e10)
80 # Estimate the p-values using the Cumulative Distribution Function (CDF)
81 cdf_val = sps.f.cdf(f_val, 42, 1e10)
82 # Set a minimum threshold for p-values to avoid infinite z-scores
83 p_val = np.array(np.minimum(np.maximum(p_val, 1.0e-300), 1.0 - 1.0e-16))
84 cdf_val = np.array(
85 np.minimum(np.maximum(cdf_val, 1.0e-300), 1.0 - 1.0e-16)
86 )
87 # Compute z-score from the p-value estimated with the SF
88 z_val_sf = norm.isf(p_val)
89 # Compute z-score from the p-value estimated with the CDF
90 z_val_cdf = norm.ppf(cdf_val)
91 # Create the final array of z-scores, ...
92 z_val = np.zeros(p_val.size)
93 # ... in which z-scores < 0 estimated w/ SF are replaced by z-scores < 0
94 # estimated w/ CDF
95 z_val[np.atleast_1d(z_val_sf < 0)] = z_val_cdf[z_val_sf < 0]
96 # ... and z-scores >=0 estimated from SF are kept.
97 z_val[np.atleast_1d(z_val_sf >= 0)] = z_val_sf[z_val_sf >= 0]
99 # Test 'z_score' function in 'nilearn/glm/contrasts.py'
100 assert_array_almost_equal(z_score(p_val, one_minus_pvalue=cdf_val), z_val)
102 # Test 'z_score' function in 'nilearn/glm/contrasts.py',
103 # when one_minus_pvalue is None
104 assert_array_almost_equal(norm.sf(z_score(p_val)), p_val)
106 # ##################### Check the numerical precision #####################
107 for t in [33.75, -8.3]:
108 p = sps.t.sf(t, 1e10)
109 cdf = sps.t.cdf(t, 1e10)
110 z_sf = norm.isf(p)
111 z_cdf = norm.ppf(cdf)
112 z = z_sf if p <= 0.5 else z_cdf
114 assert_array_almost_equal(z_score(p, one_minus_pvalue=cdf), z)
117def test_z_score_opposite_contrast(rng):
118 fmri, mask = generate_fake_fmri(
119 shape=(50, 20, 50), length=96, random_state=rng
120 )
122 nifti_masker = NiftiMasker(mask_img=mask)
123 data = nifti_masker.fit_transform(fmri)
125 frametimes = np.linspace(0, (96 - 1) * 2, 96)
127 for i in [0, 20]:
128 design_matrix = make_first_level_design_matrix(
129 frametimes,
130 hrf_model="spm",
131 add_regs=np.array(data[:, i]).reshape(-1, 1),
132 )
133 c1 = np.array([1] + [0] * (design_matrix.shape[1] - 1))
134 c2 = np.array([0] + [1] + [0] * (design_matrix.shape[1] - 2))
135 contrasts = {"seed1 - seed2": c1 - c2, "seed2 - seed1": c2 - c1}
136 fmri_glm = FirstLevelModel(
137 t_r=2.0,
138 noise_model="ar1",
139 standardize=False,
140 hrf_model="spm",
141 drift_model="cosine",
142 )
143 fmri_glm.fit(fmri, design_matrices=design_matrix)
144 z_map_seed1_vs_seed2 = fmri_glm.compute_contrast(
145 contrasts["seed1 - seed2"], output_type="z_score"
146 )
147 z_map_seed2_vs_seed1 = fmri_glm.compute_contrast(
148 contrasts["seed2 - seed1"], output_type="z_score"
149 )
151 assert_almost_equal(
152 z_map_seed1_vs_seed2.get_fdata(dtype="float32").min(),
153 -z_map_seed2_vs_seed1.get_fdata(dtype="float32").max(),
154 decimal=10,
155 )
156 assert_almost_equal(
157 z_map_seed1_vs_seed2.get_fdata(dtype="float32").max(),
158 -z_map_seed2_vs_seed1.get_fdata(dtype="float32").min(),
159 decimal=10,
160 )
163def test_mahalanobis(rng):
164 n = 50
165 x = rng.uniform(size=n) / n
166 A = rng.uniform(size=(n, n)) / n
167 A = np.dot(A.transpose(), A) + np.eye(n)
168 mah = np.dot(x, np.dot(spl.inv(A), x))
170 assert_almost_equal(mah, multiple_mahalanobis(x, A), decimal=1)
173def test_mahalanobis2(rng):
174 n = 50
175 x = rng.standard_normal(size=(n, 3))
176 Aa = np.zeros([n, n, 3])
177 for i in range(3):
178 A = rng.standard_normal(size=(120, n))
179 A = np.dot(A.T, A)
180 Aa[:, :, i] = A
181 i = rng.integers(3)
182 mah = np.dot(x[:, i], np.dot(spl.inv(Aa[:, :, i]), x[:, i]))
183 f_mah = (multiple_mahalanobis(x, Aa))[i]
185 assert np.allclose(mah, f_mah)
188def test_mahalanobis_errors():
189 effect = np.zeros((1, 2, 3))
190 cov = np.zeros((3, 3, 3))
192 with pytest.raises(
193 ValueError, match="Inconsistent shape for effect and covariance"
194 ):
195 multiple_mahalanobis(effect, cov)
197 cov = np.zeros((1, 2, 3))
199 with pytest.raises(ValueError, match="Inconsistent shape for covariance"):
200 multiple_mahalanobis(effect, cov)
203def test_multiple_fast_inv(rng):
204 shape = (10, 20, 20)
205 X = rng.standard_normal(size=shape)
206 X_inv_ref = np.zeros(shape)
207 for i in range(shape[0]):
208 X[i] = np.dot(X[i], X[i].T)
209 X_inv_ref[i] = spl.inv(X[i])
210 X_inv = multiple_fast_inverse(X)
212 assert_almost_equal(X_inv_ref, X_inv)
215def test_multiple_fast_inverse_errors():
216 shape = (2, 2, 2)
217 X = np.zeros(shape)
219 with pytest.raises(ValueError, match="Matrix LU decomposition failed"):
220 multiple_fast_inverse(X)
222 shape = (10, 20, 20)
223 X = np.zeros(shape)
225 with pytest.raises(ValueError, match="Matrix LU decomposition failed"):
226 multiple_fast_inverse(X)
229def test_pos_recipr():
230 X = np.array([2, 1, -1, 0], dtype=np.int8)
231 eX = np.array([0.5, 1, 0, 0])
232 Y = positive_reciprocal(X)
234 assert_array_almost_equal(Y, eX)
235 assert Y.dtype.type == np.float64
237 X2 = X.reshape((2, 2))
238 Y2 = positive_reciprocal(X2)
240 assert_array_almost_equal(Y2, eX.reshape((2, 2)))
242 # check that lists have arrived
243 XL = [0, 1, -1]
245 assert_array_almost_equal(positive_reciprocal(XL), [0, 1, 0])
246 # scalars
247 assert positive_reciprocal(-1) == 0
248 assert positive_reciprocal(0) == 0
249 assert positive_reciprocal(2) == 0.5
252@pytest.mark.parametrize("val", [[1], [1, 0]])
253@pytest.mark.parametrize("stat_type", ["t", "F"])
254def test_pad_contrast(rng, val, stat_type):
255 """Check padding of vector contrasts."""
256 theta = rng.random((4, 1))
257 con_val = np.array([val])
258 padded_contrast = pad_contrast(con_val, theta, stat_type=stat_type)
259 assert_array_equal(padded_contrast, [[1, 0, 0, 0]])
262@pytest.mark.parametrize("val", [[[1, 0], [0, 1]], [[1, 0, 0], [0, 1, 0]]])
263def test_pad_f_contrast(rng, val):
264 """Check padding of matrix contrasts."""
265 theta = rng.random((4, 1))
266 con_val = np.array(val)
267 padded_contrast = pad_contrast(con_val, theta, stat_type="F")
268 assert_array_equal(padded_contrast, [[1, 0, 0, 0], [0, 1, 0, 0]])