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

1import warnings 

2 

3import numpy as np 

4import pytest 

5from numpy.testing import ( 

6 assert_almost_equal, 

7 assert_array_almost_equal, 

8 assert_array_equal, 

9) 

10 

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) 

26 

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] 

35 

36 

37@pytest.fixture 

38def expected_integral(hrf_model): 

39 return 1 if hrf_model in [spm_hrf, glover_hrf] else 0 

40 

41 

42@pytest.fixture 

43def expected_length(t_r): 

44 return int(32 / t_r * 50) 

45 

46 

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) 

52 

53 

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) 

63 

64 assert_almost_equal(h.sum(), expected_integral) 

65 assert len(h) == expected_length 

66 

67 

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) 

72 

73 z = _resample_regressor(x, x, y) 

74 

75 assert_almost_equal(z, y) 

76 

77 

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) 

82 

83 z = _resample_regressor(np.cos(x), x, y) 

84 

85 assert_almost_equal(z, np.cos(y), decimal=2) 

86 

87 

88def test_orthogonalize(rng): 

89 """Test that the orthogonalization is OK.""" 

90 X = rng.standard_normal(size=(100, 5)) 

91 

92 X = orthogonalize(X) 

93 

94 K = np.dot(X.T, X) 

95 K -= np.diag(np.diag(K)) 

96 

97 assert_almost_equal((K**2).sum(), 0, 15) 

98 

99 

100def test_orthogonalize_trivial(rng): 

101 """Test that the orthogonalization is OK.""" 

102 X = rng.standard_normal(size=100) 

103 Y = X.copy() 

104 

105 X = orthogonalize(X) 

106 

107 assert_array_equal(Y, X) 

108 

109 

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) 

114 

115 reg, _ = _sample_condition( 

116 condition, frame_times, oversampling=1, min_onset=0 

117 ) 

118 

119 assert reg.sum() == 3 

120 assert reg[1] == 1 

121 assert reg[20] == 1 

122 assert reg[37] == 1 

123 

124 reg, _ = _sample_condition(condition, frame_times, oversampling=1) 

125 

126 assert reg.sum() == 3 

127 assert reg[25] == 1 

128 assert reg[44] == 1 

129 assert reg[61] == 1 

130 

131 

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) 

136 

137 reg, _ = _sample_condition( 

138 condition, frame_times, oversampling=1, min_onset=-10 

139 ) 

140 

141 assert reg.sum() == 6 

142 assert reg[10] == 1 

143 assert reg[48] == 1 

144 assert reg[31] == 1 

145 

146 

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) 

151 

152 reg, _ = _sample_condition( 

153 condition, frame_times, oversampling=10, min_onset=0 

154 ) 

155 

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 

161 

162 # check robustness to non-int oversampling 

163 reg_, _ = _sample_condition( 

164 condition, frame_times, oversampling=10.0, min_onset=0 

165 ) 

166 

167 assert_almost_equal(reg, reg_) 

168 

169 

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) 

174 

175 reg, _ = _sample_condition(condition, frame_times, oversampling=1) 

176 

177 assert reg.sum() == 10 

178 assert reg[25] == 1.0 

179 assert reg[44] == -1.0 

180 assert reg[61] == 5.0 

181 

182 

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) 

187 

188 reg, _ = _sample_condition(condition, frame_times, oversampling=1) 

189 

190 assert reg.sum() == 10 

191 assert reg[14] == 1.0 

192 assert reg[24] == -1.0 

193 assert reg[61] == 5.0 

194 

195 

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) 

202 

203 reg, _ = _sample_condition(condition, frame_times, oversampling=1) 

204 

205 assert reg.sum() == 4 

206 assert reg[24] == 2.0 

207 assert reg[34] == 1.0 

208 assert reg[61] == 0.0 

209 

210 

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) 

217 

218 reg, _ = _sample_condition(condition, frame_times, oversampling=1) 

219 

220 assert reg.sum() == 13 

221 assert reg[24] == 1.0 

222 assert reg[34] == 2.0 

223 assert reg[61] == 0.0 

224 

225 

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 ] 

249 

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 ] 

257 

258 def custom_rf(t_r, ov): 

259 return np.ones(int(t_r * ov)) 

260 

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 ] 

267 

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 ) 

278 

279 

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 

296 

297 

298def test_hkernel_length_fir_lambda(): 

299 """Test the hrf computation.""" 

300 t_r = 2.0 

301 

302 h = _hrf_kernel("fir", t_r, fir_delays=np.arange(4)) 

303 assert len(h) == 4 

304 

305 h = _hrf_kernel(lambda t_r, ov: np.ones(int(t_r * ov)), t_r) 

306 assert len(h) == 1 

307 

308 h = _hrf_kernel([lambda t_r, ov: np.ones(int(t_r * ov))], t_r) 

309 assert len(h) == 1 

310 

311 

312def test_hkernel(): 

313 """Test the hrf computation.""" 

314 t_r = 2.0 

315 

316 h = _hrf_kernel("spm", t_r) 

317 assert_almost_equal(h[0], spm_hrf(t_r)) 

318 

319 h = _hrf_kernel("spm + derivative", t_r) 

320 assert_almost_equal(h[1], spm_time_derivative(t_r)) 

321 

322 h = _hrf_kernel("spm + derivative + dispersion", t_r) 

323 assert_almost_equal(h[2], spm_dispersion_derivative(t_r)) 

324 

325 h = _hrf_kernel("glover", t_r) 

326 assert_almost_equal(h[0], glover_hrf(t_r)) 

327 

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)) 

331 

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)) 

336 

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) 

340 

341 h = _hrf_kernel(None, t_r) 

342 assert_almost_equal(h[0], np.hstack((1, np.zeros(49)))) 

343 

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) 

350 

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)) 

353 

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) 

358 

359 

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" 

365 

366 reg, reg_names = compute_regressor(condition, hrf_model, frame_times) 

367 

368 assert_almost_equal(reg.sum(), 6, 1) 

369 assert reg_names[0] == "cond" 

370 

371 

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" 

377 

378 reg, reg_names = compute_regressor(condition, hrf_model, frame_times) 

379 

380 assert_almost_equal(reg.sum() * 50, 3, 1) 

381 assert reg_names[0] == "cond" 

382 

383 

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" 

389 

390 reg, reg_names = compute_regressor( 

391 condition, hrf_model, frame_times, fir_delays=np.arange(4) 

392 ) 

393 

394 assert_array_almost_equal(np.sum(reg, 0), np.array([3, 3, 3, 3])) 

395 assert len(reg_names) == 4 

396 

397 reg_, _ = compute_regressor( 

398 condition, 

399 hrf_model, 

400 frame_times, 

401 fir_delays=np.arange(4), 

402 oversampling=50.0, 

403 ) 

404 

405 assert_array_equal(reg, reg_) 

406 

407 

408def test_calculate_tr(): 

409 """Test the TR calculation.""" 

410 true_tr = 0.75 

411 n_vols = 4 

412 

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 ) 

418 

419 estimated_tr = _calculate_tr(frame_times) 

420 

421 assert_almost_equal(estimated_tr, true_tr, 2) 

422 

423 

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" 

428 

429 

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" 

437 

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]) 

445 

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)