BIDS IntendedFor

Hi everyone,

I am in the process of converting my subjects into BIDS format using heudiconv and I’m having a little trouble getting the last few scans to populate the “IntendedFor” field correctly. Unfortunately, due to parameter changes and additional tasks added to the scan protocol throughout the 60 or so runs we’ve completed, the script I have has run into some nuances that I won’t get into unless needed – I’ve had to run two versions of the script on different scans.

I can’t get the “IntendedFor” field to populate at all in my DWI and resting state fmap json files. I’ve tried messing around with the POPULATE_INTENDED_FOR_OPTS section, but it may be a problem with the main body of my code. Help would be appreciated! For reference, I’ve included my heuristic below and would be happy to provide additional information:

#from __future__ import annotations

#from typing import Optional

#from heudiconv.utils import SeqInfo

import os


def create_key(template, outtype=('nii.gz', 'dicom'), annotation_classes=None):
    if template is None or not template:
        raise ValueError('Template must be a valid format string')
    return template, outtype, annotation_classes

def infotodict(seqinfo):
   t1w =       create_key('sub-{subject}/anat/sub-{subject}_T1w')
   t2w =       create_key('sub-{subject}/anat/sub-{subject}_T2w')
   dwi =       create_key('sub-{subject}/dwi/sub-{subject}_acq-dwi_dir-AP_dwi')
   epi1 =      create_key('sub-{subject}/func/sub-{subject}_task-rest_acq-func_run-01_bold')
   epi2 =      create_key('sub-{subject}/func/sub-{subject}_task-move_acq-func_run-01_bold')
   epi3 =      create_key('sub-{subject}/func/sub-{subject}_task-test_acq-func_run-01_bold')
   epi4 =      create_key('sub-{subject}/func/sub-{subject}_task-move_acq-func_run-02_bold')
   epi5 =      create_key('sub-{subject}/func/sub-{subject}_task-test_acq-func_run-02_bold')
   fmapAPdsi =    create_key('sub-{subject}/fmap/sub-{subject}_acq-dwi_dir-AP_epi')
   fmapPAdsi =    create_key('sub-{subject}/fmap/sub-{subject}_acq-dwi_dir-PA_epi')
   fmapAPrest =    create_key('sub-{subject}/fmap/sub-{subject}_acq-rest_dir-AP_epi')
   fmapPArest =    create_key('sub-{subject}/fmap/sub-{subject}_acq-rest_dir-PA_epi')
   fmapAPmovetest =    create_key('sub-{subject}/fmap/sub-{subject}_acq-func_dir-AP_epi')
   fmapPAmovetest =    create_key('sub-{subject}/fmap/sub-{subject}_acq-func_dir-PA_epi')

   info = {
               t1w: [],
               t2w: [],   
               dwi: [],   
               epi1: [], epi2: [], epi3: [],   epi4: [],   epi5: [], 
               fmapAPdsi: [], fmapPAdsi: [], fmapAPrest: [], fmapPArest: [], fmapAPmovetest: [], fmapPAmovetest: []
          }


   for s in seqinfo:
        # T1w
        if s.series_files == 240 and s.protocol_name == '0.8mm_TR2.3_t1_mprage':
            info[t1w].append(s.series_id)
        # T2w
        elif s.series_files == 30 and s.protocol_name == 'anat-T2w_acq-ADNI3Hippocampus':
               info[t2w].append(s.series_id)
        # dwi
        elif s.TR == 3.5 and s.TE == 102: # s.series_files == 134:
           # fmap
            if (s.series_files == 252): #(s.protocol_name == 'dwi_acq-AP64d2b'):
                if ('AP' in s.protocol_name):
                    if ( len(info[fmapAPdsi]) > 0 ) and ( s.total_files_till_now > latestAP ):
                       info[fmapAPdsi].clear()
                    info[fmapAPdsi].append(s.series_id)
                    latestAP = s.total_files_till_now
                elif ('PA' in s.protocol_name):
                    if ( len(info[fmapPAdsi]) > 0 ) and ( s.total_files_till_now > latestPA ):
                       info[fmapPAdsi].clear()
                    info[fmapPAdsi].append(s.series_id)
                    latestPA = s.total_files_till_now
            # dwi scan
            else:
                info[dwi].append(s.series_id)
        # Move/Test scans
        elif (s.TR == 2 and s.TE == 25.0):
            # fmaps
            if (s.series_files == 5):
                if ('_AP_' in s.protocol_name):
                    info[fmapAPmovetest].append(s.series_id)
                elif ('_PA_' in s.protocol_name):
                    info[fmapPAmovetest].append(s.series_id)
            else:
                if (s.protocol_name == 'BOLD_AP_movement_1'):
                    info[epi2].append(s.series_id)
                elif (s.protocol_name == 'BOLD_AP_testingrun_1'):
                    info[epi3].append(s.series_id)
                elif (s.protocol_name == 'BOLD_AP_movement_2'):
                    info[epi4].append(s.series_id)
                elif (s.protocol_name == 'BOLD_AP_testingrun_2'):
                    info[epi5].append(s.series_id)       
        #Resting state
        elif (s.TR == 1.66 and s.TE == 26.0):
            # fmaps
            if (s.series_files == 5):
                if ( ('_AP_' in s.protocol_name) or ('dirAP' in s.protocol_name) ):
                    info[fmapAPrest].append(s.series_id)
                elif ( ('_PA_' in s.protocol_name) or ('dirPA' in s.protocol_name) ):
                    info[fmapPArest].append(s.series_id)
            else:
                info[epi1].append(s.series_id)
   return info 

POPULATE_INTENDED_FOR_OPTS = {
        'matching_parameters': ['ImagingVolume', 'Shims'],
        'criterion': 'Closest'
}

just take a sample session, check the sizes and shim settings for those files – do they match between fieldmaps and target DWI / functionals?

Just checked and unfortunately they do not match which would explain why it’s not popping up…any work around for this? We previously used the following but this didn’t work either:

POPULATE_INTENDED_FOR_OPTS = {
    'matching_parameters': ['ModalityAcquisitionLabel'],
    'criterion': 'Closest'
    }

well – collect future sessions correctly linking fieldmaps to desired data sequences. Without matching geometry and shimming fieldmaps are of questionable applicability.

unfortunately “didn’t work” does not tell me much - i.e. “how” did you use it? Looking at the code

    elif matching_parameter == "ModalityAcquisitionLabel":
        # Check the acq label for the fmap and the modality for others:
        modality = op.basename(op.dirname(json_file))
        if modality == "fmap":
            # extract the <acq> entity:
            acq_label = BIDSFile.parse(op.basename(json_file))["acq"]
            assert acq_label is not None
            if any(s in acq_label.lower() for s in ["fmri", "bold", "func"]):
                key_info = ["func"]
            elif any(s in acq_label.lower() for s in ["diff", "dwi"]):
                key_info = ["dwi"]
            elif any(s in acq_label.lower() for s in ["anat", "struct"]):
                key_info = ["anat"]
        else:
            key_info = [modality]

I would have expected _acq-func in the fieldmap sequences to be used for functional runs, and _anat-dwi – for DWI. Is that what you had?

Hi @yarikoptic @neekee

I meet the same issue that ShimSetting in _phasediff.json is slightly different from ShimSetting in _bold.json.

For example,
in _bold.json, the ShimSetting is [2957, 4394, -6624, 354, -7, -55, -115, -26]
in _phasediff.json, the ShimSetting is [2965, 4393, -6624, 368, 13, -63, -91, -25]

So this slight difference will have an effect on func when it does SDC?

This is also causing heudiconv errors when adding IntendedFor to _phasediff.json file.

Best,
Yunhong