Nipype: SPM realignment multiple sessions

Hello everyone,

I’m currently setting up my first nipype script and would like to ask a question wrt file selection and flow:

I have a data set with 2 functional runs per participant and would like to realign them together (corresponding to two sessions within the realignment module in a classic SPM batch), resulting in one mean image. When running the node in my current set up, using IdentityInterface and SelectFiles to get and pass the run corresponding files to the realign node like this:

infosource = Node(IdentityInterface(fields=['subject_id',
                                            'session_id'],
                  name="infosource")
infosource.iterables = [('subject_id', subject_list),
                        ('session_id', session_list)]
                    
templates = {'func': 'fMRI/{subject_id}/{session_id}/func_*.nii',
                     'struct':'fMRI/{subject_id}/{session_id}/struc.nii'} 
selectfiles = Node(SelectFiles(templates,
                               base_directory=experiment_dir),
                               name="selectfiles") 

mypipeline.connect([(infosource, selectfiles, [('subject_id', 'subject_id'),
                                               ('session_id', 'session_id')]),
                    (selectfiles, realign, [('func', 'in_files')]),
                    etc...

realignment is done run wise and not across runs, also resulting in two mean images.
This continues of course throughout my pipeline (e.g. coregistration, etc.).

I guess this happens, because I use Iterables for both, participants and sessions, whereas I
should probably use a MapNode/ Iterfield for the sessions.
Could someone give me a hint / or has an idea on how to exactly do that, as I’m not quite sure!?

Regards, Peer

Hi,

I also have a similar problem. I’ve got 6 runs and want to avoid running coregistration for each run separately. Is there a more efficient way to do this?

Best,

Sebastian

Ahoi hoi @Sebastian,

in the end it kinda depends on your dataset and it’s structure, as well as the preprocessing and subsequent analyses steps you’ve planed.
But for now, assuming you have a dataset (of course in BIDS) that contains 6 functional runs of the same task for a bunch of participants and you want to apply realignment (via SPM) and coregistration (via FreeSurfer) something like the following should work:

import nipype.interfaces.freesurfer as fs  
import nipype.interfaces.spm as spm  
import nipype.interfaces.utility as util 
import nipype.interfaces.io as io   
import nipype.algorithms.misc as mc  
import nipype.pipeline.engine as eng  

# Gunzip node - unzip functional images, as SPM can't read .gz
gunzip = eng.MapNode(mc.Gunzip(), name="gunzip", iterfield=['in_file'])

# realign node - register functional images to the mean functional
realign = eng.Node(spm.Realign(register_to_mean=True),
                   name="realign")

# coregistration node - coregister the mean functional to the anatomical image
bbregister = eng.Node(fs.BBRegister(init='spm',
                             contrast_type='t2',
                             out_fsl_file=True),
                      name='bbregister')

# Create a preprocessing workflow
preproc = eng.Workflow(name='preproc')
preproc.base_dir = opj(experiment_dir, working_dir)

# Connect all components of the preprocessing workflow  
preproc_masks.connect([(gunzip, realign, [('out_file', 'in_files')]), 
                       (realign, bbregister, [('mean_image', 'source_file')]),]) 

# Infosource - a function free node to iterate over the list of subject names
infosource = eng.Node(util.IdentityInterface(fields=['subject_id']),
                      name="infosource")
infosource.iterables = [('subject_id', subject_list)]

# SelectFiles - to grab the data 
templates = {'func': 'bids_dataset/{subject_id}/func/task-test_run-*_bold.nii.gz'}
selectfiles = eng.Node(io.SelectFiles(templates,
                                      base_directory=experiment_dir),
                       name="selectfiles")

# connect Infosource and SelectFiles to the preprocessing workflow
preproc.connect([(infosource, selectfiles, [('subject_id', 'subject_id')]),
                 (infosource, bbregister, [('subject_id', 'subject_id')]),
                 (selectfiles, gunzip, [('func', 'in_file')]),])

(This of course doesn’t include necessary paths like the experiment, working and FreeSurfer directory, or the subject list.)

Based on that you can continue by e.g. coregister & transform the functional runs to a certain reference space via ANTs.

HTH, best, Peer

1 Like

Hi,

Thanks Peer!
So if I understand the code correctly you select all functional runs at once and realign them to the mean image across runs. Then the mean image across runs is registered to the t1. Is that correct?

So far I’ve used ‘task_id’ (functional run) as iterable as well and done the realignment, coregistration and normalization separately. So this would indeed be way more efficient.
Is the realignment across runs a valid way to go?

Best,

Sebastian

Hi @Sebastian,

no biggie!
Yes, exactly. The option register_to_mean=True will result in a two pass procedure:
–> initially SPM realigns each session to each other by aligning the first volume from each session to
     to the first volume of the first session and subsequently all volumes within each session are aligned to the      first volume of that session
–> after that the volumes from the first realignment step are used to create a mean image and than all volumes
     are aligned to that mean image

Using register_to_mean=False will “just” do the initial realignment.
In the example above the resulting mean image is registered to the t1 weighted image, yep.

I’m not sure if I completely understand your second question. Do you mean if it’s okay to align images across runs? If that’s the case:
Puh, that’s one hell of a question (at least for me, I hope others with more expertise will drop in as well).
If you have multiple runs of the same task/conditions and plan to analyze your data in a mass univariate way (GLM) across runs, than time series should correspond to “roughly” the same location/voxel within and between runs. Otherwise, chances are that the signal of a given voxel contains signal from two (or more) different voxels or even types of tissues, up to signal loss in voxels near the borders of the images (e.g. in frontal areas).
If you meant something else: sorry, could you maybe elaborate on that?

HTH, best, Peer

Hi @PeerHerholz ,

Thanks for your answer and sorry for the delayed response. So even though I’m selecting all functional runs at once nipype detects that they are different runs?

With regards to the second part, yes that’s what I meant. So if I plan to analyze my data using MVPA this approach is no longer valid?

Thank you very much for your help,

Best,

Sebastian

Hi, Peer,

I have an issue when preproccessing multi-session data using the following code:

templates = {'anat': 'sub-{subject_id}/ses-d1/anat/'
                     'sub-{subject_id}_ses-d1_T1w.nii.gz',
             'func': 'sub-{subject_id}/ses-{ses_id}/func/'
                     'sub-{subject_id}_ses-{ses_id}_task-{task_id}_bold.nii.gz'}

# Create SelectFiles node
sf = Node(SelectFiles(templates,
                     base_directory=data_dir,
                     sort_filelist=True),
         name='selectfiles')
#sf.inputs.ses_id = 'd1'
# sf.inputs.ses_id = ['d1','d2','d3']
sf.inputs.task_id = 'exp'

subject_list = ['001', '002']
ses_list = ['d1','d2','d3']
sf.iterables = [('subject_id', subject_list),
                ('ses_id', ses_list)]

preproc.connect([(sf, gunzip_anat, [('anat', 'in_file')]),
                 (sf, gunzip_func, [('func', 'in_file')])])

My question is about the layout of the output folder: I expected that the output will has two layers of the folders as below:

subject-001
—session-001
—session-002
subject-002
—session-001
—session-002

But it turned out only one layer and each folder combined both subject and session, see below.

Screenshot%20from%202020-02-19%2017-35-43

Functionally, it’s OK, but I am wondering, can I somehow change the layout of the output folder as I expected?

the picture did not get attached.

1 Like

Hi, @satra,
Thanks for your response. Uploaded now :wink:

Dear @satra @PeerHerholz,
I posted my problem as a new post here: How to use datasink to get BIDS styple output folders structure?.
And found the issue myself a few minutes after posting it. :joy::joy:

Hi Peer,

I tried changing the func node to MapNode but the realignment is still generating 4 mean_image (I have 4 runs) Do you see anything I did wrong in my code?

gunzip_func = MapNode(Gunzip(in_file=func_file), iterfield=[‘in_file’], name=‘gunzip_func’)
realign = Node(spm.Realign(), name=“realign”)
realign.inputs.register_to_mean = True
preproc.connect([
# realign functional data
(gunzip_func, realign, [(‘out_file’, ‘in_files’)])])

String template with {}-based strings

templates = {‘anat’: ‘sub-{subject_id}/anat/sub{subject_id}_T1w.nii.gz’,
‘func’: ‘sub-{subject_id}/func/run-{run_id}/sub{subject_id}_run-{run_id}_bold.nii.gz’}

Create SelectFiles node

sf = Node(SelectFiles(templates,
base_directory=os.path.abspath(‘data’),
sort_filelist=True),
name=‘selectfiles’)

subject_list = [‘17’,‘22’]
run_list = [‘01’,‘02’,‘03’,‘04’]
sf.iterables = [(‘subject_id’, subject_list),(‘run_id’, run_list)]
preproc.connect([(sf, gunzip_anat, [(‘anat’, ‘in_file’)]),
(sf, gunzip_func, [(‘func’, ‘in_file’)])])

Hi @HuangHam,

I’m truly sorry for my late reply.

As this thread went a bit back and forth re the precise topic/question: Could you maybe provide a bit more information on your data and what you’re trying to achieve/want to implement (if possible)? Sorry again.

Cheers, Peer

No apologies needed! Yeah so essentially I hope to preprocess my functional fMRI data using SPM in Nipype. For each subject, I have an anatomical scan and 4 functional scans. Then I want to first realign the 4 function scans, producing 4 realigned functional images and 1 mean functional image across all 4 runs. This is what would be output in matlab SPM. Then I want to coregister the one and only anatomical image to this mean functional image. Right now somehow NiPype spm.realign outputs 4 mean images, one for each run. I’m just puzzled why it’s different from matlab SPM and how to fix it. I looked at your code in this Neurostar thread and thought maybe because when I unzip functional files, I need to make it a map node instead of a regular node. But this didn’t work.

Have you considered fMRIPrep?
My study is similar to yours (one anatomical, four functional runs for each subject), and fMRIPrep worked well for me.

https://fmriprep.org/en/stable/

thanks for recommending it! I’ll look into fMRIPrep as well but I still want to figure out how to make the SPM realignment work on NiPype. Otherwise, this question will bug me until the end of my life haha.

Hi @HuangHam,

thanks for the additional information.

First of all, I completely agree with @JohnAtl that it’s a very good idea to use fMRIPrep (and comparable BIDS Apps) to process data as it comes with many benefits including throughout tested state-of-the-art workflows, transparency, reproducibility, FAIR-ness, etc. .

However, now to your question. Based on the description of your data and intended preprocessing steps the code example I shared above should also work for you (of course after adapting the paths, etc.). As outlined here, the crucial part is the register_to_mean parameter but you have that set to True. I think the problem could be that you also set the run_id as an interable which means that nipype will repeat/iterate the workflow over runs as well. Could you try to remove the run_id from the iterables and define func like so ‘sub-{subject_id}/func/run-*/sub{subject_id}_run-*_bold.nii.gz’} (that’s also what I did in my example above)?

Cheers, Peer

P.S.: Are you using/planning to use BIDS to structure/describe/define your dataset? If so, the .../run-{run_id}/... part should be removed as BIDS suggests to put all runs of one session in one folder.


HuangHam

1m

Oh cool! Thanks. Yeah I thought * might be the reason (I did set register_to_mean to true), but we also don’t want to treat all runs the same, right? I’m hoping to have a mean image for registering the anatomical image, but I also need to realign functional images separately for each run for other steps such as slice-timing correction. If I replace run_id with *, will Nipype collapse all runs into one run? Which wouldn’t be what I want either.

actually, I tried replacing run_id with * and it did work! It’s magical. But in the case where I want to exclude some runs for some participants, I guess it’s impossible? Since * will automatically select all the runs in the directory.

Hi @HuangHam,

sorry for the late reply.

So, what should happen is the two-stage approach described here which is what you would like to have (if I understood it correctly).

Cheers, Peer

Hi @HuangHam,

sorry for the late reply.

I think you should be able to address this via defining character ranges, ie. getting run 1, 2 and 3:

‘sub-{subject_id}/func/run-*/sub{subject_id}_run-*[1-3] _bold.nii.gz’

However, not completely sure re your data structure as noted above.

Cheers, Peer

1 Like