Getting study level bids files (e.g., participants.tsv) in the correct place

Hi
I’m stuck with something and don’t want to start reinventing the wheel. Could you help?

I am iterating through the scanning sessions in our DICOM store and calling heudiconv for each. I’d like BIDS output.

If I include the subject and session in “outdir” then the func etc directories end up where I want them (e.g., bids/sub-ICN1/sess-2/func). But the study-level bids files then end up in those same session directories (e.g., bids/sub-ICN1/sess-2/dataset_description.json).
But, if I omit the subject and session then the func ends up at the study level.

I’ve tried various ways of implementing - dicom_dir_template and subjs/session below; or using files and infotoids. But I never get right mix. What have I broken?

I’m calling from python, hope that’s allowed.
Thanks for the great tool.
All the best, Rhodri

from heudiconv import bids
from heudiconv.main import workflow
from os import path

import glob

# Path to scan
dcmpth='/mnt/siemens-dicom/anon'

# Each subject
allsubjdicom = glob.glob(path.join(dcmpth,'*_RC_FOUNDCOG'))
for subjdicom in allsubjdicom:
    # get pithy sub from DICOM participant name
    flds = path.basename(subjdicom).split('_') 
    sub = '_'.join(flds[:2]) 

    # Each session for this subject
    allsessdicom = glob.glob(subjdicom + '/*')
    allsessdicom.sort() # sessions in ascending order by time

    for ses, sessdicom in enumerate(allsessdicom, start = 1):
        print(f'Working on sub-{sub} ses-{ses}')

        workflow(dicom_dir_template=path.join(dcmpth,'{subject}_RC_FOUNDCOG',path.basename(sessdicom),'*/*.dcm'),`
            converter='dcm2niix',
            outdir='/projects/pi-cusackrh/HPC_18_01039/foundcog/bids/',
            heuristic='/projects/pi-cusackrh/HPC_18_01039/repos/heudiconv_cusacklab/heuristic.py',
            overwrite=True, 
            bids_options=[],
            subjs=[sub],
            session=ses)

I think culprit could be in that custom heuristic

1 Like

In here?

import os
from os import path

def create_key(template, outtype=('nii.gz',), 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):
    """Heuristic evaluator for determining which runs belong where
    allowed template fields - follow python string module:
    item: index within category
    subject: participant id
    seqitem: run number during scanning
    subindex: sub index within group
    """
    anat = create_key('anat/sub-{subject}_{session}_run-00{item:01d}_T1w')
    func_video = create_key('func/sub-{subject}_{session}_dir-AP_task-videos_run-00{item:01d}_bold')
    func_pictures = create_key('func/sub-{subject}_{session}_dir-AP_task-pictures_run-00{item:01d}_bold')
    func_rest5 = create_key('func/sub-{subject}_{session}_dir-AP_task-rest5_run-00{item:01d}_bold')
    func_rest10 = create_key('func/sub-{subject}_{session}_dir-AP_task-rest10_run-00{item:01d}_bold')
    dwi = create_key('dwi/sub-{subject}_{session}_dir-{dir}_run-{item}_dwi')
    fmap = create_key('fmap/sub-{subject}_{session}_dir-{dir}_run-{item}_epi')
    info = {anat: [], func_video: [], func_pictures: [], func_rest5: [], func_rest10: [], dwi: [], fmap:[] }
    
    for idx, s in enumerate(seqinfo):
        if 't2_tse_tra_p3_noisereduction' in s.protocol_name:
            info[anat].append(s.series_id)
        if (s.dim1 == 64) and (s.dim2 == 64) and ('cmrr_mbep2d_bold_videos_AP' in s.protocol_name):
            info[func_video].append(s.series_id)
        if (s.dim1 == 64) and (s.dim2 == 64) and ('cmrr_mbep2d_bold_Pictures_AP' in s.protocol_name):
            info[func_pictures].append(s.series_id)
        if (s.dim1 == 64) and (s.dim2 == 64) and ('cmrr_mbep2d_bold_Resting_10' in s.protocol_name):
            info[func_rest10].append(s.series_id)
        if (s.dim1 == 64) and (s.dim2 == 64) and ('cmrr_mbep2d_bold_Resting_5' in s.protocol_name):
            info[func_rest5].append(s.series_id)
        if (s.dim1 == 96) and (s.dim2 == 96) and ('cmrr_mbep2d_diff_AP_mb4_norm_pulse1.5x' in s.protocol_name):
            info[dwi].append({'item': s.series_id, 'dir':'AP'})
        if (s.dim1 == 96) and (s.dim2 == 96) and ('cmrr_mbep2d_diff_PA_mb4_norm_pulse1.5x' in s.protocol_name):
            info[dwi].append({'item': s.series_id, 'dir':'PA'})
        if (s.dim1 == 64) and (s.dim2 == 64) and ('cmrr_mbep2d_se_AP' in s.protocol_name):
            info[fmap].append({'item': s.series_id, 'dir':'AP'})
        if (s.dim1 == 64) and (s.dim2 == 64) and ('cmrr_mbep2d_se_PA' in s.protocol_name):
            info[fmap].append({'item': s.series_id, 'dir':'PA'})

    print('*** infotodict')
    print(info)

    return info

# # Get a numeric subject ID for BIDS format from list of DICOM file names
# def infotoids(seqinfos, outdir):
#     # Use the sub and ses labels as given in the output path
#     fp = outdir.split(path.sep)
#     if fp[-1][:4] == 'ses-':
#         ses=fp[-1][4:]
#     if fp[-2][:4] == 'sub-':
#         sub=fp[-2][4:]
    
#     return({'subject':sub, 'session':ses, 'locator':outdir})

P.S. heudiconv version 0.10.0

SOLVED: I’m sorry I realise now it was an error in my create_key commands, I needed
create_key(‘sub-{subject}/{session}/anat/sub-{subject}_{session}_run-00{item:01d}T1w’)
not
create_key('anat/sub-{subject}
{session}_run-00{item:01d}_T1w’)

1 Like