— Cognitive Neuroscience

DICOM -> BIDS

DICOM -> BIDS

A friend asked if I could share some scripts to get fMRI data in proper BIDS format.

So I thought it would make sense to just create a quick post going over my workflow, in case it happens to be of interest to others.

A few caveats.

  1. I have not actually used these scripts in over two months (since December 2018). I find that these new specifications can change fairly rapidly. (E.g. fmriprep changing some of their naming conventions after I had processed half of my subjects.)
  2. While the following workflow worked for me, it is a bit of a bodge (ymmv).

The Workflow

Using dcm2bids convert your .ima files into NIfTI files. This will also put them in the appropriate subject folders and scan modality subfolders (e.g. anat, fmap, func).

This is the shell script I used for the conversion.

#!/bin/bash

bidsdir= <path to your bids directory>

# convert dicoms to BIDS format dataset
# MULTIPLE SUBJECTS
for subject in {101..110}; do
	dcm2bids \
	    -d $bidsdir/sourcedata/${subject} \
	    -p ${subject} \
	    -c $bidsdir/code/config.json \
	    -o $bidsdir
done

# Create Event Files
for subject in {159..164}; do
	# Copy from existing files
	cp $bidsdir/sub-101/func/*.tsv $bidsdir/sub-${subject}/func/
	# Rename Event Files
	for file in $bidsdir/sub-${subject}/func/*.tsv; do mv "$file" "${file/101/${subject}}"; done
done

# ADD Task Name
for subject in {159..164}; do
	for funcjson in $(ls $bidsdir/sub-${subject}/func/*.json); do 
		#Does TaskName exist?
		taskexist=$(cat ${funcjson} | jq '.TaskName')
		if [ "$taskexist" == "null" ]; then
		jsonname="${funcjson%.*}"
		taskfield=$(echo $jsonname | cut -f 4 -d '-' | cut -f 1 -d '_')
		jq '. |= . + {"TaskName":"'${taskfield}'"}' ${funcjson} > tasknameadd.json
		rm ${funcjson}
		mv tasknameadd.json ${funcjson}
		echo "TaskName was added to ${jsonname} and matches the tasklabel in the filename"
		else
		Taskquotevalue=$(jq '.TaskName' ${funcjson})
		Taskvalue=$(echo $Taskquotevalue | cut -d '"' -f2)	
		jsonname="${funcjson%.*}"
		taskfield=$(echo $jsonname | cut -d '_' -f2 | cut -d '-' -f2)
		if [ $Taskvalue == $taskfield ]; then
		echo "TaskName is present and matches the tasklabel in the filename"
		else
		echo "TaskName and tasklabel do not match"
		fi
		fi
done
done

One important thing to note is that because I didn’t have actual event files at this point I just created a set of placeholder files for subject 101, then those were just copied for the rest of the subjects and renamed.

Also another important file required for the shell script above to work correctly is the config.json. It uses this file to correctly categorize the scans and put them in the appropriate folders.

This was my config file, where “dataType” determines the folder the files will be put in, “customLabels” get added to the scan’s file names, “TaskName” gets added to the scan’s json file (not the name, the actual file), and “SeriesDescription” is used to locate the files that you want to group together. You will also want to grab the echo times from the fieldmap json files and add those.

{
    "descriptions": [
        {
            "dataType": "anat",
            "modalityLabel": "T1w",
            "criteria": {
                "SeriesDescription": "t1*"
            }
        },

        {
            "dataType": "anat",
            "modalityLabel": "T2w",
            "criteria": {
                "SeriesDescription": "t2*"
            }
        },

        {
            "dataType": "fmap",
            "modalityLabel": "magnitude1",
            "criteria": {
                "SeriesDescription": "gre_field_mapping*",
                "EchoTime": 0.00492,
                "ImageType": "M"
            }
        },
        {
            "dataType": "fmap",
            "modalityLabel": "magnitude2",
            "criteria": {
                "SeriesDescription": "gre_field_mapping*",
                "EchoTime": 0.00738,
                "ImageType": "M"
            }
        },
        {
            "dataType": "fmap",
            "modalityLabel": "phasediff",
            "EchoTime1": 0.00492,
            "EchoTime2": 0.00738,
            "criteria": {
                "SeriesDescription": "gre_field_mapping*",
                "EchoTime": 0.00738,
                "ImageType": "P"
            }
        },
        
        {
            "dataType": "func",
            "modalityLabel": "bold",
            "customLabels": "task-choose",
            "TaskName": "choose",
            "criteria": {
                "SidecarFilename": "*ep2*",
                "SeriesDescription": "*run*"
            }
        },
        {
            "dataType": "func",
            "modalityLabel": "bold",
            "customLabels": "task-switch",
            "TaskName": "switch",
            "criteria": {
                "SeriesDescription": "*switch*"
            }
        },
        {
            "dataType": "func",
            "modalityLabel": "bold",
            "customLabels": "task-gonogo",
            "TaskName": "gonogo",
            "criteria": {
                "SeriesDescription": "*gng*"
            }
        }

    ]
}

Then, once that is done, I run the following script:


#!/bin/bash
## Example script for updating json files with jq and sponge
## Run this code from one directory up (or adjust code)

for sbj in {159..164}

do
	#sbj=$1
	sbjdir=sub-${sbj}

	# add taskname to functionals
	for r in {1..9}
	do
	    funcpath=${sbjdir}/func/sub-${sbj}_task-choose_run-0${r}_bold.json
	    jq '.TaskName="choose"' <${funcpath} | sponge ${funcpath}
	done

	# add echo times to fieldmaps
	fmpath=${sbjdir}/fmap/sub-${sbj}_phasediff.json
	jq '.EchoTime1=0.00492' <${fmpath} | sponge ${fmpath}
	jq '.EchoTime2=0.00738' <${fmpath} | sponge ${fmpath}

	# add functionals to fieldmaps
	fmpath=${sbjdir}/fmap/sub-${sbj}_phasediff.json
	jq '.IntendedFor=[]' <$fmpath | sponge $fmpath
	for r in {1..9}
	do
	    funcpath=func/sub-${sbj}_task-choose_run-0${r}_bold.nii.gz
	    jq --arg funcpath "$funcpath" '.IntendedFor += [$funcpath]' <$fmpath | sponge $fmpath
	done

	for r in {1..2}
	do
	    funcpath=func/sub-${sbj}_task-gonogo_run-0${r}_bold.nii.gz
	    jq --arg funcpath "$funcpath" '.IntendedFor += [$funcpath]' <$fmpath | sponge $fmpath
	done


	funcpath=func/sub-${sbj}_task-switch_bold.nii.gz
	jq --arg funcpath "$funcpath" '.IntendedFor += [$funcpath]' <$fmpath | sponge $fmpath

done

This correctly names the functional files (adding the task), and also adds the files that you want associated with the field maps – these will be added to the “IntendedFor” field of the phasediff.json fieldmap file.

Then go to the bids validator and see what errors are being thrown and attempt to fix them.


Note that in the future this sort of thing will be done through Github – this is obviously not an ideal format…