Skip to content

Commit c29b496

Browse files
Update tutorial dataset to support new SCT tutorials (#1)
1 parent 55f4a5e commit c29b496

8 files changed

Lines changed: 311 additions & 203 deletions

File tree

.github/workflows/run_batch_script.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ jobs:
4848
- name: Run sct_run_batch -s process_data.sh
4949
run: |
5050
cd "${{ github.event.repository.name }}/multi_subject"
51-
sct_run_batch -script process_data.sh -d
51+
sct_run_batch -script process_data.sh -config config.yml
5252
53-
- name: Assert that script executed without error
53+
- name: Output full log for sanity checking
54+
run: |
55+
cd "${{ github.event.repository.name }}/multi_subject/output/log"
56+
cat process_data_sub-01.log
57+
58+
- name: Check that script executed without error
5459
run: |
5560
cd "${{ github.event.repository.name }}/multi_subject/output/log"
5661
[ "$(compgen -G "process_data_sub-0*.log")" ] # Log files should exist
57-
[ ! "$(compgen -G "err.process_data_sub-0*.log")" ] # Error files should NOT exist
62+
[ ! "$(compgen -G "err.process_data_sub-0*.log")" ] # Error files should NOT exist
63+
grep -iF "warning" process_data_sub-01.log
64+
grep -iF "error" process_data_sub-01.log

.github/workflows/run_script_and_create_release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jobs:
4949
- name: "Checkout '${{ github.event.repository.name }}'"
5050
uses: actions/checkout@v2
5151
with:
52+
ref: ${{ env.GITHUB_SHA }}
5253
path: ${{ github.event.repository.name }}
5354

5455
- name: Run batch_single_subject.sh to generate intermediate files
@@ -59,15 +60,14 @@ jobs:
5960
- name: "Package data into tutorial-specific .zip files"
6061
run: |
6162
cd ${{ github.event.repository.name }}
62-
awk -F, '{ print $1,$2 }' tutorial-datasets.csv | xargs -l zip -u
63-
# Only package data if workflow is run manually. (This allows the workflow to double as a PR test.)
64-
if: github.event_name == 'workflow_dispatch'
63+
awk -F, '{ print $1,$2 }' tutorial-datasets.csv | xargs -l zip -ur
6564
6665
- uses: ncipollo/release-action@v1
6766
name: Create release ('${{ github.event.inputs.release_title }}')
6867
id: create_release
6968
with:
7069
tag: ${{ github.event.inputs.release_title }}
70+
commit: ${{ env.GITHUB_SHA }}
7171
token: ${{ secrets.GITHUB_TOKEN }}
7272
# Only create release if workflow is run manually. (This allows the workflow to double as a PR test.)
7373
if: github.event_name == 'workflow_dispatch'

multi_subject/config.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
########################################################################################################
2+
# config.yml: Configuration parameters for sct_run_batch
3+
#
4+
# Usage: "sct_run_batch -script process_data.sh -config config.yml"
5+
#
6+
# Each "key: value" entry in this file corresponds to an input argument for 'sct_run_batch'. For example,
7+
# you could call "sct_run_batch -path-data ./data -path-output ./output [...]" for all of the arguments,
8+
# and you would get the same results. However, we strongly recommend using a config file like this one,
9+
# as it will help with reproducibility.
10+
########################################################################################################
11+
12+
# Path to the folder containing the BIDS dataset
13+
path_data: "./data"
14+
15+
# Path to save the output to. This path is what determines the following subdirectory environment variables:
16+
# - PATH_DATA_PROCESSED: <path_output>/data_processed
17+
# - PATH_RESULTS: <path_output>/results
18+
# - PATH_QC: <path_output>/qc
19+
# - PATH_LOG: <path_output>/log
20+
# You can then use these subdirectory environment variables to direct the output for your batch processing script.
21+
path_output: "./output"
22+
23+
# Location of manually-corrected segmentations
24+
path_segmanual: "./seg_manual"
25+
26+
# If each subject folder starts with a prefix, indicate it here. Otherwise, set to ""
27+
subject_prefix: "sub-"
28+
29+
# If you only want to process specific subjects, uncomment one or both the lines below:
30+
#include-list: sub-01 sub-05
31+
#exclude-list: sub-03
32+
33+
# Number of jobs for parallel processing
34+
# To know the number of available cores, run: getconf _NPROCESSORS_ONLN
35+
# We recommend not using more than half the number of available cores.
36+
jobs: 4
37+
38+
# Number of jobs for ANTs routine. Set to 1 if ANTs functions crash when CPU saturates.
39+
#itk_threads: 1

multi_subject/parameters.sh

Lines changed: 0 additions & 45 deletions
This file was deleted.

multi_subject/process_data.sh

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,164 @@
11
#!/bin/bash
22
#
3-
# Process data. This script should be run within the subject's folder.
3+
# Process data. This script is designed to be run in the folder for a single subject, however 'sct_run_batch' can be
4+
# used to run this script multiple times in parallel across a multi-subject BIDS dataset.
45
#
56
# Usage:
6-
# ./process_data.sh <SUBJECT> <FILEPARAM>
7+
# ./process_data.sh <SUBJECT>
78
#
89
# Example:
9-
# ./process_data.sh sub-03 parameters.sh
10+
# ./process_data.sh sub-03
1011
#
1112
# Author: Julien Cohen-Adad
1213

13-
# The following global variables are retrieved from parameters.sh but could be
14-
# overwritten here:
15-
# PATH_QC="~/qc"
14+
# BASH SETTINGS
15+
# ======================================================================================================================
1616

1717
# Uncomment for full verbose
18-
set -v
18+
# set -v
1919

2020
# Immediately exit if error
2121
set -e
2222

2323
# Exit if user presses CTRL+C (Linux) or CMD+C (OSX)
2424
trap "echo Caught Keyboard Interrupt within script. Exiting now.; exit" INT
2525

26-
# Retrieve input params
27-
SUBJECT=$1
28-
FILEPARAM=$2
29-
30-
31-
# FUNCTIONS
32-
# ==============================================================================
26+
# CONVENIENCE FUNCTIONS
27+
# ======================================================================================================================
3328

34-
# Check if manual label already exists. If it does, copy it locally. If it does
35-
# not, perform labeling.
36-
label_if_does_not_exist(){
29+
label_if_does_not_exist() {
30+
###
31+
# This function checks if a manual label file already exists, then:
32+
# - If it does, copy it locally.
33+
# - If it doesn't, perform automatic labeling.
34+
# This allows you to add manual labels on a subject-by-subject basis without disrupting the pipeline.
35+
###
3736
local file="$1"
3837
local file_seg="$2"
3938
# Update global variable with segmentation file name
4039
FILELABEL="${file}_labels"
4140
if [ -e "${PATH_SEGMANUAL}/${file}_labels-manual.nii.gz" ]; then
4241
echo "Found manual label: ${PATH_SEGMANUAL}/${file}_labels-manual.nii.gz"
43-
rsync -avzh "${PATH_SEGMANUAL}/${file}_labels-manual.nii.gz" ${FILELABEL}.nii.gz
42+
rsync -avzh "${PATH_SEGMANUAL}/${file}_labels-manual.nii.gz" "${FILELABEL}.nii.gz"
4443
else
4544
# Generate labeled segmentation
46-
sct_label_vertebrae -i ${file}.nii.gz -s ${file_seg}.nii.gz -c t2 -qc ${PATH_QC} -qc-subject ${SUBJECT}
45+
sct_label_vertebrae -i "${file}.nii.gz" -s "${file_seg}.nii.gz" -c t2 -qc "${PATH_QC}" -qc-subject "${SUBJECT}"
4746
# Create labels in the cord at C3 and C5 mid-vertebral levels
48-
sct_label_utils -i ${file_seg}_labeled.nii.gz -vert-body 3,5 -o ${FILELABEL}.nii.gz
47+
sct_label_utils -i "${file_seg}_labeled.nii.gz" -vert-body 3,5 -o "${FILELABEL}.nii.gz"
4948
fi
5049
}
5150

52-
# Check if manual segmentation already exists. If it does, copy it locally. If
53-
# it does not, perform seg.
54-
segment_if_does_not_exist(){
51+
segment_if_does_not_exist() {
52+
###
53+
# This function checks if a manual spinal cord segmentation file already exists, then:
54+
# - If it does, copy it locally.
55+
# - If it doesn't, perform automatic spinal cord segmentation.
56+
# This allows you to add manual segmentations on a subject-by-subject basis without disrupting the pipeline.
57+
###
5558
local file="$1"
5659
local contrast="$2"
5760
# Update global variable with segmentation file name
5861
FILESEG="${file}_seg"
5962
if [ -e "${PATH_SEGMANUAL}/${FILESEG}-manual.nii.gz" ]; then
6063
echo "Found manual segmentation: ${PATH_SEGMANUAL}/${FILESEG}-manual.nii.gz"
61-
rsync -avzh "${PATH_SEGMANUAL}/${FILESEG}-manual.nii.gz" ${FILESEG}.nii.gz
62-
sct_qc -i ${file}.nii.gz -s ${FILESEG}.nii.gz -p sct_deepseg_sc -qc ${PATH_QC} -qc-subject ${SUBJECT}
64+
rsync -avzh "${PATH_SEGMANUAL}/${FILESEG}-manual.nii.gz" "${FILESEG}.nii.gz"
65+
sct_qc -i "${file}.nii.gz" -s "${FILESEG}.nii.gz" -p sct_deepseg_sc -qc "${PATH_QC}" -qc-subject "${SUBJECT}"
6366
else
6467
# Segment spinal cord
65-
sct_deepseg_sc -i ${file}.nii.gz -c $contrast -qc ${PATH_QC} -qc-subject ${SUBJECT}
68+
sct_deepseg_sc -i "${file}.nii.gz" -c "${contrast}" -qc "${PATH_QC}" -qc-subject "${SUBJECT}"
6669
fi
6770
}
6871

69-
7072
# SCRIPT STARTS HERE
71-
# ==============================================================================
73+
# ======================================================================================================================
74+
75+
# The following global variables are retrieved from config.yml but could be overwritten by uncommenting:
76+
# PATH_QC="~/qc"
7277

73-
source $FILEPARAM
78+
# Retrieve input params
79+
SUBJECT=$1
7480

7581
# Go to results folder, where most of the outputs will be located
76-
cd $PATH_RESULTS
82+
cd "${PATH_RESULTS}"
7783

7884
# Copy source images
7985
mkdir -p data
8086
cd data
81-
cp -r $PATH_DATA/$SUBJECT .
87+
cp -r "${PATH_DATA}/${SUBJECT}" .
8288

8389
# T2w
84-
# =============================================================================
85-
cd $SUBJECT/anat/
86-
file_t2=${SUBJECT}_T2w
90+
# ======================================================================================================================
91+
cd "${SUBJECT}/anat/"
92+
file_t2="${SUBJECT}_T2w"
8793
# Segment spinal cord (only if it does not exist)
88-
segment_if_does_not_exist $file_t2 "t2"
89-
file_t2_seg=$FILESEG
94+
segment_if_does_not_exist "${file_t2}" "t2"
95+
file_t2_seg="${FILESEG}"
9096
# Create labels in the cord at C2 and C5 mid-vertebral levels (only if it does not exist)
91-
label_if_does_not_exist $file_t2 $file_t2_seg
92-
file_label=$FILELABEL
97+
label_if_does_not_exist "${file_t2}" "${file_t2_seg}"
98+
file_label="${FILELABEL}"
9399
# Register to template
94-
sct_register_to_template -i $file_t2.nii.gz -s $file_t2_seg.nii.gz -l $file_label.nii.gz -c t2 -param step=1,type=seg,algo=centermassrot:step=2,type=im,algo=syn,iter=5,slicewise=1,metric=CC,smooth=0 -qc $PATH_QC
100+
sct_register_to_template -i "${file_t2}.nii.gz" -s "${file_t2_seg}.nii.gz" -l "${file_label}.nii.gz" -c t2 \
101+
-param step=1,type=seg,algo=centermassrot:step=2,type=im,algo=syn,iter=5,slicewise=1,metric=CC,smooth=0 \
102+
-qc "${PATH_QC}"
95103
# Warp template
96104
# Note: we don't need the white matter atlas at this point, therefore use flag "-a 0"
97-
sct_warp_template -d $file_t2.nii.gz -w warp_template2anat.nii.gz -a 0 -ofolder label_T2w -qc $PATH_QC
105+
sct_warp_template -d "${file_t2}.nii.gz" -w warp_template2anat.nii.gz -a 0 -ofolder label_T2w -qc "${PATH_QC}"
98106
# Compute average CSA between C2 and C3 levels (append across subjects)
99-
sct_process_segmentation -i $file_t2_seg.nii.gz -vert 2:3 -vertfile label_T2w/template/PAM50_levels.nii.gz -o $PATH_RESULTS/CSA.csv -append 1 -qc $PATH_QC
107+
sct_process_segmentation -i "${file_t2_seg}.nii.gz" -vert 2:3 -vertfile label_T2w/template/PAM50_levels.nii.gz \
108+
-o "${PATH_RESULTS}/CSA.csv" -append 1 -qc "${PATH_QC}"
100109

101110
# MT
102-
# =============================================================================
111+
# ======================================================================================================================
103112
file_mt1="${SUBJECT}_acq-MTon_MTS"
104113
file_mt0="${SUBJECT}_acq-MToff_MTS"
105114
# Segment spinal cord
106-
segment_if_does_not_exist $file_mt1 "t2s"
115+
segment_if_does_not_exist "${file_mt1}" "t2s"
107116
file_mt1_seg=$FILESEG
108117
# Create mask
109-
sct_create_mask -i $file_mt1.nii.gz -p centerline,$file_mt1_seg.nii.gz -size 45mm
118+
sct_create_mask -i "${file_mt1}.nii.gz" -p centerline,"${file_mt1_seg}.nii.gz" -size 45mm
110119
# Crop data for faster processing
111-
sct_crop_image -i $file_mt1.nii.gz -m mask_$file_mt1.nii.gz -o ${file_mt1}_crop.nii.gz
112-
sct_crop_image -i $file_mt1_seg.nii.gz -m mask_$file_mt1.nii.gz -o ${file_mt1}_crop_seg.nii.gz
113-
file_mt1=${file_mt1}_crop
120+
sct_crop_image -i "${file_mt1}.nii.gz" -m "mask_${file_mt1}.nii.gz" -o "${file_mt1}_crop.nii.gz"
121+
sct_crop_image -i "${file_mt1_seg}.nii.gz" -m "mask_${file_mt1}.nii.gz" -o "${file_mt1}_crop_seg.nii.gz"
122+
file_mt1="${file_mt1}_crop"
114123
# Register mt0->mt1
115124
# Tips: here we only use rigid transformation because both images have very
116125
# similar sequence parameters. We don't want to use SyN/BSplineSyN to avoid
117126
# introducing spurious deformations.
118-
sct_register_multimodal -i $file_mt0.nii.gz -d $file_mt1.nii.gz -param step=1,type=im,algo=rigid,slicewise=1,metric=CC -x spline -qc $PATH_QC
127+
sct_register_multimodal -i "${file_mt0}.nii.gz" -d "${file_mt1}.nii.gz" \
128+
-param step=1,type=im,algo=rigid,slicewise=1,metric=CC \
129+
-x spline -qc "${PATH_QC}"
119130
# Register template->mt1
120131
# Tips: here we only use the segmentations due to poor SC/CSF contrast at the bottom slice.
121132
# Tips: First step: slicereg based on images, with large smoothing to capture
122133
# potential motion between anat and mt, then at second step: bpslinesyn in order to
123134
# adapt the shape of the cord to the mt modality (in case there are distortions between anat and mt).
124-
sct_register_multimodal -i $SCT_DIR/data/PAM50/template/PAM50_t2.nii.gz -iseg $SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz -d ${file_mt1}.nii.gz -dseg ${file_mt1}_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -initwarp warp_template2anat.nii.gz -initwarpinv warp_anat2template.nii.gz -qc $PATH_QC
135+
sct_register_multimodal -i "${SCT_DIR}/data/PAM50/template/PAM50_t2.nii.gz" \
136+
-iseg "${SCT_DIR}/data/PAM50/template/PAM50_cord.nii.gz" \
137+
-d "${file_mt1}.nii.gz" \
138+
-dseg "${file_mt1}_seg.nii.gz" \
139+
-param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 \
140+
-initwarp warp_template2anat.nii.gz \
141+
-initwarpinv warp_anat2template.nii.gz \
142+
-qc "${PATH_QC}"
125143
# Rename warping fields for clarity
126-
mv warp_PAM50_t22$file_mt1.nii.gz warp_template2mt.nii.gz
127-
mv warp_${file_mt1}2PAM50_t2.nii.gz warp_mt2template.nii.gz
144+
mv "warp_PAM50_t22${file_mt1}.nii.gz" warp_template2mt.nii.gz
145+
mv "warp_${file_mt1}2PAM50_t2.nii.gz" warp_mt2template.nii.gz
128146
# Warp template
129-
sct_warp_template -d $file_mt1.nii.gz -w warp_template2mt.nii.gz -ofolder label_MT -qc $PATH_QC
147+
sct_warp_template -d "${file_mt1}.nii.gz" -w warp_template2mt.nii.gz -ofolder label_MT -qc "${PATH_QC}"
130148
# Compute mtr
131-
sct_compute_mtr -mt0 ${file_mt0}_reg.nii.gz -mt1 $file_mt1.nii.gz
149+
sct_compute_mtr -mt0 "${file_mt0}_reg.nii.gz" -mt1 "${file_mt1}.nii.gz"
132150
# compute MTR in dorsal columns between levels C2 and C5 (append across subjects)
133-
sct_extract_metric -i mtr.nii.gz -f label_MT/atlas -l 53 -vert 2:5 -vertfile label_MT/template/PAM50_levels.nii.gz -method map -o $PATH_RESULTS/MTR_in_DC.csv -append 1
151+
sct_extract_metric -i mtr.nii.gz -f label_MT/atlas -l 53 -vert 2:5 -vertfile label_MT/template/PAM50_levels.nii.gz \
152+
-method map -o "${PATH_RESULTS}/MTR_in_DC.csv" -append 1
134153

135154
# Verify presence of output files and write log file if error
136-
# =============================================================================
155+
# ======================================================================================================================
137156
FILES_TO_CHECK=(
138157
"$file_t2_seg.nii.gz"
139158
"mtr.nii.gz"
140159
)
141-
for file in ${FILES_TO_CHECK[@]}; do
142-
if [ ! -e $file ]; then
143-
echo "$SUBJECT/$file does not exist" >> $PATH_LOG/error.log
160+
for file in "${FILES_TO_CHECK[@]}"; do
161+
if [ ! -e "${file}" ]; then
162+
echo "${SUBJECT}/${file} does not exist" >> "${PATH_LOG}/error.log"
144163
fi
145164
done

0 commit comments

Comments
 (0)