Coverage for nilearn/glm/tests/test_hemodynamic_models.py: 0%
214 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
1import warnings
3import numpy as np
4import pytest
5from numpy.testing import (
6 assert_almost_equal,
7 assert_array_almost_equal,
8 assert_array_equal,
9)
11from nilearn.glm.first_level.hemodynamic_models import (
12 _calculate_tr,
13 _hrf_kernel,
14 _regressor_names,
15 _resample_regressor,
16 _sample_condition,
17 compute_regressor,
18 glover_dispersion_derivative,
19 glover_hrf,
20 glover_time_derivative,
21 orthogonalize,
22 spm_dispersion_derivative,
23 spm_hrf,
24 spm_time_derivative,
25)
27HRF_MODELS = [
28 spm_hrf,
29 glover_hrf,
30 spm_time_derivative,
31 glover_time_derivative,
32 spm_dispersion_derivative,
33 glover_dispersion_derivative,
34]
37@pytest.fixture
38def expected_integral(hrf_model):
39 return 1 if hrf_model in [spm_hrf, glover_hrf] else 0
42@pytest.fixture
43def expected_length(t_r):
44 return int(32 / t_r * 50)
47@pytest.mark.parametrize("hrf_model", HRF_MODELS)
48def test_hrf_tr_deprecation(hrf_model):
49 """Test that using tr throws a warning."""
50 with pytest.deprecated_call(match='"tr" will be removed in'):
51 hrf_model(tr=2)
54@pytest.mark.parametrize("hrf_model", HRF_MODELS)
55@pytest.mark.parametrize("t_r", [2, 3])
56def test_hrf_norm_and_length(
57 hrf_model, t_r, expected_integral, expected_length
58):
59 """Test that the hrf models are correctly normalized and \
60 have correct lengths.
61 """
62 h = hrf_model(t_r)
64 assert_almost_equal(h.sum(), expected_integral)
65 assert len(h) == expected_length
68def test_resample_regressor():
69 """Test regressor resampling on a linear function."""
70 x = np.linspace(0, 1, 200)
71 y = np.linspace(0, 1, 30)
73 z = _resample_regressor(x, x, y)
75 assert_almost_equal(z, y)
78def test_resample_regressor_nl():
79 """Test regressor resampling on a sine function."""
80 x = np.linspace(0, 10, 1000)
81 y = np.linspace(0, 10, 30)
83 z = _resample_regressor(np.cos(x), x, y)
85 assert_almost_equal(z, np.cos(y), decimal=2)
88def test_orthogonalize(rng):
89 """Test that the orthogonalization is OK."""
90 X = rng.standard_normal(size=(100, 5))
92 X = orthogonalize(X)
94 K = np.dot(X.T, X)
95 K -= np.diag(np.diag(K))
97 assert_almost_equal((K**2).sum(), 0, 15)
100def test_orthogonalize_trivial(rng):
101 """Test that the orthogonalization is OK."""
102 X = rng.standard_normal(size=100)
103 Y = X.copy()
105 X = orthogonalize(X)
107 assert_array_equal(Y, X)
110def test_sample_condition_1():
111 """Test that the experimental condition is correctly sampled."""
112 condition = ([1, 20, 36.5], [0, 0, 0], [1, 1, 1])
113 frame_times = np.linspace(0, 49, 50)
115 reg, _ = _sample_condition(
116 condition, frame_times, oversampling=1, min_onset=0
117 )
119 assert reg.sum() == 3
120 assert reg[1] == 1
121 assert reg[20] == 1
122 assert reg[37] == 1
124 reg, _ = _sample_condition(condition, frame_times, oversampling=1)
126 assert reg.sum() == 3
127 assert reg[25] == 1
128 assert reg[44] == 1
129 assert reg[61] == 1
132def test_sample_condition_2():
133 """Test the experimental condition sampling -- onset = 0."""
134 condition = ([0, 20, 36.5], [2, 2, 2], [1, 1, 1])
135 frame_times = np.linspace(0, 49, 50)
137 reg, _ = _sample_condition(
138 condition, frame_times, oversampling=1, min_onset=-10
139 )
141 assert reg.sum() == 6
142 assert reg[10] == 1
143 assert reg[48] == 1
144 assert reg[31] == 1
147def test_sample_condition_3():
148 """Test the experimental condition sampling -- oversampling=10."""
149 condition = ([1, 20, 36.5], [2, 2, 2], [1, 1, 1])
150 frame_times = np.linspace(0, 49, 50)
152 reg, _ = _sample_condition(
153 condition, frame_times, oversampling=10, min_onset=0
154 )
156 assert_almost_equal(reg.sum(), 60.0)
157 assert reg[10] == 1
158 assert reg[380] == 1
159 assert reg[210] == 1
160 assert np.sum(reg > 0) == 60
162 # check robustness to non-int oversampling
163 reg_, _ = _sample_condition(
164 condition, frame_times, oversampling=10.0, min_onset=0
165 )
167 assert_almost_equal(reg, reg_)
170def test_sample_condition_4():
171 """Test the experimental condition sampling -- negative amplitude."""
172 condition = ([1, 20, 36.5], [2, 2, 2], [1.0, -1.0, 5.0])
173 frame_times = np.linspace(0, 49, 50)
175 reg, _ = _sample_condition(condition, frame_times, oversampling=1)
177 assert reg.sum() == 10
178 assert reg[25] == 1.0
179 assert reg[44] == -1.0
180 assert reg[61] == 5.0
183def test_sample_condition_5():
184 """Test the experimental condition sampling -- negative onset."""
185 condition = ([-10, 0, 36.5], [2, 2, 2], [1.0, -1.0, 5.0])
186 frame_times = np.linspace(0, 49, 50)
188 reg, _ = _sample_condition(condition, frame_times, oversampling=1)
190 assert reg.sum() == 10
191 assert reg[14] == 1.0
192 assert reg[24] == -1.0
193 assert reg[61] == 5.0
196def test_sample_condition_6():
197 """Test the experimental condition sampling -- overalapping onsets, \
198 different durations.
199 """
200 condition = ([0, 0, 10], [1, 2, 1], [1.0, 1.0, 1.0])
201 frame_times = np.linspace(0, 49, 50)
203 reg, _ = _sample_condition(condition, frame_times, oversampling=1)
205 assert reg.sum() == 4
206 assert reg[24] == 2.0
207 assert reg[34] == 1.0
208 assert reg[61] == 0.0
211def test_sample_condition_7():
212 """Test the experimental condition sampling -- different onsets, \
213 overlapping offsets.
214 """
215 condition = ([0, 10, 20], [11, 1, 1], [1.0, 1.0, 1.0])
216 frame_times = np.linspace(0, 49, 50)
218 reg, _ = _sample_condition(condition, frame_times, oversampling=1)
220 assert reg.sum() == 13
221 assert reg[24] == 1.0
222 assert reg[34] == 2.0
223 assert reg[61] == 0.0
226def test_names():
227 """Test the regressor naming function."""
228 name = "con"
229 assert _regressor_names(name, "spm") == [name]
230 assert _regressor_names(name, "spm + derivative") == [
231 name,
232 f"{name}_derivative",
233 ]
234 assert _regressor_names(name, "spm + derivative + dispersion") == [
235 name,
236 f"{name}_derivative",
237 f"{name}_dispersion",
238 ]
239 assert _regressor_names(name, "glover") == [name]
240 assert _regressor_names(name, "glover + derivative") == [
241 name,
242 f"{name}_derivative",
243 ]
244 assert _regressor_names(name, "glover + derivative + dispersion") == [
245 name,
246 f"{name}_derivative",
247 f"{name}_dispersion",
248 ]
250 assert _regressor_names(name, None) == [name]
251 assert _regressor_names(name, [None, None]) == [f"{name}_0", f"{name}_1"]
252 assert _regressor_names(name, "typo") == [name]
253 assert _regressor_names(name, ["typo", "typo"]) == [
254 f"{name}_0",
255 f"{name}_1",
256 ]
258 def custom_rf(t_r, ov):
259 return np.ones(int(t_r * ov))
261 assert _regressor_names(name, custom_rf) == [
262 f"{name}_{custom_rf.__name__}"
263 ]
264 assert _regressor_names(name, [custom_rf]) == [
265 f"{name}_{custom_rf.__name__}"
266 ]
268 with pytest.raises(
269 ValueError, match="Computed regressor names are not unique"
270 ):
271 _regressor_names(
272 name,
273 [
274 lambda t_r, ov: np.ones(int(t_r * ov)),
275 lambda t_r, ov: np.ones(int(t_r * ov)),
276 ],
277 )
280@pytest.mark.parametrize(
281 "hrf_model, expected_len",
282 [
283 ("spm", 1),
284 ("glover", 1),
285 (None, 1),
286 ("spm + derivative", 2),
287 ("glover + derivative", 2),
288 ("spm + derivative + dispersion", 3),
289 ("glover + derivative + dispersion", 3),
290 ],
291)
292def test_hkernel_length(hrf_model, expected_len):
293 """Test the hrf computation."""
294 t_r = 2.0
295 assert len(_hrf_kernel(hrf_model, t_r)) == expected_len
298def test_hkernel_length_fir_lambda():
299 """Test the hrf computation."""
300 t_r = 2.0
302 h = _hrf_kernel("fir", t_r, fir_delays=np.arange(4))
303 assert len(h) == 4
305 h = _hrf_kernel(lambda t_r, ov: np.ones(int(t_r * ov)), t_r)
306 assert len(h) == 1
308 h = _hrf_kernel([lambda t_r, ov: np.ones(int(t_r * ov))], t_r)
309 assert len(h) == 1
312def test_hkernel():
313 """Test the hrf computation."""
314 t_r = 2.0
316 h = _hrf_kernel("spm", t_r)
317 assert_almost_equal(h[0], spm_hrf(t_r))
319 h = _hrf_kernel("spm + derivative", t_r)
320 assert_almost_equal(h[1], spm_time_derivative(t_r))
322 h = _hrf_kernel("spm + derivative + dispersion", t_r)
323 assert_almost_equal(h[2], spm_dispersion_derivative(t_r))
325 h = _hrf_kernel("glover", t_r)
326 assert_almost_equal(h[0], glover_hrf(t_r))
328 h = _hrf_kernel("glover + derivative", t_r)
329 assert_almost_equal(h[1], glover_time_derivative(t_r))
330 assert_almost_equal(h[0], glover_hrf(t_r))
332 h = _hrf_kernel("glover + derivative + dispersion", t_r)
333 assert_almost_equal(h[2], glover_dispersion_derivative(t_r))
334 assert_almost_equal(h[1], glover_time_derivative(t_r))
335 assert_almost_equal(h[0], glover_hrf(t_r))
337 h = _hrf_kernel("fir", t_r, fir_delays=np.arange(4))
338 for dh in h:
339 assert_almost_equal(dh.sum(), 1.0)
341 h = _hrf_kernel(None, t_r)
342 assert_almost_equal(h[0], np.hstack((1, np.zeros(49))))
344 with pytest.raises(
345 ValueError, match="Could not process custom HRF model provided."
346 ):
347 _hrf_kernel(lambda x: np.ones(int(x)), t_r)
348 _hrf_kernel([lambda x, y, z: x + y + z], t_r)
349 _hrf_kernel([lambda x: np.ones(int(x))] * 2, t_r)
351 h = _hrf_kernel(lambda t_r, ov: np.ones(int(t_r * ov)), t_r)
352 assert_almost_equal(h[0], np.ones(100))
354 h = _hrf_kernel([lambda t_r, ov: np.ones(int(t_r * ov))], t_r)
355 assert_almost_equal(h[0], np.ones(100))
356 with pytest.raises(ValueError, match="is not a known hrf model."):
357 _hrf_kernel("foo", t_r)
360def test_make_regressor_1():
361 """Test the generated regressor."""
362 condition = ([1, 20, 36.5], [2, 2, 2], [1, 1, 1])
363 frame_times = np.linspace(0, 69, 70)
364 hrf_model = "spm"
366 reg, reg_names = compute_regressor(condition, hrf_model, frame_times)
368 assert_almost_equal(reg.sum(), 6, 1)
369 assert reg_names[0] == "cond"
372def test_make_regressor_2():
373 """Test the generated regressor."""
374 condition = ([1, 20, 36.5], [0, 0, 0], [1, 1, 1])
375 frame_times = np.linspace(0, 69, 70)
376 hrf_model = "spm"
378 reg, reg_names = compute_regressor(condition, hrf_model, frame_times)
380 assert_almost_equal(reg.sum() * 50, 3, 1)
381 assert reg_names[0] == "cond"
384def test_make_regressor_3():
385 """Test the generated regressor."""
386 condition = ([1, 20, 36.5], [2, 2, 2], [1, 1, 1])
387 frame_times = np.linspace(0, 138, 70)
388 hrf_model = "fir"
390 reg, reg_names = compute_regressor(
391 condition, hrf_model, frame_times, fir_delays=np.arange(4)
392 )
394 assert_array_almost_equal(np.sum(reg, 0), np.array([3, 3, 3, 3]))
395 assert len(reg_names) == 4
397 reg_, _ = compute_regressor(
398 condition,
399 hrf_model,
400 frame_times,
401 fir_delays=np.arange(4),
402 oversampling=50.0,
403 )
405 assert_array_equal(reg, reg_)
408def test_calculate_tr():
409 """Test the TR calculation."""
410 true_tr = 0.75
411 n_vols = 4
413 # create times for four volumes, shifted forward by half a TR
414 # (as with fMRIPrep slice timing corrected data)
415 frame_times = np.linspace(
416 true_tr / 2, (n_vols * true_tr) + (true_tr / 2), n_vols + 1
417 )
419 estimated_tr = _calculate_tr(frame_times)
421 assert_almost_equal(estimated_tr, true_tr, 2)
424def test__regressor_names():
425 """Test that function allows invalid column identifier."""
426 reg_names = _regressor_names("1_cond", "glover")
427 assert reg_names[0] == "1_cond"
430def test_design_warnings():
431 """Test that warnings are correctly raised \
432 upon weird design specification.
433 """
434 condition = ([-25, 20, 36.5], [0, 0, 0], [1, 1, 1])
435 frame_times = np.linspace(0, 69, 70)
436 hrf_model = "spm"
438 with warnings.catch_warnings(record=True):
439 warnings.simplefilter("always")
440 with pytest.warns(
441 UserWarning, match="Some stimulus onsets are earlier than -24.0"
442 ):
443 compute_regressor(condition, hrf_model, frame_times)
444 condition = ([-25, -25, 36.5], [0, 0, 0], [1, 1, 1])
446 with warnings.catch_warnings(record=True):
447 warnings.simplefilter("always")
448 with pytest.warns(
449 UserWarning, match="Some stimulus onsets are earlier than -24.0"
450 ):
451 compute_regressor(condition, hrf_model, frame_times)