Coverage for nilearn/glm/first_level/hemodynamic_models.py: 17%

152 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-20 10:58 +0200

1"""Hemodynamic response function (hrf) specification. 

2 

3Here we provide for SPM, Glover hrfs and finite impulse response (FIR) models. 

4This module closely follows SPM implementation 

5""" 

6 

7import warnings 

8from collections.abc import Iterable 

9 

10import numpy as np 

11from scipy.interpolate import interp1d 

12from scipy.linalg import pinv 

13from scipy.stats import gamma 

14 

15from nilearn._utils import fill_doc, rename_parameters 

16from nilearn._utils.logger import find_stack_level 

17from nilearn._utils.param_validation import check_params 

18 

19 

20def _gamma_difference_hrf( 

21 t_r, 

22 oversampling=50, 

23 time_length=32.0, 

24 onset=0.0, 

25 delay=6, 

26 undershoot=16.0, 

27 dispersion=1.0, 

28 u_dispersion=1.0, 

29 ratio=0.167, 

30): 

31 """Compute an hrf as the difference of two gamma functions. 

32 

33 Parameters 

34 ---------- 

35 t_r : :obj:`float` 

36 :term:`Repetition time<TR>`, in seconds (sampling period). 

37 

38 oversampling : :obj:`int`, default=50 

39 Temporal oversampling factor. 

40 

41 time_length : :obj:`float`, default=32 

42 hrf kernel length, in seconds. 

43 

44 onset : :obj:`float`, default=0 

45 Onset time of the hrf. 

46 

47 delay : :obj:`float`, default=6 

48 Delay parameter of the hrf (in s.). 

49 

50 undershoot : :obj:`float`, default=16 

51 Undershoot parameter of the hrf (in s.). 

52 

53 dispersion : :obj:`float`, default=1 

54 Dispersion parameter for the first gamma function. 

55 

56 u_dispersion : :obj:`float`, default=1 

57 Dispersion parameter for the second gamma function. 

58 

59 ratio : :obj:`float`, default=0.167 

60 Ratio of the two gamma components. 

61 

62 Returns 

63 ------- 

64 hrf : array of shape(length / t_r * oversampling, dtype=float) 

65 hrf sampling on the oversampled time grid 

66 

67 """ 

68 dt = t_r / oversampling 

69 time_stamps = np.linspace( 

70 0, time_length, np.rint(float(time_length) / dt).astype(int) 

71 ) 

72 time_stamps -= onset 

73 

74 # define peak and undershoot gamma functions 

75 peak_gamma = gamma.pdf( 

76 time_stamps, delay / dispersion, loc=dt, scale=dispersion 

77 ) 

78 undershoot_gamma = gamma.pdf( 

79 time_stamps, undershoot / u_dispersion, loc=dt, scale=u_dispersion 

80 ) 

81 

82 # calculate the hrf 

83 hrf = peak_gamma - ratio * undershoot_gamma 

84 hrf /= hrf.sum() 

85 return hrf 

86 

87 

88@rename_parameters({"tr": "t_r"}, end_version="0.13.0") 

89def spm_hrf(t_r, oversampling=50, time_length=32.0, onset=0.0): 

90 """Implement the :term:`SPM` :term:`HRF` model. 

91 

92 Parameters 

93 ---------- 

94 t_r : :obj:`float` 

95 :term:`Repetition time<TR>`, in seconds (sampling period). 

96 

97 tr: 

98 

99 .. deprecated:: 0.11.0 

100 

101 Use ``t_r`` instead (see above). 

102 

103 oversampling : :obj:`int`, default=50 

104 Temporal oversampling factor. 

105 

106 time_length : :obj:`float`, default=32.0 

107 :term:`HRF` kernel length, in seconds. 

108 

109 onset : :obj:`float`, default=0.0 

110 :term:`HRF` onset time, in seconds. 

111 

112 Returns 

113 ------- 

114 hrf : array of shape(length / t_r * oversampling, dtype=float) 

115 :term:`HRF` sampling on the oversampled time grid 

116 

117 """ 

118 return _gamma_difference_hrf(t_r, oversampling, time_length, onset) 

119 

120 

121@rename_parameters({"tr": "t_r"}, end_version="0.13.0") 

122def glover_hrf(t_r, oversampling=50, time_length=32.0, onset=0.0): 

123 """Implement the Glover :term:`HRF` model. 

124 

125 Parameters 

126 ---------- 

127 t_r : :obj:`float` 

128 :term:`Repetition time<TR>`, in seconds (sampling period). 

129 

130 tr: 

131 

132 .. deprecated:: 0.11.0 

133 

134 Use ``t_r`` instead (see above). 

135 

136 oversampling : :obj:`int`, default=50 

137 Temporal oversampling factor. 

138 

139 time_length : :obj:`float`, default=32.0 

140 :term:`HRF` kernel length, in seconds. 

141 

142 onset : :obj:`float`, default=0.0 

143 Onset of the response. 

144 

145 Returns 

146 ------- 

147 hrf : array of shape(length / t_r * oversampling, dtype=float) 

148 :term:`HRF` sampling on the oversampled time grid. 

149 

150 """ 

151 return _gamma_difference_hrf( 

152 t_r, 

153 oversampling, 

154 time_length, 

155 onset, 

156 delay=6, 

157 undershoot=12.0, 

158 dispersion=0.9, 

159 u_dispersion=0.9, 

160 ratio=0.48, 

161 ) 

162 

163 

164def _compute_derivative_from_values(values, values_plus_dt, dt=0.1): 

165 """Return the time or dispersion derivative of an hrf.""" 

166 return 1.0 / dt * (values - values_plus_dt) 

167 

168 

169def _generic_time_derivative( 

170 func, t_r, oversampling=50, time_length=32.0, onset=0.0, dt=0.1 

171): 

172 """Return the time derivative of an hrf for a given function. 

173 

174 Parameters 

175 ---------- 

176 func : :obj:`function` 

177 spm_hrf or glover_hrf 

178 

179 t_r : :obj:`float` 

180 :term:`Repetition time<TR>`, in seconds (sampling period). 

181 

182 oversampling : :obj:`int`, default=50 

183 Temporal oversampling factor. 

184 

185 time_length : :obj:`float`, default=32 

186 hrf kernel length, in seconds. 

187 

188 onset : :obj:`float`, default=0 

189 Onset of the response. 

190 

191 dt : :obj:`float`, default=0.1 

192 Time step for the derivative. 

193 """ 

194 return _compute_derivative_from_values( 

195 func(t_r, oversampling, time_length, onset), 

196 func(t_r, oversampling, time_length, onset + dt), 

197 dt=dt, 

198 ) 

199 

200 

201@rename_parameters({"tr": "t_r"}, end_version="0.13.0") 

202def spm_time_derivative(t_r, oversampling=50, time_length=32.0, onset=0.0): 

203 """Implement the :term:`SPM` time derivative :term:`HRF` (dhrf) model. 

204 

205 Parameters 

206 ---------- 

207 t_r : :obj:`float` 

208 :term:`Repetition time<TR>`, in seconds (sampling period). 

209 

210 tr: 

211 

212 .. deprecated:: 0.11.0 

213 

214 Use ``t_r`` instead (see above). 

215 

216 oversampling : :obj:`int`, default=50 

217 Temporal oversampling factor. 

218 

219 time_length : :obj:`float`, default=32.0 

220 :term:`HRF` kernel length, in seconds. 

221 

222 onset : :obj:`float`, default=0.0 

223 Onset of the response in seconds. 

224 

225 Returns 

226 ------- 

227 dhrf : array of shape(length / t_r, dtype=float) 

228 dhrf sampling on the provided grid 

229 

230 """ 

231 return _generic_time_derivative( 

232 spm_hrf, 

233 t_r=t_r, 

234 oversampling=oversampling, 

235 time_length=time_length, 

236 onset=onset, 

237 ) 

238 

239 

240@rename_parameters({"tr": "t_r"}, end_version="0.13.0") 

241def glover_time_derivative(t_r, oversampling=50, time_length=32.0, onset=0.0): 

242 """Implement the Glover time derivative :term:`HRF` (dhrf) model. 

243 

244 Parameters 

245 ---------- 

246 t_r : :obj:`float` 

247 :term:`Repetition time<TR>`, in seconds (sampling period). 

248 

249 tr: 

250 

251 .. deprecated:: 0.11.0 

252 

253 Use ``t_r`` instead (see above). 

254 

255 oversampling : :obj:`int`, default=50 

256 Temporal oversampling factor. 

257 

258 time_length : :obj:`float`, default=32.0 

259 :term:`HRF` kernel length, in seconds. 

260 

261 onset : :obj:`float`, default=0.0 

262 Onset of the response. 

263 

264 Returns 

265 ------- 

266 dhrf : array of shape(length / t_r), dtype=float 

267 dhrf sampling on the provided grid 

268 

269 """ 

270 return _generic_time_derivative( 

271 glover_hrf, 

272 t_r=t_r, 

273 oversampling=oversampling, 

274 time_length=time_length, 

275 onset=onset, 

276 ) 

277 

278 

279def _generic_dispersion_derivative( 

280 t_r, 

281 oversampling=50, 

282 time_length=32.0, 

283 onset=0.0, 

284 undershoot=16, 

285 ratio=0.167, 

286 dispersion=1.0, 

287 dt=0.01, 

288): 

289 """Return the dispersion derivative of an hrf. 

290 

291 Parameters 

292 ---------- 

293 dt : :obj:`float`, default=0.01 

294 Dispersion step for the derivative. 

295 

296 See _gamma_difference_hrf for the other parameters description. 

297 """ 

298 return _compute_derivative_from_values( 

299 _gamma_difference_hrf( 

300 t_r, 

301 oversampling, 

302 time_length, 

303 onset, 

304 undershoot=undershoot, 

305 ratio=ratio, 

306 dispersion=dispersion, 

307 ), 

308 _gamma_difference_hrf( 

309 t_r, 

310 oversampling, 

311 time_length, 

312 onset, 

313 undershoot=undershoot, 

314 ratio=ratio, 

315 dispersion=dispersion + dt, 

316 ), 

317 dt=dt, 

318 ) 

319 

320 

321@rename_parameters({"tr": "t_r"}, end_version="0.13.0") 

322def spm_dispersion_derivative( 

323 t_r, oversampling=50, time_length=32.0, onset=0.0 

324): 

325 """Implement the :term:`SPM` dispersion derivative :term:`HRF` model. 

326 

327 Parameters 

328 ---------- 

329 t_r : :obj:`float` 

330 :term:`Repetition time<TR>`, in seconds (sampling period). 

331 

332 tr: 

333 

334 .. deprecated:: 0.11.0 

335 

336 Use ``t_r`` instead (see above). 

337 

338 oversampling : :obj:`int`, default=50 

339 Temporal oversampling factor in seconds. 

340 

341 time_length : :obj:`float`, default=32.0 

342 :term:`HRF` kernel length, in seconds. 

343 

344 onset : :obj:`float`, default=0.0 

345 Onset of the response in seconds. 

346 

347 Returns 

348 ------- 

349 dhrf : array of shape(length / tr * oversampling), dtype=float 

350 dhrf sampling on the oversampled time grid 

351 

352 """ 

353 return _generic_dispersion_derivative( 

354 t_r, oversampling=oversampling, time_length=time_length, onset=onset 

355 ) 

356 

357 

358@rename_parameters({"tr": "t_r"}, end_version="0.13.0") 

359def glover_dispersion_derivative( 

360 t_r, oversampling=50, time_length=32.0, onset=0.0 

361): 

362 """Implement the Glover dispersion derivative :term:`HRF` model. 

363 

364 Parameters 

365 ---------- 

366 t_r : :obj:`float` 

367 :term:`Repetition time<TR>`, in seconds (sampling period). 

368 

369 oversampling : :obj:`int`, default=50 

370 Temporal oversampling factor in seconds. 

371 

372 tr: 

373 

374 .. deprecated:: 0.11.0 

375 

376 Use ``t_r`` instead (see above). 

377 

378 time_length : :obj:`float`, default=32.0 

379 :term:`HRF` kernel length, in seconds. 

380 

381 onset : :obj:`float`, default=0.0 

382 Onset of the response in seconds. 

383 

384 Returns 

385 ------- 

386 dhrf : array of shape(length / t_r * oversampling), dtype=float 

387 dhrf sampling on the oversampled time grid 

388 

389 """ 

390 return _generic_dispersion_derivative( 

391 t_r, 

392 oversampling=oversampling, 

393 time_length=time_length, 

394 onset=onset, 

395 undershoot=12.0, 

396 ratio=0.48, 

397 dispersion=0.9, 

398 ) 

399 

400 

401def _sample_condition( 

402 exp_condition, frame_times, oversampling=50, min_onset=-24 

403): 

404 """Make a possibly oversampled event regressor from condition information. 

405 

406 Parameters 

407 ---------- 

408 exp_condition : arraylike of shape (3, n_events) 

409 yields description of events for this condition as a 

410 (onsets, durations, amplitudes) triplet 

411 

412 frame_times : array of shape(n_scans) 

413 Sample time points. 

414 

415 oversampling : :obj:`int`, default=50 

416 Factor for oversampling event regressor. 

417 

418 min_onset : :obj:`float`, default=-24 

419 Minimal onset relative to frame_times[0] (in seconds) 

420 events that start before frame_times[0] + min_onset are not considered. 

421 

422 Returns 

423 ------- 

424 regressor : array of shape(over_sampling * n_scans) 

425 Possibly oversampled event regressor. 

426 

427 frame_times_high_res : array of shape(over_sampling * n_scans) 

428 Time points used for regressor sampling. 

429 

430 """ 

431 # Find the high-resolution frame_times 

432 n_frames = frame_times.size 

433 min_onset = float(min_onset) 

434 n_frames_high_res = _compute_n_frames_high_res( 

435 frame_times, min_onset, oversampling 

436 ) 

437 

438 frame_times_high_res = np.linspace( 

439 frame_times.min() + min_onset, 

440 frame_times.max() * (1 + 1.0 / (n_frames - 1)), 

441 np.rint(n_frames_high_res).astype(int), 

442 ) 

443 

444 # Get the condition information 

445 onsets, durations, values = tuple(map(np.asanyarray, exp_condition)) 

446 if (onsets < frame_times[0] + min_onset).any(): 

447 warnings.warn( 

448 ( 

449 "Some stimulus onsets are earlier " 

450 f"than {frame_times[0] + min_onset} in the" 

451 " experiment and are thus not considered in the model." 

452 ), 

453 UserWarning, 

454 stacklevel=find_stack_level(), 

455 ) 

456 

457 # Set up the regressor timecourse 

458 tmax = len(frame_times_high_res) 

459 regressor = np.zeros_like(frame_times_high_res).astype(np.float64) 

460 t_onset = np.minimum( 

461 np.searchsorted(frame_times_high_res, onsets), tmax - 1 

462 ) 

463 for t, v in zip(t_onset, values): 

464 regressor[t] += v 

465 t_offset = np.minimum( 

466 np.searchsorted(frame_times_high_res, onsets + durations), tmax - 1 

467 ) 

468 

469 # Handle the case where duration is 0 by offsetting at t + 1 

470 for i, t in enumerate(t_offset): 

471 if t < (tmax - 1) and t == t_onset[i]: 

472 t_offset[i] += 1 

473 

474 for t, v in zip(t_offset, values): 

475 regressor[t] -= v 

476 regressor = np.cumsum(regressor) 

477 

478 return regressor, frame_times_high_res 

479 

480 

481def _compute_n_frames_high_res(frame_times, min_onset, oversampling): 

482 """Compute the number of frames after upsampling.""" 

483 n_frames = frame_times.size 

484 mini, maxi = _extrema(frame_times) 

485 n_frames_high_res = (n_frames - 1) * 1.0 / (maxi - mini) 

486 n_frames_high_res *= ( 

487 maxi * (1 + 1.0 / (n_frames - 1)) - mini - min_onset 

488 ) * oversampling 

489 return n_frames_high_res + 1 

490 

491 

492def _extrema(arr): 

493 """Return the min and max of an array.""" 

494 return np.min(arr), np.max(arr) 

495 

496 

497def _resample_regressor(hr_regressor, frame_times_high_res, frame_times): 

498 """Sub-sample the regressors at frame times. 

499 

500 Parameters 

501 ---------- 

502 hr_regressor : array of shape(n_samples), 

503 the regressor time course sampled at high temporal resolution 

504 

505 frame_times_high_res : array of shape(n_samples), 

506 the corresponding time stamps 

507 

508 frame_times : array of shape(n_scans), 

509 the desired time stamps 

510 

511 Returns 

512 ------- 

513 regressor : array of shape(n_scans) 

514 The resampled regressor. 

515 

516 """ 

517 f = interp1d(frame_times_high_res, hr_regressor) 

518 return f(frame_times).T 

519 

520 

521def orthogonalize(X): 

522 """Orthogonalize every column of design `X` w.r.t preceding columns. 

523 

524 Parameters 

525 ---------- 

526 X : array of shape(n, p) 

527 The data to be orthogonalized. 

528 

529 Returns 

530 ------- 

531 X : array of shape(n, p) 

532 The data after orthogonalization. 

533 

534 Notes 

535 ----- 

536 X is changed in place. The columns are not normalized. 

537 

538 """ 

539 if X.size == X.shape[0]: 

540 return X 

541 

542 for i in range(1, X.shape[1]): 

543 X[:, i] -= np.dot(np.dot(X[:, i], X[:, :i]), pinv(X[:, :i])) 

544 

545 return X 

546 

547 

548@fill_doc 

549def _regressor_names(con_name, hrf_model, fir_delays=None): 

550 """Return a list of regressor names, \ 

551 computed from con-name and hrf type \ 

552 when this information is explicitly given. 

553 

554 If hrf_model is a custom function or a list of custom functions, 

555 return their names. 

556 

557 Parameters 

558 ---------- 

559 con_name : :obj:`str` 

560 identifier of the condition 

561 %(hrf_model)s 

562 fir_delays : 1D array_like, optional 

563 Delays (in scans) used in case of an FIR model 

564 

565 Returns 

566 ------- 

567 names : :obj:`list` of strings, 

568 regressor names 

569 

570 """ 

571 check_params(locals()) 

572 # Default value 

573 names = [con_name] 

574 

575 # Handle strings 

576 if hrf_model in ["glover", "spm"]: 

577 names = [con_name] 

578 elif hrf_model in ["glover + derivative", "spm + derivative"]: 

579 names = [con_name, f"{con_name}_derivative"] 

580 elif hrf_model in [ 

581 "spm + derivative + dispersion", 

582 "glover + derivative + dispersion", 

583 ]: 

584 names = [con_name, f"{con_name}_derivative", f"{con_name}_dispersion"] 

585 elif hrf_model == "fir": 

586 names = [f"{con_name}_delay_{int(i)}" for i in fir_delays] 

587 elif callable(hrf_model): 

588 names = [f"{con_name}_{hrf_model.__name__}"] 

589 elif isinstance(hrf_model, Iterable) and all( 

590 callable(_) for _ in hrf_model 

591 ): 

592 names = [f"{con_name}_{model.__name__}" for model in hrf_model] 

593 elif isinstance(hrf_model, Iterable) and not isinstance(hrf_model, str): 

594 names = [f"{con_name}_{i}" for i in range(len(hrf_model))] 

595 

596 # Check that all names within the list are different 

597 if len(np.unique(names)) != len(names): 

598 raise ValueError(f"Computed regressor names are not unique: {names}") 

599 

600 return names 

601 

602 

603def _hrf_kernel(hrf_model, t_r, oversampling=50, fir_delays=None): 

604 """Return the list of matching kernels \ 

605 given the specification of the hemodynamic model and time parameters. 

606 

607 Parameters 

608 ---------- 

609 %(hrf_model)s 

610 

611 t_r : :obj:`float` 

612 the repetition time in seconds 

613 

614 oversampling : :obj:`int`, default=50 

615 Temporal oversampling factor to have a smooth hrf. 

616 

617 fir_delays : 1D-array-like, optional 

618 List of delays (in scans) for finite impulse response models. 

619 

620 Returns 

621 ------- 

622 hkernel : :obj:`list` of arrays 

623 Samples of the hrf (the number depends on the hrf_model used). 

624 

625 """ 

626 check_params(locals()) 

627 acceptable_hrfs = [ 

628 "spm", 

629 "spm + derivative", 

630 "spm + derivative + dispersion", 

631 "fir", 

632 "glover", 

633 "glover + derivative", 

634 "glover + derivative + dispersion", 

635 None, 

636 ] 

637 error_msg = ( 

638 "Could not process custom HRF model provided. " 

639 "Please refer to the related documentation." 

640 ) 

641 if hrf_model == "spm": 

642 hkernel = [spm_hrf(t_r, oversampling)] 

643 elif hrf_model == "spm + derivative": 

644 hkernel = [ 

645 spm_hrf(t_r, oversampling), 

646 spm_time_derivative(t_r, oversampling), 

647 ] 

648 elif hrf_model == "spm + derivative + dispersion": 

649 hkernel = [ 

650 spm_hrf(t_r, oversampling), 

651 spm_time_derivative(t_r, oversampling), 

652 spm_dispersion_derivative(t_r, oversampling), 

653 ] 

654 elif hrf_model == "glover": 

655 hkernel = [glover_hrf(t_r, oversampling)] 

656 elif hrf_model == "glover + derivative": 

657 hkernel = [ 

658 glover_hrf(t_r, oversampling), 

659 glover_time_derivative(t_r, oversampling), 

660 ] 

661 elif hrf_model == "glover + derivative + dispersion": 

662 hkernel = [ 

663 glover_hrf(t_r, oversampling), 

664 glover_time_derivative(t_r, oversampling), 

665 glover_dispersion_derivative(t_r, oversampling), 

666 ] 

667 elif hrf_model == "fir": 

668 hkernel = [ 

669 np.hstack( 

670 ( 

671 np.zeros((f) * oversampling), 

672 np.ones(oversampling) * 1.0 / oversampling, 

673 ) 

674 ) 

675 for f in fir_delays 

676 ] 

677 elif callable(hrf_model): 

678 try: 

679 hkernel = [hrf_model(t_r, oversampling)] 

680 except TypeError: 

681 raise ValueError(error_msg) 

682 elif isinstance(hrf_model, Iterable) and all( 

683 callable(_) for _ in hrf_model 

684 ): 

685 try: 

686 hkernel = [model(t_r, oversampling) for model in hrf_model] 

687 except TypeError: 

688 raise ValueError(error_msg) 

689 elif hrf_model is None: 

690 hkernel = [np.hstack((1, np.zeros(oversampling - 1)))] 

691 else: 

692 raise ValueError( 

693 f'"{hrf_model}" is not a known hrf model. ' 

694 "Use either a custom model or " 

695 f"one of {acceptable_hrfs}" 

696 ) 

697 return hkernel 

698 

699 

700@fill_doc 

701def compute_regressor( 

702 exp_condition, 

703 hrf_model, 

704 frame_times, 

705 con_id="cond", 

706 oversampling=50, 

707 fir_delays=None, 

708 min_onset=-24, 

709): 

710 """Convolve regressors with :term:`HRF` model. 

711 

712 Parameters 

713 ---------- 

714 exp_condition : array-like of shape (3, n_events) 

715 yields description of events for this condition as a 

716 (onsets, durations, amplitudes) triplet 

717 %(hrf_model)s 

718 frame_times : array of shape (n_scans) 

719 the desired sampling times 

720 

721 con_id : :obj:`str`, default='cond' 

722 Identifier of the condition 

723 

724 oversampling : :obj:`int`, default=50 

725 Oversampling factor to perform the convolution. 

726 

727 fir_delays : [int] 1D-array-like or None, default=None 

728 Delays (in scans) used in case of a finite impulse response model. 

729 

730 min_onset : :obj:`float`, default=-24 

731 Minimal onset relative to frame_times[0] (in seconds) 

732 events that start before frame_times[0] + min_onset are not considered. 

733 

734 Returns 

735 ------- 

736 computed_regressors : array of shape(n_scans, n_reg) 

737 Computed regressors sampled at frame times. 

738 

739 reg_names : :obj:`list` of strings 

740 Corresponding regressor names. 

741 

742 """ 

743 check_params(locals()) 

744 # fir_delays should be integers 

745 if fir_delays is not None: 

746 fir_delays = [int(x) for x in fir_delays] 

747 oversampling = int(oversampling) 

748 

749 # this is the minimal t_r in this run, not necessarily the true t_r 

750 t_r = _calculate_tr(frame_times) 

751 # 1. create the high temporal resolution regressor 

752 hr_regressor, frame_times_high_res = _sample_condition( 

753 exp_condition, frame_times, oversampling, min_onset 

754 ) 

755 

756 # 2. create the hrf model(s) 

757 hkernel = _hrf_kernel(hrf_model, t_r, oversampling, fir_delays) 

758 

759 # 3. convolve the regressor and hrf, and downsample the regressor 

760 conv_reg = np.array( 

761 [np.convolve(hr_regressor, h)[: hr_regressor.size] for h in hkernel] 

762 ) 

763 

764 # 4. temporally resample the regressors 

765 if hrf_model == "fir" and oversampling > 1: 

766 computed_regressors = _resample_regressor( 

767 conv_reg[:, oversampling - 1 :], 

768 frame_times_high_res[: 1 - oversampling], 

769 frame_times, 

770 ) 

771 else: 

772 computed_regressors = _resample_regressor( 

773 conv_reg, frame_times_high_res, frame_times 

774 ) 

775 

776 # 5. ortogonalize the regressors 

777 if hrf_model != "fir": 

778 computed_regressors = orthogonalize(computed_regressors) 

779 

780 # 6 generate regressor names 

781 reg_names = _regressor_names(con_id, hrf_model, fir_delays=fir_delays) 

782 return computed_regressors, reg_names 

783 

784 

785def _calculate_tr(frame_times): 

786 """Calculate TR from differences in frame_times. 

787 

788 Parameters 

789 ---------- 

790 frame_times : array of shape (n_scans) 

791 the desired sampling times 

792 

793 Returns 

794 ------- 

795 :obj:`float` 

796 repetition time 

797 """ 

798 return float(np.min(np.diff(frame_times)))