Skip to content

Commit e9d8c2a

Browse files
authored
[ENH,FIX] optimally combine temporal + dispersion derivatives for beta estimation (#296)
* add function _calc_beta_map - the function implements calhoun 2004 beta estimate combination with temporal derivatives - also minor switch from Nift1Image to Nifti2image * test various hrf_models on the _calc_beta_map function
1 parent 544a874 commit e9d8c2a

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

src/nibetaseries/interfaces/nistats.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def _run_interface(self, runtime):
104104
beta_maps[new_delay_ttype] = [beta_map]
105105
else:
106106
# calculate the beta map
107-
beta_map = model.compute_contrast(trial_type, output_type='effect_size')
107+
beta_map = _calc_beta_map(model, trial_type, self.inputs.hrf_model)
108108
design_matrix_collector[trial_idx] = model.design_matrices_[0]
109109
# assign beta map to appropriate list
110110
if trial_type in beta_maps:
@@ -122,7 +122,7 @@ def _run_interface(self, runtime):
122122
ave_residual = residuals / (trial_idx + 1)
123123
# make residual nifti image
124124
residual_file = os.path.join(runtime.cwd, 'desc-residuals_bold.nii.gz')
125-
nib.Nifti1Image(
125+
nib.Nifti2Image(
126126
ave_residual,
127127
model.residuals[0].affine,
128128
model.residuals[0].header,
@@ -213,7 +213,7 @@ def _run_interface(self, runtime):
213213
t_type = lsa_df.loc[i_trial, 'original_trial_type']
214214

215215
# calculate the beta map
216-
beta_map = model.compute_contrast(t_name, output_type='effect_size')
216+
beta_map = _calc_beta_map(model, t_name, self.inputs.hrf_model)
217217

218218
# assign beta map to appropriate list
219219
if t_type in beta_maps:
@@ -344,3 +344,51 @@ def _select_confounds(confounds_file, selected_confounds):
344344
msg = "The selected confounds contain nans: {conf}".format(conf=expanded_confounds)
345345
raise ValueError(msg)
346346
return desired_confounds
347+
348+
349+
def _calc_beta_map(model, trial_type, hrf_model):
350+
"""
351+
Calculates the beta estimates for every voxel from
352+
a nistats model
353+
354+
Parameters
355+
----------
356+
model : nistats.first_level_model.FirstLevelModel
357+
a fit model of the first level results
358+
359+
Returns
360+
-------
361+
beta_map : nibabel.nifti2.Nifti2Image
362+
nifti image containing voxelwise beta estimates
363+
"""
364+
import numpy as np
365+
366+
# calculate the beta map
367+
beta_map_list = []
368+
beta_map_base = model.compute_contrast(trial_type, output_type='effect_size')
369+
beta_map_list.append(beta_map_base.get_fdata())
370+
beta_sign = np.where(beta_map_list[0] < 0, -1, 1)
371+
if 'derivative' in hrf_model:
372+
td_contrast = '_'.join([trial_type, 'derivative'])
373+
beta_map_list.append(
374+
model.compute_contrast(
375+
td_contrast, output_type='effect_size').get_fdata())
376+
if 'dispersion' in hrf_model:
377+
dd_contrast = '_'.join([trial_type, 'dispersion'])
378+
beta_map_list.append(
379+
model.compute_contrast(
380+
dd_contrast, output_type='effect_size').get_fdata())
381+
382+
if len(beta_map_list) == 1:
383+
beta_map = beta_map_base
384+
else:
385+
beta_map_array = beta_sign * \
386+
np.sqrt(
387+
np.sum(
388+
np.array([np.power(c, 2) for c in beta_map_list]), axis=0))
389+
beta_map = nib.Nifti2Image(
390+
beta_map_array,
391+
beta_map_base.affine,
392+
beta_map_base.header)
393+
394+
return beta_map

src/nibetaseries/interfaces/tests/test_nistats.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@
66
import nibabel as nib
77
from nilearn.image import load_img
88
import pandas as pd
9+
from nistats import first_level_model
910
import pytest
1011

1112

1213
from ..nistats import (LSSBetaSeries, LSABetaSeries,
1314
_lss_events_iterator, _lsa_events_converter,
14-
_select_confounds)
15+
_select_confounds,
16+
_calc_beta_map)
1517

1618

17-
@pytest.mark.parametrize("use_nibabel", [(True), (False)])
19+
@pytest.mark.parametrize(
20+
"use_nibabel,hrf_model",
21+
[
22+
(True, 'spm'),
23+
(False, 'spm + derivative'),
24+
(False, 'spm + derivative + dispersion')
25+
]
26+
)
1827
def test_lss_beta_series(sub_metadata, preproc_file, sub_events,
19-
confounds_file, brainmask_file, use_nibabel):
28+
confounds_file, brainmask_file, use_nibabel,
29+
hrf_model):
2030
"""Test lss interface with nibabel nifti images
2131
"""
2232
if use_nibabel:
@@ -27,7 +37,6 @@ def test_lss_beta_series(sub_metadata, preproc_file, sub_events,
2737
mask_file = str(brainmask_file)
2838

2939
selected_confounds = ['white_matter', 'csf']
30-
hrf_model = 'spm'
3140
with open(str(sub_metadata), 'r') as md:
3241
bold_metadata = json.load(md)
3342

@@ -274,3 +283,39 @@ def test_select_confounds(confounds_file, selected_confounds, nan_confounds,
274283
vals = confounds_df[nan_c].values
275284
expected_result = np.nanmean(vals[vals != 0])
276285
assert res_df[nan_c][0] == expected_result
286+
287+
288+
@pytest.mark.parametrize(
289+
"hrf_model",
290+
[
291+
("glover"),
292+
("glover + derivative"),
293+
("glover + derivative + dispersion"),
294+
],
295+
)
296+
def test_calc_beta_map(sub_metadata, preproc_file, sub_events,
297+
confounds_file, brainmask_file, hrf_model):
298+
299+
model = first_level_model.FirstLevelModel(
300+
t_r=2,
301+
slice_time_ref=0,
302+
hrf_model=hrf_model,
303+
mask=str(brainmask_file),
304+
smoothing_fwhm=0,
305+
signal_scaling=False,
306+
high_pass=0.008,
307+
drift_model='cosine',
308+
verbose=1,
309+
minimize_memory=False,
310+
)
311+
312+
lsa_df = _lsa_events_converter(str(sub_events))
313+
314+
model.fit(str(preproc_file), events=lsa_df)
315+
316+
i_trial = lsa_df.index[0]
317+
t_name = lsa_df.loc[i_trial, 'trial_type']
318+
319+
beta_map = _calc_beta_map(model, t_name, hrf_model)
320+
321+
assert beta_map.shape == nib.load(str(brainmask_file)).shape

0 commit comments

Comments
 (0)