From 4b4c88e708feed0233d20a8b80eace38301ae011 Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Fri, 6 Mar 2026 15:08:28 -0500 Subject: [PATCH 1/6] `batch_single_subject.sh`: Add legacy commands from `batch_processing` This commands should produce the necessary files to run `test_batch_processing.yml`, albeit likely with different results. --- single_subject/batch_single_subject.sh | 113 +++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/single_subject/batch_single_subject.sh b/single_subject/batch_single_subject.sh index f752a2f..fb162af 100755 --- a/single_subject/batch_single_subject.sh +++ b/single_subject/batch_single_subject.sh @@ -85,6 +85,21 @@ sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -discfile t2_totalspines # Compute cross-sectional area (CSA) of spinal cord and average it across levels C3 and C4 sct_process_segmentation -i t2_seg.nii.gz -vert 3:4 -discfile t2_totalspineseg_discs.nii.gz -o csa_c3c4.csv + +####################################################################################################################### +# FIXME: Metric 1/8: [t2/csa_c2c3.csv-0-MEAN(area)] +# Notes: +# - We have a corresponding command that computes `csa_c3c4.csv` (see above). +# - The above `batch_single_subject.sh` command uses the disc labels directly. +# - By comparison, the old `batch_processing.sh` commands rely on the the warped template (registered using the vert labels) +# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. +sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 +sct_label_utils -i t2_seg_labeled.nii.gz -vert-body 2,5 -o labels_vert.nii.gz +sct_register_to_template -i t2.nii.gz -s t2_seg.nii.gz -l labels_vert.nii.gz -c t2 +sct_warp_template -d t2.nii.gz -w warp_template2anat.nii.gz -a 0 +sct_process_segmentation -i t2_seg.nii.gz -vert 2:3 -o csa_c2c3.csv +####################################################################################################################### + # Aggregate CSA value per level (including new anat-based symmetry metrics) sct_process_segmentation -i t2_seg.nii.gz -anat t2.nii.gz -vert 3:4 -discfile t2_totalspineseg_discs.nii.gz -perlevel 1 -o csa_perlevel.csv # Aggregate CSA value per slices @@ -98,10 +113,28 @@ sct_detect_pmj -i t2.nii.gz -c t2 -qc ~/qc_singleSubj # Check the QC to make sure PMJ was properly detected, then compute CSA using the distance from the PMJ: sct_process_segmentation -i t2_seg.nii.gz -pmj t2_pmj.nii.gz -pmj-distance 64 -pmj-extent 30 -o csa_pmj.csv -qc ~/qc_singleSubj -qc-image t2.nii.gz +####################################################################################################################### +# FIXME: Metric 2/8: [t2/csa_pmj.csv-0-MEAN(area)] +# Notes: +# - We have a corresponding command that computes `csa_pmj.csv` (see above). +# - There is only a small discrepancy in `-pmj-distance`, and this is easily fixable. +sct_process_segmentation -i t2_seg.nii.gz -pmj t2_pmj.nii.gz -pmj-distance 60 -pmj-extent 30 -o csa_pmj.csv +####################################################################################################################### + # The above commands will output the metrics in the subject space (with the original image's slice numbers) # However, you can get the corresponding slice number in the PAM50 space by using the flag `-normalize-PAM50 1` sct_process_segmentation -i t2_seg.nii.gz -discfile t2_totalspineseg_discs.nii.gz -perslice 1 -normalize-PAM50 1 -o csa_PAM50.csv +####################################################################################################################### +# FIXME: Metric 3/8: [t2/csa_pam50.csv-38-MEAN(area)] +# Notes: +# - We have a corresponding command that computes `csa_pam50.csv` (see above). +# - The above `batch_single_subject.sh` command uses the disc labels directly. +# - By comparison, the old `batch_processing.sh` commands relies on the `sct_label_vertebrae` seg for `-vertfile`. +# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. +sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 +sct_process_segmentation -i t2_seg.nii.gz -vertfile t2_seg_labeled.nii.gz -perslice 1 -normalize-PAM50 1 -o csa_pam50.csv +####################################################################################################################### # Quantifying spinal cord compression using maximum spinal cord compression (MSCC) and normalizing with database of healthy controls @@ -124,6 +157,78 @@ sct_compute_compression -i t2_compressed_seg.nii.gz -vertfile t2_compressed_seg_ # Compute ratio of AP diameter, normalized with healthy controls using `-normalize-hc 1`. sct_compute_compression -i t2_compressed_seg.nii.gz -vertfile t2_compressed_seg_labeled.nii.gz -l t2_compressed_labels-compression.nii.gz -metric diameter_AP -normalize-hc 1 -o ap_ratio_norm_PAM50.csv +# NB: All 5 of the metrics below use the T2 registration to template as an `-initwarp` step. This means that to reproduce +# the old batch_processing.sh values, we need to use the old T2 warping fields, too. (See: "Metric 1/8" for the steps) +# It's totally possible to move these commands to _after_ the revamped T2 registration step, but the values will be off: +# - https://github.com/spinalcordtoolbox/sct_tutorial_data/commit/84139952c531ada90a901d9612ff7a8070bcc02f +# - https://github.com/spinalcordtoolbox/spinalcordtoolbox/actions/runs/22780277293/job/67453420618?pr=5185 + +####################################################################################################################### +# FIXME: Metric 4/8: [t2s/csa_gm.csv-3-MEAN(area)] +# FIXME: Metric 5/8: [t2s/csa_wm.csv-3-MEAN(area)] +# Notes: +# - We have corresponding commands that compute the GM/WM (see GM sections later on). +# - For `batch_single_subject.sh`, we use the new `graymatter` method, and register using the WM seg with updated params. +# - For `batch_processing.sh`, we use the old `sct_deepseg_gm` method, and register using the GM seg with outdated params. +cd ../t2s +sct_deepseg spinalcord -i t2s.nii.gz -qc ~/qc_singleSubj +sct_deepseg_gm -i t2s.nii.gz +sct_maths -i t2s_seg.nii.gz -sub t2s_gmseg.nii.gz -o t2s_wmseg.nii.gz +# - We also have corresponding commands that compute the `csa_wm.csv`/`csa_gm.csv` (see above). +# - For `batch_single_subject.sh`, we compute `-perslice 1` directly on the segmentation file. +# - For `batch_processing.sh`, we compute `-perlevel 1` using the warped template. +# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. +sct_register_multimodal -i "$SCT_DIR/data/PAM50/template/PAM50_t2s.nii.gz" -iseg "$SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz" -d t2s.nii.gz -dseg t2s_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,algo=syn,slicewise=1,iter=1,metric=CC -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2t2s.nii.gz -owarpinv warp_t2s2template.nii.gz +sct_warp_template -d t2s.nii.gz -w warp_template2t2s.nii.gz +sct_process_segmentation -i t2s_gmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_gm.csv -centerline t2s_seg.nii.gz +sct_process_segmentation -i t2s_wmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_wm.csv -centerline t2s_seg.nii.gz +####################################################################################################################### + +####################################################################################################################### +# FIXME: Metric 6/8: [mt/mtr_in_wm.csv-0-MAP()] +# Notes: +# - We have corresponding commands to compute the MTR (see MT sections later on). +# - For `batch_single_subject.sh`, the mask is used directly for the `-m` argument of `sct_register_multimodal`. +# - For `batch_processing.sh`, however, the mask is used to crop the image prior to registration. +cd ../mt +sct_get_centerline -i mt1.nii.gz -c t2 +sct_create_mask -i mt1.nii.gz -p centerline,mt1_centerline.nii.gz -size 45mm +sct_crop_image -i mt1.nii.gz -m mask_mt1.nii.gz -o mt1_crop.nii.gz +sct_deepseg spinalcord -i mt1_crop.nii.gz +sct_register_multimodal -i mt0.nii.gz -d mt1_crop.nii.gz -dseg mt1_crop_seg.nii.gz -param step=1,type=im,algo=slicereg,metric=CC -x spline +sct_compute_mtr -mt0 mt0_reg.nii.gz -mt1 mt1_crop.nii.gz +# - We also have corresponding commands that compute the `mtr_in_wm.csv` file +# - For `batch_single_subject.sh`, we A) use the mask during registration to template, B) we use the t2s for initwarp, and C) we don't aggregate the levels at all. +# - For `batch_processing.sh`, however, A) use the cropped image during template registration, B) we use the t2 for initwarp, and C) we aggregate the MTR values across levels 2 to 5. +# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. +sct_register_multimodal -i "$SCT_DIR/data/PAM50/template/PAM50_t2.nii.gz" -iseg "$SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz" -d mt1_crop.nii.gz -dseg mt1_crop_seg.nii.gz -param step=1,type=seg,algo=slicereg,smooth=3:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2mt.nii.gz -owarpinv warp_mt2template.nii.gz +sct_warp_template -d mt1_crop.nii.gz -w warp_template2mt.nii.gz +sct_extract_metric -i mtr.nii.gz -method map -o mtr_in_wm.csv -l 51 -vert 2:5 +####################################################################################################################### + +####################################################################################################################### +# FIXME: Metric 7/8: [dmri/fa_in_cst.csv-0-WA()] +# FIXME: Metric 8/8: [dmri/fa_in_cst.csv-1-WA()] +# - We have corresponding commands to compute the mask for motion correction (see dMRI sections later on). +# - For `batch_single_subject.sh`, we currently segment the dMRI cord directly, then dilate it. +# - For `batch_processing.sh`, we warp the T2 seg, then create the mask from that. +cd ../dmri +sct_dmri_separate_b0_and_dwi -i dmri.nii.gz -bvec bvecs.txt +sct_register_multimodal -i ../t2/t2_seg.nii.gz -d dmri_dwi_mean.nii.gz -identity 1 -x nn +sct_create_mask -i dmri_dwi_mean.nii.gz -p centerline,t2_seg_reg.nii.gz -size 35mm +# - We also have corresponding commands to warp the template to the motion-corrected sequence (see dMRI sections later on). +# - The process is basically the same (apart from the differing masks) +sct_dmri_moco -i dmri.nii.gz -bvec bvecs.txt -m mask_dmri_dwi_mean.nii.gz +sct_deepseg spinalcord -i dmri_moco_dwi_mean.nii.gz +sct_qc -i dmri.nii.gz -d dmri_moco.nii.gz -s dmri_moco_dwi_mean_seg.nii.gz -p sct_dmri_moco +sct_register_multimodal -i "$SCT_DIR/data/PAM50/template/PAM50_t1.nii.gz" -iseg "$SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz" -d dmri_moco_dwi_mean.nii.gz -dseg dmri_moco_dwi_mean_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,metric=MeanSquares,smooth=1,iter=3 -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2dmri.nii.gz -owarpinv warp_dmri2template.nii.gz +sct_warp_template -d dmri_moco_dwi_mean.nii.gz -w warp_template2dmri.nii.gz +# - Lastly, we also have a corresponding command to extract DTI metrics (see dMRI sections later on). +# - For `batch_single_subject.sh`, we compute FA in the WM aggregated across levels. +# - For `batch_processing.sh`, we compute FA in the CST, aggregated across slices. +sct_dmri_compute_dti -i dmri_moco.nii.gz -bval bvals.txt -bvec bvecs.txt +sct_extract_metric -i dti_FA.nii.gz -z 2:14 -method wa -l 4,5 -o fa_in_cst.csv +####################################################################################################################### # Registration to template @@ -261,8 +366,8 @@ sct_maths -i t2s_seg.nii.gz -sub t2s_gmseg.nii.gz -thr 0 -o t2s_wmseg.nii.gz # Compute cross-sectional area (CSA) of the gray and white matter for all slices in the volume. # Note: Here we use the flag -angle-corr 0, because we do not want to correct the computed CSA by the cosine of the # angle between the cord centerline and the S-I axis: we assume that slices were acquired orthogonally to the cord. -sct_process_segmentation -i t2s_wmseg.nii.gz -o csa_wm.csv -perslice 1 -angle-corr 0 -sct_process_segmentation -i t2s_gmseg.nii.gz -o csa_gm.csv -perslice 1 -angle-corr 0 +sct_process_segmentation -i t2s_wmseg.nii.gz -o csa_wm_perslice.csv -perslice 1 -angle-corr 0 +sct_process_segmentation -i t2s_gmseg.nii.gz -o csa_gm_perslice.csv -perslice 1 -angle-corr 0 # You can also use the binary masks to extract signal intensity from MRI data. # The example below will show how to use the GM and WM segmentations to quantify T2* signal intensity, as done in @@ -299,7 +404,7 @@ fsleyes mt1.nii.gz -cm greyscale -a 100.0 label/template/PAM50_t2.nii.gz -cm gre # Extract MTR for each slice within the white matter (combined label: #51) # Tips: To list all available labels, type: "sct_extract_metric" -sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 51 -o mtr_in_wm.csv +sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 51 -o mtr_in_wm_perslice.csv # Extract MTR within the right and left corticospinal tract and aggregate across specific slices sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 4,5 -z 5:15 -o mtr_in_cst.csv @@ -308,7 +413,6 @@ sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 4,5 -z 5:15 -o mt sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 53 -vert 2:4 -vertfile label/template/PAM50_levels.nii.gz -o mtr_in_dc.csv - # Diffusion-weighted MRI # ====================================================================================================================== @@ -347,7 +451,6 @@ sct_dmri_compute_dti -i dmri_moco.nii.gz -bval bvals.txt -bvec bvecs.txt sct_extract_metric -i dti_FA.nii.gz -f label/atlas -l 51 -method map -vert 2:5 -vertfile label/template/PAM50_levels.nii.gz -perlevel 1 -o fa_in_wm.csv - # Functional MRI # ====================================================================================================================== From 2ee29f01ed0e8ab3b002ebce27d4f892fce6c564 Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Mon, 30 Mar 2026 12:29:33 -0400 Subject: [PATCH 2/6] `batch_single_subject.sh`: Replace old commands with tweaked new ones The previous commit added commands to `batch_single_subject.sh` to reproduce the existing `batch_processing.sh` metrics EXACTLY. But, to do so, many outdated commands and methods were needed. This commit removes the outdated methods, and tries to reproduce the relevant metric files using only the commands that were already present within `batch_single_subject.sh`. Doing this will fundamentally change the values we produced (due to different underlying methods), but it allows us to keep the pipeline we've already developed for the SCT Course over the years. Only minimal changes to the existing commands were necessary, and the changes should be painless to port over to the SCT Course. This provides a smooth transition from having 2 scripts (batch_processing.sh and batch_single_subject.sh) to just 1 script (batch_single_subject.sh), allowing us to get rid of the old script entirely. For documentation on what exactly changed here from old -> new, refer to the extensive comments in the previous commit. --- single_subject/batch_single_subject.sh | 122 +++---------------------- 1 file changed, 12 insertions(+), 110 deletions(-) diff --git a/single_subject/batch_single_subject.sh b/single_subject/batch_single_subject.sh index fb162af..add627e 100755 --- a/single_subject/batch_single_subject.sh +++ b/single_subject/batch_single_subject.sh @@ -84,24 +84,9 @@ sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -discfile t2_totalspines # ====================================================================================================================== # Compute cross-sectional area (CSA) of spinal cord and average it across levels C3 and C4 -sct_process_segmentation -i t2_seg.nii.gz -vert 3:4 -discfile t2_totalspineseg_discs.nii.gz -o csa_c3c4.csv - -####################################################################################################################### -# FIXME: Metric 1/8: [t2/csa_c2c3.csv-0-MEAN(area)] -# Notes: -# - We have a corresponding command that computes `csa_c3c4.csv` (see above). -# - The above `batch_single_subject.sh` command uses the disc labels directly. -# - By comparison, the old `batch_processing.sh` commands rely on the the warped template (registered using the vert labels) -# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. -sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -sct_label_utils -i t2_seg_labeled.nii.gz -vert-body 2,5 -o labels_vert.nii.gz -sct_register_to_template -i t2.nii.gz -s t2_seg.nii.gz -l labels_vert.nii.gz -c t2 -sct_warp_template -d t2.nii.gz -w warp_template2anat.nii.gz -a 0 -sct_process_segmentation -i t2_seg.nii.gz -vert 2:3 -o csa_c2c3.csv -####################################################################################################################### - +sct_process_segmentation -i t2_seg.nii.gz -vert 2:3 -discfile t2_totalspineseg_discs.nii.gz -o csa_c2c3.csv # Aggregate CSA value per level (including new anat-based symmetry metrics) -sct_process_segmentation -i t2_seg.nii.gz -anat t2.nii.gz -vert 3:4 -discfile t2_totalspineseg_discs.nii.gz -perlevel 1 -o csa_perlevel.csv +sct_process_segmentation -i t2_seg.nii.gz -anat t2.nii.gz -vert 2:3 -discfile t2_totalspineseg_discs.nii.gz -perlevel 1 -o csa_perlevel.csv # Aggregate CSA value per slices sct_process_segmentation -i t2_seg.nii.gz -z 30:35 -discfile t2_totalspineseg_discs.nii.gz -perslice 1 -o csa_perslice.csv @@ -111,30 +96,12 @@ sct_process_segmentation -i t2_seg.nii.gz -z 30:35 -discfile t2_totalspineseg_di # of the spinal cord will vary depending on the position of the neck. sct_detect_pmj -i t2.nii.gz -c t2 -qc ~/qc_singleSubj # Check the QC to make sure PMJ was properly detected, then compute CSA using the distance from the PMJ: -sct_process_segmentation -i t2_seg.nii.gz -pmj t2_pmj.nii.gz -pmj-distance 64 -pmj-extent 30 -o csa_pmj.csv -qc ~/qc_singleSubj -qc-image t2.nii.gz - -####################################################################################################################### -# FIXME: Metric 2/8: [t2/csa_pmj.csv-0-MEAN(area)] -# Notes: -# - We have a corresponding command that computes `csa_pmj.csv` (see above). -# - There is only a small discrepancy in `-pmj-distance`, and this is easily fixable. -sct_process_segmentation -i t2_seg.nii.gz -pmj t2_pmj.nii.gz -pmj-distance 60 -pmj-extent 30 -o csa_pmj.csv -####################################################################################################################### +sct_process_segmentation -i t2_seg.nii.gz -pmj t2_pmj.nii.gz -pmj-distance 60 -pmj-extent 30 -o csa_pmj.csv -qc ~/qc_singleSubj -qc-image t2.nii.gz # The above commands will output the metrics in the subject space (with the original image's slice numbers) # However, you can get the corresponding slice number in the PAM50 space by using the flag `-normalize-PAM50 1` -sct_process_segmentation -i t2_seg.nii.gz -discfile t2_totalspineseg_discs.nii.gz -perslice 1 -normalize-PAM50 1 -o csa_PAM50.csv +sct_process_segmentation -i t2_seg.nii.gz -discfile t2_totalspineseg_discs.nii.gz -perslice 1 -normalize-PAM50 1 -o csa_pam50.csv -####################################################################################################################### -# FIXME: Metric 3/8: [t2/csa_pam50.csv-38-MEAN(area)] -# Notes: -# - We have a corresponding command that computes `csa_pam50.csv` (see above). -# - The above `batch_single_subject.sh` command uses the disc labels directly. -# - By comparison, the old `batch_processing.sh` commands relies on the `sct_label_vertebrae` seg for `-vertfile`. -# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. -sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -sct_process_segmentation -i t2_seg.nii.gz -vertfile t2_seg_labeled.nii.gz -perslice 1 -normalize-PAM50 1 -o csa_pam50.csv -####################################################################################################################### # Quantifying spinal cord compression using maximum spinal cord compression (MSCC) and normalizing with database of healthy controls @@ -157,78 +124,6 @@ sct_compute_compression -i t2_compressed_seg.nii.gz -vertfile t2_compressed_seg_ # Compute ratio of AP diameter, normalized with healthy controls using `-normalize-hc 1`. sct_compute_compression -i t2_compressed_seg.nii.gz -vertfile t2_compressed_seg_labeled.nii.gz -l t2_compressed_labels-compression.nii.gz -metric diameter_AP -normalize-hc 1 -o ap_ratio_norm_PAM50.csv -# NB: All 5 of the metrics below use the T2 registration to template as an `-initwarp` step. This means that to reproduce -# the old batch_processing.sh values, we need to use the old T2 warping fields, too. (See: "Metric 1/8" for the steps) -# It's totally possible to move these commands to _after_ the revamped T2 registration step, but the values will be off: -# - https://github.com/spinalcordtoolbox/sct_tutorial_data/commit/84139952c531ada90a901d9612ff7a8070bcc02f -# - https://github.com/spinalcordtoolbox/spinalcordtoolbox/actions/runs/22780277293/job/67453420618?pr=5185 - -####################################################################################################################### -# FIXME: Metric 4/8: [t2s/csa_gm.csv-3-MEAN(area)] -# FIXME: Metric 5/8: [t2s/csa_wm.csv-3-MEAN(area)] -# Notes: -# - We have corresponding commands that compute the GM/WM (see GM sections later on). -# - For `batch_single_subject.sh`, we use the new `graymatter` method, and register using the WM seg with updated params. -# - For `batch_processing.sh`, we use the old `sct_deepseg_gm` method, and register using the GM seg with outdated params. -cd ../t2s -sct_deepseg spinalcord -i t2s.nii.gz -qc ~/qc_singleSubj -sct_deepseg_gm -i t2s.nii.gz -sct_maths -i t2s_seg.nii.gz -sub t2s_gmseg.nii.gz -o t2s_wmseg.nii.gz -# - We also have corresponding commands that compute the `csa_wm.csv`/`csa_gm.csv` (see above). -# - For `batch_single_subject.sh`, we compute `-perslice 1` directly on the segmentation file. -# - For `batch_processing.sh`, we compute `-perlevel 1` using the warped template. -# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. -sct_register_multimodal -i "$SCT_DIR/data/PAM50/template/PAM50_t2s.nii.gz" -iseg "$SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz" -d t2s.nii.gz -dseg t2s_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,algo=syn,slicewise=1,iter=1,metric=CC -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2t2s.nii.gz -owarpinv warp_t2s2template.nii.gz -sct_warp_template -d t2s.nii.gz -w warp_template2t2s.nii.gz -sct_process_segmentation -i t2s_gmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_gm.csv -centerline t2s_seg.nii.gz -sct_process_segmentation -i t2s_wmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_wm.csv -centerline t2s_seg.nii.gz -####################################################################################################################### - -####################################################################################################################### -# FIXME: Metric 6/8: [mt/mtr_in_wm.csv-0-MAP()] -# Notes: -# - We have corresponding commands to compute the MTR (see MT sections later on). -# - For `batch_single_subject.sh`, the mask is used directly for the `-m` argument of `sct_register_multimodal`. -# - For `batch_processing.sh`, however, the mask is used to crop the image prior to registration. -cd ../mt -sct_get_centerline -i mt1.nii.gz -c t2 -sct_create_mask -i mt1.nii.gz -p centerline,mt1_centerline.nii.gz -size 45mm -sct_crop_image -i mt1.nii.gz -m mask_mt1.nii.gz -o mt1_crop.nii.gz -sct_deepseg spinalcord -i mt1_crop.nii.gz -sct_register_multimodal -i mt0.nii.gz -d mt1_crop.nii.gz -dseg mt1_crop_seg.nii.gz -param step=1,type=im,algo=slicereg,metric=CC -x spline -sct_compute_mtr -mt0 mt0_reg.nii.gz -mt1 mt1_crop.nii.gz -# - We also have corresponding commands that compute the `mtr_in_wm.csv` file -# - For `batch_single_subject.sh`, we A) use the mask during registration to template, B) we use the t2s for initwarp, and C) we don't aggregate the levels at all. -# - For `batch_processing.sh`, however, A) use the cropped image during template registration, B) we use the t2 for initwarp, and C) we aggregate the MTR values across levels 2 to 5. -# - In summary, the old metric values we are testing are wholly incompatible with our current up-to-date pipelines. -sct_register_multimodal -i "$SCT_DIR/data/PAM50/template/PAM50_t2.nii.gz" -iseg "$SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz" -d mt1_crop.nii.gz -dseg mt1_crop_seg.nii.gz -param step=1,type=seg,algo=slicereg,smooth=3:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2mt.nii.gz -owarpinv warp_mt2template.nii.gz -sct_warp_template -d mt1_crop.nii.gz -w warp_template2mt.nii.gz -sct_extract_metric -i mtr.nii.gz -method map -o mtr_in_wm.csv -l 51 -vert 2:5 -####################################################################################################################### - -####################################################################################################################### -# FIXME: Metric 7/8: [dmri/fa_in_cst.csv-0-WA()] -# FIXME: Metric 8/8: [dmri/fa_in_cst.csv-1-WA()] -# - We have corresponding commands to compute the mask for motion correction (see dMRI sections later on). -# - For `batch_single_subject.sh`, we currently segment the dMRI cord directly, then dilate it. -# - For `batch_processing.sh`, we warp the T2 seg, then create the mask from that. -cd ../dmri -sct_dmri_separate_b0_and_dwi -i dmri.nii.gz -bvec bvecs.txt -sct_register_multimodal -i ../t2/t2_seg.nii.gz -d dmri_dwi_mean.nii.gz -identity 1 -x nn -sct_create_mask -i dmri_dwi_mean.nii.gz -p centerline,t2_seg_reg.nii.gz -size 35mm -# - We also have corresponding commands to warp the template to the motion-corrected sequence (see dMRI sections later on). -# - The process is basically the same (apart from the differing masks) -sct_dmri_moco -i dmri.nii.gz -bvec bvecs.txt -m mask_dmri_dwi_mean.nii.gz -sct_deepseg spinalcord -i dmri_moco_dwi_mean.nii.gz -sct_qc -i dmri.nii.gz -d dmri_moco.nii.gz -s dmri_moco_dwi_mean_seg.nii.gz -p sct_dmri_moco -sct_register_multimodal -i "$SCT_DIR/data/PAM50/template/PAM50_t1.nii.gz" -iseg "$SCT_DIR/data/PAM50/template/PAM50_cord.nii.gz" -d dmri_moco_dwi_mean.nii.gz -dseg dmri_moco_dwi_mean_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,metric=MeanSquares,smooth=1,iter=3 -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2dmri.nii.gz -owarpinv warp_dmri2template.nii.gz -sct_warp_template -d dmri_moco_dwi_mean.nii.gz -w warp_template2dmri.nii.gz -# - Lastly, we also have a corresponding command to extract DTI metrics (see dMRI sections later on). -# - For `batch_single_subject.sh`, we compute FA in the WM aggregated across levels. -# - For `batch_processing.sh`, we compute FA in the CST, aggregated across slices. -sct_dmri_compute_dti -i dmri_moco.nii.gz -bval bvals.txt -bvec bvecs.txt -sct_extract_metric -i dti_FA.nii.gz -z 2:14 -method wa -l 4,5 -o fa_in_cst.csv -####################################################################################################################### # Registration to template @@ -387,6 +282,9 @@ sct_extract_metric -i t2s.nii.gz -f t2s_gmseg.nii.gz -method bin -z 2:12 -o t2s_ sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t2s.nii.gz -iseg "${SCT_DIR}"/data/PAM50/template/PAM50_wm.nii.gz -d t2s.nii.gz -dseg t2s_wmseg.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2t2s.nii.gz -owarpinv warp_t2s2template.nii.gz -param step=1,type=seg,algo=rigid:step=2,type=seg,metric=CC,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,metric=CC,algo=syn,slicewise=1,iter=2 -qc ~/qc_singleSubj # Warp template sct_warp_template -d t2s.nii.gz -w warp_template2t2s.nii.gz -qc ~/qc_singleSubj +# Compute vertebral level-based metrics using warped template (needed for the template's vertlevel file) +sct_process_segmentation -i t2s_gmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_gm.csv -centerline t2s_seg.nii.gz -centerline-exclude-missing 1 +sct_process_segmentation -i t2s_wmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_wm.csv -centerline t2s_seg.nii.gz -centerline-exclude-missing 1 # Register another metric while reusing newly-created GM-informed warping fields cd ../mt @@ -404,7 +302,7 @@ fsleyes mt1.nii.gz -cm greyscale -a 100.0 label/template/PAM50_t2.nii.gz -cm gre # Extract MTR for each slice within the white matter (combined label: #51) # Tips: To list all available labels, type: "sct_extract_metric" -sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 51 -o mtr_in_wm_perslice.csv +sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 51 -vert 2:5 -o mtr_in_wm.csv # Extract MTR within the right and left corticospinal tract and aggregate across specific slices sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 4,5 -z 5:15 -o mtr_in_cst.csv @@ -413,6 +311,7 @@ sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 4,5 -z 5:15 -o mt sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 53 -vert 2:4 -vertfile label/template/PAM50_levels.nii.gz -o mtr_in_dc.csv + # Diffusion-weighted MRI # ====================================================================================================================== @@ -449,6 +348,9 @@ sct_dmri_compute_dti -i dmri_moco.nii.gz -bval bvals.txt -bvec bvecs.txt # Compute FA within the white matter from individual level 2 to 5 sct_extract_metric -i dti_FA.nii.gz -f label/atlas -l 51 -method map -vert 2:5 -vertfile label/template/PAM50_levels.nii.gz -perlevel 1 -o fa_in_wm.csv +# Compute FA within the CST, aggregated across z slices, using the weighted average method +sct_extract_metric -i dti_FA.nii.gz -f label/atlas -l 4,5 -method wa -z 2:14 -o fa_in_cst.csv + # Functional MRI From 23d4fce3af42766d4e9c8925fa7e1f84adff5f85 Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Wed, 24 Jun 2026 11:00:41 -0400 Subject: [PATCH 3/6] `batch_single_subject.sh`: Copy over changes from PR #5185 This is needed to be able to regenerate the .csv files that we display in the tutorials (done automatically in CI) --- single_subject/batch_single_subject.sh | 150 ++++++++++++++++++++----- 1 file changed, 121 insertions(+), 29 deletions(-) diff --git a/single_subject/batch_single_subject.sh b/single_subject/batch_single_subject.sh index add627e..dab946e 100755 --- a/single_subject/batch_single_subject.sh +++ b/single_subject/batch_single_subject.sh @@ -18,7 +18,26 @@ # ====================================================================================================================== # If a command fails, set -e will make the whole script exit, instead of just resuming on the next line -set -e +set -ve + +# For full verbose, uncomment the next line +# set -x + +# get starting time: +start=$(date +%s) + +# Fetch OS type +if uname -a | grep -i darwin > /dev/null 2>&1; then + # OSX + open_command="open" +elif uname -a | grep -i linux > /dev/null 2>&1; then + # Linux + open_command="xdg-open" +fi + +# download example data and enter our data directory +sct_download_data -d sct_course_data +cd "$SCT_DIR/data/sct_course_data/single_subject/data/" # Exit if user presses CTRL+C (Linux) or CMD+C (OSX) trap "echo Caught Keyboard Interrupt within script. Exiting now.; exit" INT @@ -44,7 +63,7 @@ fi # ====================================================================================================================== # Go to T2 folder -cd data/t2 +cd t2 # Spinal cord segmentation (using the new 2024 contrast-agnostic method) sct_deepseg spinalcord -i t2.nii.gz -qc ~/qc_singleSubj # The default output is t2_seg.nii.gz @@ -74,9 +93,21 @@ fsleyes t2.nii.gz -cm greyscale t2_step1_canal.nii.gz -cm YlOrRd -a 70.0 t2_step # Check QC report: Go to your browser and do "refresh". # Optionally, you can use the generated disc labels to create a labeled segmentation +# sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -discfile t2_totalspineseg_discs.nii.gz + +# If you wish to bypass the disc labeling model entirely and instead wish to use the legacy +# vertebral labeling method (`sct_label_vertebrae`), you can do so. # Note: This approach is no longer recommended. Instead, use the disc labels directly in subsequent commands (e.g. `sct_process_segmentation`). -sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -discfile t2_totalspineseg_discs.nii.gz -# FIXME: Remove this command once the web tutorials are updated to no longer use labeled segmentations +# sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -qc ~/qc_singleSubj + +# Optionally, if the legacy `sct_label_vertebrae` fails as well, you can initialize the command +# by manually labeling the c2-c3 disc. +# sct_label_utils -i t2.nii.gz -create-viewer 3 -o label_c2c3.nii.gz -msg "Click at the posterior tip of C2/C3 inter-vertebral disc" +# sct_label_vertebrae -i t2.nii.gz -s t2_seg.nii.gz -c t2 -initlabel label_c2c3.nii.gz -qc ~/qc_singleSubj + +# If generating a labeled segmentation using `sct_label_vertebrae`, you can then extract vertebral +# body labels from the segmentation using `-vert-body`. +# sct_label_utils -i t2_seg_labeled.nii.gz -vert-body 3,9 -o t2_labels_vert.nii.gz @@ -124,6 +155,14 @@ sct_compute_compression -i t2_compressed_seg.nii.gz -vertfile t2_compressed_seg_ # Compute ratio of AP diameter, normalized with healthy controls using `-normalize-hc 1`. sct_compute_compression -i t2_compressed_seg.nii.gz -vertfile t2_compressed_seg_labeled.nii.gz -l t2_compressed_labels-compression.nii.gz -metric diameter_AP -normalize-hc 1 -o ap_ratio_norm_PAM50.csv +# Canal segmentation +sct_deepseg canal -i t2_compressed.nii.gz -o t2_compressed_canal_seg.nii.gz -qc ~/qc_singleSubj +# Check results using FSLeyes +fsleyes t2.nii.gz -cm greyscale t2_canal_seg_seg.nii.gz -cm red -a 70.0 & +# Compute aSCOR (Adapted Spinal Cord Occupation Ratio) +# i.e. Spinal cord to canal ratio using the canal seg +sct_compute_ascor -i-SC t2_compressed_seg.nii.gz -i-canal t2_compressed_canal_seg.nii.gz -perlevel 1 -o ascor.csv + # Registration to template @@ -181,7 +220,7 @@ sct_create_mask -i mt1.nii.gz -p centerline,mt1_seg.nii.gz -size 35mm -f cylinde # Register template->mt1. The flag -initwarp ../t2/warp_template2anat.nii.gz initializes the registration using the # template->t2 transformation which was previously estimated -sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t2.nii.gz -iseg "${SCT_DIR}"/data/PAM50/template/PAM50_cord.nii.gz -d mt1.nii.gz -dseg mt1_seg.nii.gz -m mask_mt1.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -owarp warp_template2mt.nii.gz -qc ~/qc_singleSubj +sct_register_multimodal -i "${SCT_DIR}/data/PAM50/template/PAM50_t2.nii.gz" -iseg "${SCT_DIR}/data/PAM50/template/PAM50_cord.nii.gz" -d mt1.nii.gz -dseg mt1_seg.nii.gz -m mask_mt1.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -owarp warp_template2mt.nii.gz -qc ~/qc_singleSubj # Tips: Here we only use the segmentations (type=seg) to minimize the sensitivity of the registration procedure to # image artifacts. # Tips: Step 1: algo=centermass to align source and destination segmentations, then Step 2: algo=bpslinesyn to adapt the @@ -194,7 +233,7 @@ sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t2.nii.gz -ise # sct_register_to_template -i mt1.nii.gz -s mt1_seg.nii.gz -ldisc label_c3c4.nii.gz -ref subject -param step=1,type=seg,algo=centermassrot:step=2,type=seg,algo=bsplinesyn,slicewise=1 # Warp template -sct_warp_template -d mt1.nii.gz -w warp_template2mt.nii.gz -a 1 -qc ~/qc_singleSubj +sct_warp_template -d mt1.nii.gz -w warp_template2mt.nii.gz -a 0 -qc ~/qc_singleSubj # Check results using FSLeyes fsleyes mt1.nii.gz -cm greyscale -a 100.0 label/template/PAM50_t2.nii.gz -cm greyscale -dr 0 4000 -a 100.0 label/template/PAM50_gm.nii.gz -cm red-yellow -dr 0.4 1 -a 50.0 label/template/PAM50_wm.nii.gz -cm blue-lightblue -dr 0.4 1 -a 50.0 & @@ -204,7 +243,9 @@ fsleyes mt1.nii.gz -cm greyscale -a 100.0 label/template/PAM50_t2.nii.gz -cm gre # ====================================================================================================================== # Register mt0->mt1 using z-regularized slicewise translations (algo=slicereg) -# Note: Segmentation and mask can be re-used from "MT registration" section +# Note: Segmentation and mask can be re-used from "Register MT to PAM50" section (or reproduced using commands below) +# sct_deepseg spinalcord -i mt1.nii.gz -qc ~/qc_singleSubj +# sct_create_mask -i mt1.nii.gz -p centerline,mt1_seg.nii.gz -size 35mm -f cylinder -o mask_mt1.nii.gz sct_register_multimodal -i mt0.nii.gz -d mt1.nii.gz -dseg mt1_seg.nii.gz -m mask_mt1.nii.gz -param step=1,type=im,algo=slicereg,metric=CC -x spline -qc ~/qc_singleSubj # Check results using FSLeyes fsleyes mt1.nii.gz mt0_reg.nii.gz & @@ -214,11 +255,42 @@ sct_compute_mtr -mt0 mt0_reg.nii.gz -mt1 mt1.nii.gz +# Registering additional contrasts (contrast-agnostic registration with deep learning between T2 and T1) +# ====================================================================================================================== + +# Go to T2 folder +cd ../t2 + +# Segment the spinal cord on T2-weighted data +sct_deepseg spinalcord -i t2.nii.gz -qc ~/qc_singleSubj +# Create a mask around the spinal cord +sct_create_mask -i t2.nii.gz -p centerline,t2_seg.nii.gz -size 35mm -o mask_t2.nii.gz +# Crop around the spinal cord to speed up and improve the subsequent registration +sct_crop_image -i t2.nii.gz -m mask_t2.nii.gz + +# Go to T1 folder +cd ../t1 + +# Segment the spinal cord on T1-weighted data +sct_deepseg spinalcord -i t1.nii.gz -qc ~/qc_singleSubj +# Create a mask around the spinal cord +sct_create_mask -i t1.nii.gz -p centerline,t1_seg.nii.gz -size 35mm -o mask_t1.nii.gz +# Crop around the spinal cord to speed up and improve the subsequent registration +sct_crop_image -i t1.nii.gz -m mask_t1.nii.gz + +# Register T1->T2 using the contrast-agnostic deep learning method +# Note: We use the cropped images to focus the registration on the spinal cord region +# Note: The destination segmentation is provided for QC reporting only +sct_register_multimodal -i t1_crop.nii.gz -d ../t2/t2_crop.nii.gz -param step=1,type=im,algo=dl -qc ~/qc_singleSubj -dseg ../t2/t2_seg.nii.gz + + + # Registering additional contrasts (T2 lumbar data) # ====================================================================================================================== cd ../t2_lumbar # Use lumbar-specific `sct_deepseg` model to segment the spinal cord +sct_deepseg sc_lumbar_t2 -install sct_deepseg sc_lumbar_t2 -i t2_lumbar.nii.gz -qc ~/qc_singleSubj # Generate labels for the 2 spinal cord landmarks: cauda equinea ('99') and T9-T10 disc ('17') @@ -250,7 +322,7 @@ sct_deepseg spinalcord -i t2s.nii.gz -qc ~/qc_singleSubj # Subtract GM segmentation from cord segmentation to obtain WM segmentation # Note that we use the flag -thr 0 in case some voxels in the GM segmentation are *not* included in the cord # segmentation. That would results in voxels in the WM segmentation having the value “-1”, which would cause issues -# with the registration. +# with the registration. sct_maths -i t2s_seg.nii.gz -sub t2s_gmseg.nii.gz -thr 0 -o t2s_wmseg.nii.gz @@ -279,7 +351,7 @@ sct_extract_metric -i t2s.nii.gz -f t2s_gmseg.nii.gz -method bin -z 2:12 -o t2s_ # Register template->t2s (using warping field generated from template<->t2 registration) # Tips: Here we use the WM seg for the iseg/dseg fields in order to account for both the cord and the GM shape. -sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t2s.nii.gz -iseg "${SCT_DIR}"/data/PAM50/template/PAM50_wm.nii.gz -d t2s.nii.gz -dseg t2s_wmseg.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2t2s.nii.gz -owarpinv warp_t2s2template.nii.gz -param step=1,type=seg,algo=rigid:step=2,type=seg,metric=CC,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,metric=CC,algo=syn,slicewise=1,iter=2 -qc ~/qc_singleSubj +sct_register_multimodal -i "${SCT_DIR}/data/PAM50/template/PAM50_t2s.nii.gz" -iseg "${SCT_DIR}/data/PAM50/template/PAM50_wm.nii.gz" -d t2s.nii.gz -dseg t2s_wmseg.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2t2s.nii.gz -owarpinv warp_t2s2template.nii.gz -param step=1,type=seg,algo=rigid:step=2,type=seg,metric=CC,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,metric=CC,algo=syn,slicewise=1,iter=2 -qc ~/qc_singleSubj # Warp template sct_warp_template -d t2s.nii.gz -w warp_template2t2s.nii.gz -qc ~/qc_singleSubj # Compute vertebral level-based metrics using warped template (needed for the template's vertlevel file) @@ -289,9 +361,9 @@ sct_process_segmentation -i t2s_wmseg.nii.gz -vert 2:5 -perlevel 1 -o csa_wm.csv # Register another metric while reusing newly-created GM-informed warping fields cd ../mt # Register template->mt using `-initwarp` with t2s to account for GM segmentation -sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t2.nii.gz -iseg "${SCT_DIR}"/data/PAM50/template/PAM50_cord.nii.gz -d mt1.nii.gz -dseg mt1_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -m mask_mt1.nii.gz -initwarp ../t2s/warp_template2t2s.nii.gz -owarp warp_template2mt.nii.gz -qc ~/qc_singleSubj +sct_register_multimodal -i "${SCT_DIR}/data/PAM50/template/PAM50_t2.nii.gz" -iseg "${SCT_DIR}/data/PAM50/template/PAM50_cord.nii.gz" -d mt1.nii.gz -dseg mt1_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -m mask_mt1.nii.gz -initwarp ../t2s/warp_template2t2s.nii.gz -owarp warp_template2mt.nii.gz -qc ~/qc_singleSubj # Warp template -sct_warp_template -d mt1.nii.gz -w warp_template2mt.nii.gz -qc ~/qc_singleSubj +sct_warp_template -d mt1.nii.gz -w warp_template2mt.nii.gz -a 1 -qc ~/qc_singleSubj # Check results fsleyes mt1.nii.gz -cm greyscale -a 100.0 label/template/PAM50_t2.nii.gz -cm greyscale -dr 0 4000 -a 100.0 label/template/PAM50_gm.nii.gz -cm red-yellow -dr 0.4 1 -a 100.0 label/template/PAM50_wm.nii.gz -cm blue-lightblue -dr 0.4 1 -a 100.0 & @@ -318,7 +390,7 @@ sct_extract_metric -i mtr.nii.gz -f label/atlas -method map -l 53 -vert 2:4 -ver cd ../dmri # Preprocessing steps # Compute mean dMRI from dMRI data -sct_dmri_separate_b0_and_dwi -i dmri.nii.gz -bvec bvecs.txt +sct_dmri_separate_b0_and_dwi -i dmri.nii.gz -bvec bvecs.txt # Segment SC on mean dMRI data # Note: This segmentation does not need to be accurate-- it is only used to create a mask around the cord sct_deepseg spinalcord -i dmri_dwi_mean.nii.gz -qc ~/qc_singleSubj @@ -337,7 +409,7 @@ sct_deepseg spinalcord -i dmri_moco_dwi_mean.nii.gz -qc ~/qc_singleSubj # -param, so it will not make a difference here) # Note: the flag “-initwarpinv" provides a transformation dmri->template, in case you would like to bring all your DTI # metrics in the PAM50 space (e.g. group averaging of FA maps) -sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t1.nii.gz -iseg "${SCT_DIR}"/data/PAM50/template/PAM50_cord.nii.gz -d dmri_moco_dwi_mean.nii.gz -dseg dmri_moco_dwi_mean_seg.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2dmri.nii.gz -owarpinv warp_dmri2template.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -qc ~/qc_singleSubj +sct_register_multimodal -i "${SCT_DIR}/data/PAM50/template/PAM50_t1.nii.gz" -iseg "${SCT_DIR}/data/PAM50/template/PAM50_cord.nii.gz" -d dmri_moco_dwi_mean.nii.gz -dseg dmri_moco_dwi_mean_seg.nii.gz -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2dmri.nii.gz -owarpinv warp_dmri2template.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3 -qc ~/qc_singleSubj # Warp template (so 'label/atlas' can be used to extract metrics) sct_warp_template -d dmri_moco_dwi_mean.nii.gz -w warp_template2dmri.nii.gz -qc ~/qc_singleSubj # Check results in the QC report @@ -356,6 +428,10 @@ sct_extract_metric -i dti_FA.nii.gz -f label/atlas -l 4,5 -method wa -z 2:14 -o # Functional MRI # ====================================================================================================================== +# the T2 segmentation will be reused, but it can also be generated using the commands below: +# cd ../t2 +# sct_deepseg spinalcord -i t2.nii.gz + cd ../fmri # Preprocessing steps # Average all fMRI time series to make it a 3D volume (needed by the next command) @@ -369,18 +445,18 @@ sct_create_mask -i fmri.nii.gz -p centerline,t2_seg_reg.nii.gz -size 35mm -f cyl sct_fmri_moco -i fmri.nii.gz -m mask_fmri.nii.gz -qc ~/qc_singleSubj -qc-seg t2_seg_reg.nii.gz # Cord segmentation on motion-corrected averaged time series -sct_deepseg spinalcord -i fmri_moco_mean.nii.gz -qc ~/qc_singleSubj/ +sct_deepseg spinalcord -i fmri_moco_mean.nii.gz -qc ~/qc_singleSubj # TSNR before/after motion correction with QC report sct_fmri_compute_tsnr -i fmri.nii.gz sct_fmri_compute_tsnr -i fmri_moco.nii.gz -sct_qc -i fmri_tsnr.nii.gz -d fmri_moco_tsnr.nii.gz -s fmri_moco_mean_seg.nii.gz -p sct_fmri_compute_tsnr -qc ~/qc_singleSubj/ +sct_qc -i fmri_tsnr.nii.gz -d fmri_moco_tsnr.nii.gz -s fmri_moco_mean_seg.nii.gz -p sct_fmri_compute_tsnr -qc ~/qc_singleSubj # Register the template to the fMRI scan. # Note: here we don't rely on the segmentation because it is difficult to obtain one automatically. Instead, we rely on # ANTs_SyN superpower to find a suitable transformation between the PAM50_t2s and the fMRI scan. We don't want to # put too many iterations because this registration is very sensitive to the artifacts (drop out) in the image. # Also, we want a 3D transformation (not 2D) because we need the through-z regularization. -sct_register_multimodal -i "${SCT_DIR}"/data/PAM50/template/PAM50_t2s.nii.gz -iseg "${SCT_DIR}"/data/PAM50/template/PAM50_cord.nii.gz -d fmri_moco_mean.nii.gz -dseg fmri_moco_mean_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,algo=syn,metric=CC,iter=3,slicewise=1 -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2fmri.nii.gz -owarpinv warp_fmri2template.nii.gz -qc ~/qc_singleSubj +sct_register_multimodal -i "${SCT_DIR}/data/PAM50/template/PAM50_t2s.nii.gz" -iseg "${SCT_DIR}/data/PAM50/template/PAM50_cord.nii.gz" -d fmri_moco_mean.nii.gz -dseg fmri_moco_mean_seg.nii.gz -param step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,algo=syn,metric=CC,iter=3,slicewise=1 -initwarp ../t2/warp_template2anat.nii.gz -initwarpinv ../t2/warp_anat2template.nii.gz -owarp warp_template2fmri.nii.gz -owarpinv warp_fmri2template.nii.gz -qc ~/qc_singleSubj # Check results in the QC report # Warp template with the spinal levels (can be found at $SCT_DIR/data/PAM50/template/) @@ -391,8 +467,8 @@ sct_warp_template -d fmri_moco_mean.nii.gz -w warp_template2fmri.nii.gz -a 0 -qc # ====================================================================================================================== cd ../t1 -# Segment T1-weighted image (to be used in later steps) -sct_deepseg spinalcord -i t1.nii.gz -qc ~/qc_singleSubj/ +# Segment T1-weighted image (but, reuse the previous segmentation to save processing time) +## sct_deepseg spinalcord -i t1.nii.gz -qc ~/qc_singleSubj # Smooth spinal cord along centerline (extracted from the segmentation) sct_smooth_spinalcord -i t1.nii.gz -s t1_seg.nii.gz @@ -429,6 +505,9 @@ sct_analyze_lesion -m t2_lesion_seg.nii.gz -s t2_sc_seg.nii.gz -qc ~/qc_singleSu sct_warp_template -d t2.nii.gz -w ../t2/warp_template2anat.nii.gz sct_analyze_lesion -m t2_lesion_seg.nii.gz -s t2_sc_seg.nii.gz -f label -qc ~/qc_singleSubj +# You can also use the legacy method if the new methods fail for your data (`-c t2s` is also supported) +sct_deepseg_lesion -i t2.nii.gz -c t2 + # Segment the spinal cord on gradient echo EPI data cd ../fmri/ # Crop extraneous tissue using the t2-based mask generated earlier @@ -436,20 +515,13 @@ sct_crop_image -i fmri_moco_mean.nii.gz -m mask_fmri.nii.gz -b 0 # Segment the cord using the cropped image sct_deepseg sc_epi -i fmri_moco_mean_crop.nii.gz -qc ~/qc_singleSubj -# Canal segmentation -cd ../t2 -sct_deepseg sc_canal_t2 -i t2.nii.gz -qc ~/qc_singleSubj -# Check results using FSLeyes -fsleyes t2.nii.gz -cm greyscale t2_canal_seg_seg.nii.gz -cm red -a 70.0 & - -# Compute aSCOR (Adapted Spinal Cord Occupation Ratio) -# i.e. Spinal cord to canal ratio using the canal seg -sct_compute_ascor -i-SC t2_seg.nii.gz -i-canal t2_canal_seg.nii.gz -perlevel 1 -o ascor.csv - # Segment the spinal nerve rootlets -sct_deepseg rootlets -i t2.nii.gz -qc ~/qc_singleSubj +cd ../t2/ +sct_deepseg rootlets -i t2.nii.gz -o t2_rootlets.nii.gz -qc ~/qc_singleSubj # Check results using FSLeyes fsleyes t2.nii.gz -cm greyscale t2_rootlets.nii.gz -cm subcortical -a 70.0 & +# Rootlets-based registration +sct_register_to_template -i t2.nii.gz -s t2_seg.nii.gz -lrootlet t2_rootlets.nii.gz -c t2 -ofolder rootlets-reg -qc ~/qc_singleSubj # Multiple sclerosis lesion segmentation on T2-weighted images cd ../t2_ms/ @@ -462,3 +534,23 @@ fsleyes t2.nii.gz -cm greyscale t2_lesion_seg.nii.gz -cm red -a 70.0 & # Return to parent directory cd .. + + +# Display results (to easily compare integrity across SCT versions) +# =========================================================================================== +set +v +end=$(date +%s) +runtime=$((end-start)) +echo "~~~" # these are used to format as code when copy/pasting in github's markdown +echo "Version: $(sct_version)" +echo "Ran on: $(uname -nsr)" +echo "Duration: $((runtime / 3600))hrs $(((runtime / 60) % 60))min $((runtime % 60))sec" +echo "---" +# The file `test_batch_processing.py` will output tested values when run as a script +"$SCT_DIR"/python/envs/venv_sct/bin/python "$SCT_DIR"/testing/batch_single_subject/test_batch_single_subject.py || +"$SCT_DIR"/python/envs/venv_sct/python.exe "$SCT_DIR"/testing/batch_single_subject/test_batch_single_subject.py +echo "~~~" + +# Display syntax to open QC report on web browser +echo "To open Quality Control (QC) report on a web-browser, run the following:" +echo "$open_command $SCT_BP_QC_FOLDER/index.html" From 32695f3231a04e2cb82ecb892d00dbfcc4288fd6 Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Wed, 24 Jun 2026 14:00:08 -0400 Subject: [PATCH 4/6] `run_script.yml`: Temporarily use upstream SCT branch --- .github/workflows/run_script_and_create_release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run_script_and_create_release.yml b/.github/workflows/run_script_and_create_release.yml index 805c58e..17ce5aa 100644 --- a/.github/workflows/run_script_and_create_release.yml +++ b/.github/workflows/run_script_and_create_release.yml @@ -34,6 +34,7 @@ jobs: - name: Checkout spinalcordtoolbox uses: actions/checkout@v4 with: + ref: jn/3508-merge-example-datasets-and-batch-scripts repository: spinalcordtoolbox/spinalcordtoolbox path: spinalcordtoolbox From f0b7fbc623ec6ce028f7a1d9d4361a3446cdaee6 Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Thu, 25 Jun 2026 10:57:01 -0400 Subject: [PATCH 5/6] [debug] update path to csv files in GHA yml --- .../workflows/run_script_and_create_release.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_script_and_create_release.yml b/.github/workflows/run_script_and_create_release.yml index 17ce5aa..1d5e66c 100644 --- a/.github/workflows/run_script_and_create_release.yml +++ b/.github/workflows/run_script_and_create_release.yml @@ -26,6 +26,12 @@ on: description: 'Release title (e.g. rYYYYMMDD)' required: true +env: + # Hardcoding this variable just to make accessing the output files a bit simpler (for testing) + # See: https://github.com/spinalcordtoolbox/sct_tutorial_data/actions/runs/28119017640/job/83265838588?pr=35#step:3:43 + # Also: https://github.com/orgs/community/discussions/25576#discussioncomment-3248364 + SCT_DIR: "${{ github.workspace }}/spinalcordtoolbox" + jobs: run-course-script: runs-on: ubuntu-latest @@ -57,6 +63,9 @@ jobs: path: ${{ github.event.repository.name }} - name: Run batch_single_subject.sh to generate intermediate files + # NB: SCT's copy of batch_single_subject.sh is standalone and cds into SCT_DIR + # So, even though we run the script here, the output files will be generated elsewhere + # This is just temporary for testing purposes, so don't commit these changes. run: | cd "${{ github.event.repository.name }}/single_subject" ./batch_single_subject.sh @@ -71,11 +80,11 @@ jobs: uses: actions/upload-artifact@v4 with: name: Single Subject CSV Files - path: ${{ github.event.repository.name }}/single_subject/data/**/*.csv + path: ${{ env.SCT_DIR }}/data/sct_course_data/single_subject/data/**/*.csv - name: "Package data into tutorial-specific .zip files" run: | - cd ${{ github.event.repository.name }} + cd "${{ env.SCT_DIR }}/data/sct_course_data/" awk -F, '{ print $1,$2 }' tutorial-datasets.csv | xargs -l zip -ur - uses: ncipollo/release-action@v1 @@ -93,7 +102,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - file: "${{ github.event.repository.name }}/*.zip" + file: "${{ env.SCT_DIR }}/data/sct_course_data/*.zip" draft: false tag_name: ${{ github.event.inputs.release_title }} release_id: ${{ steps.create_release.outputs.id }} From 12b4d82f5691bcf9715edeb5ff9f5dd1408869aa Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Thu, 25 Jun 2026 10:57:38 -0400 Subject: [PATCH 6/6] `process_csvs.py`: Update from c3c4 to c2c3 --- .github/workflows/scripts/process_csvs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scripts/process_csvs.py b/.github/workflows/scripts/process_csvs.py index e0be780..bc29c11 100644 --- a/.github/workflows/scripts/process_csvs.py +++ b/.github/workflows/scripts/process_csvs.py @@ -124,8 +124,8 @@ def process_ap_ratio_norm_pam50(ci_root: Path, tutorials_root: Path) -> None: def process_csa_c3c4(ci_root: Path, tutorials_root: Path) -> None: out = tutorials_root / "shape-analysis/compute-csa-and-other-shape-metrics" process( - ci_root / "t2/csa_c3c4.csv", - out / "csa_c3c4.csv", + ci_root / "t2/csa_c2c3.csv", + out / "csa_c2c3.csv", keep_cols=["Filename", "Slice (I->S)", "VertLevel", "MEAN(area)", "STD(area)"], conditional_vertlevel=True, )