Coverage for nilearn/glm/tests/test_thresholding.py: 0%
141 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 thresholding utilities."""
3import numpy as np
4import pytest
5from nibabel import Nifti1Image
6from numpy.testing import assert_almost_equal, assert_equal
7from scipy.stats import norm
9from nilearn.conftest import _shape_3d_default
10from nilearn.glm import (
11 cluster_level_inference,
12 fdr_threshold,
13 threshold_stats_img,
14)
15from nilearn.glm.thresholding import _compute_hommel_value
16from nilearn.image import get_data
19def test_fdr(rng):
20 n = 100
21 x = np.linspace(0.5 / n, 1.0 - 0.5 / n, n)
22 x[:10] = 0.0005
23 x = norm.isf(x)
24 rng.shuffle(x)
26 assert_almost_equal(fdr_threshold(x, 0.1), norm.isf(0.0005))
27 assert fdr_threshold(x, 0.001) == np.inf
29 # addresses #2879
30 n = 10
31 pvals = np.linspace(1 / n, 1, n)
32 pvals[0] = 0.007
34 assert np.isfinite(fdr_threshold(norm.isf(pvals), 0.1))
37def test_fdr_error(rng):
38 n = 100
39 x = np.linspace(0.5 / n, 1.0 - 0.5 / n, n)
40 x[:10] = 0.0005
41 x = norm.isf(x)
42 rng.shuffle(x)
44 match = "alpha should be between 0 and 1"
45 with pytest.raises(ValueError, match=match):
46 fdr_threshold(x, -0.1)
47 with pytest.raises(ValueError, match=match):
48 fdr_threshold(x, 1.5)
51@pytest.fixture
52def data_norm_isf():
53 p = np.prod(_shape_3d_default())
54 return norm.isf(np.linspace(1.0 / p, 1.0 - 1.0 / p, p)).reshape(
55 _shape_3d_default()
56 )
59def test_threshold_stats_img_no_height_control(
60 data_norm_isf, img_3d_ones_eye, affine_eye
61):
62 data = data_norm_isf
63 data[2:4, 5:7, 6:8] = 5.0
64 stat_img = Nifti1Image(data, affine_eye)
66 # excessive cluster forming threshold
67 th_map, _ = threshold_stats_img(
68 stat_img,
69 mask_img=img_3d_ones_eye,
70 threshold=100,
71 height_control=None,
72 cluster_threshold=0,
73 )
74 vals = get_data(th_map)
76 assert np.sum(vals > 0) == 0
78 # direct threshold
79 th_map, _ = threshold_stats_img(
80 stat_img,
81 mask_img=img_3d_ones_eye,
82 threshold=4.0,
83 height_control=None,
84 cluster_threshold=0,
85 )
86 vals = get_data(th_map)
88 assert np.sum(vals > 0) == 8
90 # without mask
91 th_map, _ = threshold_stats_img(
92 stat_img, None, threshold=4.0, height_control=None, cluster_threshold=0
93 )
94 vals = get_data(th_map)
96 assert np.sum(vals > 0) == 8
98 # without a map
99 th_map, threshold = threshold_stats_img(
100 None, None, threshold=3.0, height_control=None, cluster_threshold=0
101 )
103 assert threshold == 3.0
104 assert th_map is None
107def test_threshold_stats_img(data_norm_isf, img_3d_ones_eye, affine_eye):
108 data = data_norm_isf
109 data[2:4, 5:7, 6:8] = 5.0
110 stat_img = Nifti1Image(data, affine_eye)
112 th_map, _ = threshold_stats_img(
113 stat_img,
114 mask_img=img_3d_ones_eye,
115 alpha=0.001,
116 height_control="fpr",
117 cluster_threshold=0,
118 )
119 vals = get_data(th_map)
121 assert np.sum(vals > 0) == 8
123 # excessive size threshold
124 th_map, z_th = threshold_stats_img(
125 stat_img,
126 mask_img=img_3d_ones_eye,
127 alpha=0.001,
128 height_control="fpr",
129 cluster_threshold=10,
130 )
131 vals = get_data(th_map)
133 assert np.sum(vals > 0) == 0
134 assert z_th == norm.isf(0.0005)
136 # dr threshold + bonferroni
137 for control in ["fdr", "bonferroni"]:
138 th_map, _ = threshold_stats_img(
139 stat_img,
140 mask_img=img_3d_ones_eye,
141 alpha=0.05,
142 height_control=control,
143 cluster_threshold=5,
144 )
145 vals = get_data(th_map)
147 assert np.sum(vals > 0) == 8
149 # without a map or mask
150 th_map, threshold = threshold_stats_img(
151 None, None, alpha=0.05, height_control="fpr", cluster_threshold=0
152 )
154 assert threshold > 1.64
155 assert th_map is None
158def test_threshold_stats_img_errors():
159 with pytest.raises(ValueError, match="'stat_img' cannot be None"):
160 threshold_stats_img(None, None, alpha=0.05, height_control="fdr")
162 with pytest.raises(ValueError, match="'stat_img' cannot be None"):
163 threshold_stats_img(
164 None, None, alpha=0.05, height_control="bonferroni"
165 )
167 with pytest.raises(ValueError, match="'height_control' should be one of"):
168 threshold_stats_img(None, None, alpha=0.05, height_control="plop")
171@pytest.mark.parametrize(
172 "alpha, expected",
173 [
174 (1.0e-9, 7),
175 (1.0e-7, 6),
176 (0.059, 6),
177 (0.061, 5),
178 (0.249, 5),
179 (0.251, 4),
180 (0.399, 4),
181 (0.401, 3),
182 (0.899, 3),
183 (0.901, 0),
184 ],
185)
186def test_hommel(alpha, expected):
187 """Check that the computation of Hommel value.
189 For these, we take the example in Meijer et al. 2017
190 'A shortcut for Hommel's procedure in linearithmic time'
191 and check that we obtain the same values.
192 https://arxiv.org/abs/1710.08273
193 """
194 z = norm.isf([1.0e-8, 0.01, 0.08, 0.1, 0.5, 0.7, 0.9])
196 assert _compute_hommel_value(z, alpha=alpha) == expected
199@pytest.mark.timeout(0)
200def test_all_resolution_inference(data_norm_isf, affine_eye):
201 data = data_norm_isf
202 data[2:4, 5:7, 6:8] = 5.0
203 stat_img = Nifti1Image(data, affine_eye)
205 # standard case
206 th_map = cluster_level_inference(stat_img, threshold=3, alpha=0.05)
207 vals = get_data(th_map)
209 assert np.sum(vals > 0) == 8
211 # high threshold
212 th_map = cluster_level_inference(stat_img, threshold=6, alpha=0.05)
213 vals = get_data(th_map)
215 assert np.sum(vals > 0) == 0
217 # list of thresholds
218 th_map = cluster_level_inference(stat_img, threshold=[3, 6], alpha=0.05)
219 vals = get_data(th_map)
221 assert np.sum(vals > 0) == 8
223 # verbose mode
224 th_map = cluster_level_inference(
225 stat_img, threshold=3, alpha=0.05, verbose=1
226 )
229def test_all_resolution_inference_with_mask(
230 img_3d_ones_eye, affine_eye, data_norm_isf
231):
232 data = data_norm_isf
233 data[2:4, 5:7, 6:8] = 5.0
234 stat_img = Nifti1Image(data, affine_eye)
236 th_map = cluster_level_inference(
237 stat_img, mask_img=img_3d_ones_eye, threshold=3, alpha=0.05
238 )
239 vals = get_data(th_map)
241 assert np.sum(vals > 0) == 8
244def test_all_resolution_inference_one_voxel(data_norm_isf, affine_eye):
245 data = data_norm_isf
246 data[3, 6, 7] = 10
247 stat_img = Nifti1Image(data, affine_eye)
249 th_map = cluster_level_inference(stat_img, threshold=7, alpha=0.05)
250 vals = get_data(th_map)
252 assert np.sum(vals > 0) == 1
255def test_all_resolution_inference_one_sided(
256 data_norm_isf, img_3d_ones_eye, affine_eye
257):
258 data = data_norm_isf
259 data[2:4, 5:7, 6:8] = 5.0
260 stat_img = Nifti1Image(data, affine_eye)
262 _, z_th = threshold_stats_img(
263 stat_img,
264 mask_img=img_3d_ones_eye,
265 alpha=0.001,
266 height_control="fpr",
267 cluster_threshold=10,
268 two_sided=False,
269 )
270 assert_equal(z_th, norm.isf(0.001))
273@pytest.mark.parametrize("alpha", [-1, 2])
274def test_all_resolution_inference_errors(alpha, data_norm_isf, affine_eye):
275 # test aberrant alpha
276 data = data_norm_isf
277 stat_img = Nifti1Image(data, affine_eye)
279 with pytest.raises(ValueError, match="alpha should be between 0 and 1"):
280 cluster_level_inference(stat_img, threshold=3, alpha=alpha)
283@pytest.mark.parametrize("control", ["fdr", "bonferroni"])
284def test_all_resolution_inference_height_control(
285 control, affine_eye, img_3d_ones_eye, data_norm_isf
286):
287 # two-side fdr threshold + bonferroni
288 data = data_norm_isf
289 data[2:4, 5:7, 6:8] = 5.0
290 data[0:2, 0:2, 6:8] = -5.0
291 stat_img = Nifti1Image(data, affine_eye)
293 th_map, _ = threshold_stats_img(
294 stat_img,
295 mask_img=img_3d_ones_eye,
296 alpha=0.05,
297 height_control=control,
298 cluster_threshold=5,
299 )
300 vals = get_data(th_map)
301 assert_equal(np.sum(vals > 0), 8)
302 assert_equal(np.sum(vals < 0), 8)
303 th_map, _ = threshold_stats_img(
304 stat_img,
305 mask_img=img_3d_ones_eye,
306 alpha=0.05,
307 height_control=control,
308 cluster_threshold=5,
309 two_sided=False,
310 )
311 vals = get_data(th_map)
313 assert_equal(np.sum(vals > 0), 8)
314 assert_equal(np.sum(vals < 0), 0)
317@pytest.mark.parametrize("height_control", [None, "bonferroni", "fdr", "fpr"])
318def test_threshold_stats_img_surface(surf_img_1d, height_control):
319 """Smoke test threshold_stats_img works on surface."""
320 threshold_stats_img(surf_img_1d, height_control=height_control)
323def test_threshold_stats_img_surface_with_mask(surf_img_1d, surf_mask_1d):
324 """Smoke test threshold_stats_img works on surface with a mask."""
325 threshold_stats_img(
326 surf_img_1d, height_control="bonferroni", mask_img=surf_mask_1d
327 )