diff --git a/.github/workflows/DCE.yml b/.github/workflows/DCE.yml index 54f4499..4da723d 100644 --- a/.github/workflows/DCE.yml +++ b/.github/workflows/DCE.yml @@ -1,34 +1,40 @@ -name: Bash script +name: Run DCE then build docker on: push: - branches: [ "freesurfer", "main" ] + branches: [ "dev", "main" ] pull_request: - branches: [ "freesurfer", "main" ] + branches: [ "dev", "main" ] permissions: contents: read jobs: - build: + run: runs-on: self-hosted steps: - - uses: actions/checkout@v3 - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v5 + + - name: Checkout ROCKETSHIP repo + uses: actions/checkout@v5 with: repository: petmri/ROCKETSHIP ref: dev path: ROCKETSHIP + #- name: Set up Python 3.10 # uses: actions/setup-python@v3 # with: # python-version: "3.10" + - name: Install dependencies run: | python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install --upgrade hd-bet # pip install flake8 pytest @@ -38,29 +44,71 @@ jobs: # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + #- name: Test with pytest # run: | # pytest + #- name: Set up MATLAB # uses: matlab-actions/setup-matlab@v1 - - name: Clean the runner subject data - run: | - cd /media/network_mriphysics/USC-PPG/RUNNER_DATA/1101743_runner/1st_timepoint - shopt -s extglob - rm -f !(2.nii|5.nii|10.nii|12.nii|15.nii|DCE.nii|aif.nii) - cd /home/mrispec/actions-runner/_work/in-house_toolbox/in-house_toolbox - name: Preprocess the data - run: ./preprocess_all.sh -d /media/network_mriphysics/USC-PPG/RUNNER_DATA -b -Z + run: ./preprocess_all.sh -d /media/network_mriphysics/USC-PPG/RUNNER_DATA/rawdata -b -Z -m -c - name: Run DCE - run: ./DCE_all.sh -d /media/network_mriphysics/USC-PPG/RUNNER_DATA -b -Z + run: ./DCE_all.sh -d /media/network_mriphysics/USC-PPG/RUNNER_DATA/rawdata -f + - name: Output logs + run: | + date=$(date +"%Y-%m-%d") + cat /media/network_mriphysics/USC-PPG/RUNNER_DATA/derivatives/logs/preprocessing_log_${date}.txt + cat /media/network_mriphysics/USC-PPG/RUNNER_DATA/derivatives/logs/dce_log_${date}.txt + + - name: Convert HTML to PDF + run: wkhtmltopdf --enable-local-file-access /media/network_mriphysics/USC-PPG/RUNNER_DATA/derivatives/sub-1101743/ses-01/reports/sub-1101743_ses-01_desc-casereport.html /media/network_mriphysics/USC-PPG/RUNNER_DATA/sub-1101743/ses-01/reports/sub-1101743_ses-01_desc-casereport.pdf + continue-on-error: true + - name: Upload analysis results - uses: actions/upload-artifact@v3.1.0 + uses: actions/upload-artifact@v4 with: - name: T1_Ktrans_graphs - path: /media/network_mriphysics/USC-PPG/RUNNER_DATA/1101743_runner/1st_timepoint/report.png - retention-days: 5 + name: HTML_report + path: | + /media/network_mriphysics/USC-PPG/RUNNER_DATA/derivatives/dceprep/sub-1101743/ses-01/reports/sub-1101743_ses-01_desc-casereport.html + /media/network_mriphysics/USC-PPG/RUNNER_DATA/derivatives/dceprep/sub-1101743/ses-01/reports/sub-1101743_ses-01_desc-casereport.pdf + /media/network_mriphysics/USC-PPG/RUNNER_DATA/derivatives/dceprep/sub-1101743/ses-01/figures/ + retention-days: 7 if-no-files-found: error + docker: + needs: run + strategy: + fail-fast: false + matrix: + release: [R2020a, R2020b, R2021a, R2021b, R2022a, R2022b, R2023a] + + runs-on: self-hosted + + steps: + - uses: actions/checkout@v4 + + - name: Extract branch name + id: extract_branch + shell: bash + run: echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> "$GITHUB_ENV" + + - name: Build Dockerfile + id: docker_build + run: | + docker image prune -f + docker build --build-arg MATLAB_RELEASE=${{ matrix.release }} . -t lsaca05/dce:${{ matrix.release }}-$branch + + # - name: Run Docker + # if: ${{ matrix.release }} == "R2020a" + # run: | + # ./run_docker.sh + # exit + + # - name: Push to Docker Hub + # if: steps.docker_build.outcome == 'success' + # run: | + # docker push lsaca05/dce:${{ matrix.release }}-$branch \ No newline at end of file diff --git a/.gitignore b/.gitignore index a9af213..e74e283 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .project .pydevproject +docker/files/license.txt +__pycache__ \ No newline at end of file diff --git a/DCE_MRI_process_all.sh b/DCE_MRI_process_all.sh deleted file mode 100755 index 367011e..0000000 --- a/DCE_MRI_process_all.sh +++ /dev/null @@ -1,508 +0,0 @@ -#!/bin/bash -# FSL, AFNI, Matlab, ROCKETSHIP + parametric_scripts, and Python are required -# Within parametric_scripts should be a custom scripts folder with T1mapping_fit.m -# control variables -EN_Z_NORM=0 -EN_BIAS1=0 -EN_BIAS2=0 -ff=0 -#EN_MOTION_CORR=1 - -# make this your main data directory or pass it as an option to -d -#DATA_DIR=/media/network_mriphysics/USC-PPG/data - -# options -while getopts ":d:bBZFh" options; do - case "${options}" in - b) - EN_BIAS1=1 - ;; - B) EN_BIAS2=1 - ;; - d) - DATA_DIR=${OPTARG} - ;; - F) - ff=1 - ;; - h) - echo "This script runs through all subject folders of a specified main data directory, processing every folder ending in '_timepoint'." - echo "-b: enable first round of bias field corrections" - echo "-B: enable second round of bias field corrections, post-Z-norm if enabled" - echo "-Z: enable Z-slice normalization" - echo "-d: specify main data directory containing all subject folders" - echo "-F: fail fast, any command failures will end the script" - echo "-h: display this message" - exit 0 - ;; - Z) - EN_Z_NORM=1 - ;; - esac -done - -if [ -z "$DATA_DIR" ] - then - echo "ERROR: Please use '-d [dir_path]' to pass the path to your main data directory to this script." - exit 1 -fi -cd $DATA_DIR -ROCKETSHIP_PATH=$(find $HOME -name '*run_dce_auto.m' -printf '%h\n' -quit) -GPUFIT_PATH=$(find $HOME -type d -name Gpufit-build) -SCRIPT_PATH=$(find $HOME -name '*auto_analysis.py' -printf '%h\n' -quit) -#SCRIPT_PATH=/home/mrispec/Code/in-house_toolbox - -# Run bias correction on VFA data -# ------------------------------ -for dir in */*_timepoint/; do - date - echo Processing ${dir}... - # /home/raghav/data/203/1st_timepoint - SUBJECT_TP_PATH=$(realpath $dir) - - cd $dir - # FSL brain mask extraction from VFA 2 image - bet 2.nii brain.nii -R -m -f 0.45 -g 0 -Z - fslcpgeom 2.nii brain_mask.nii - - # FAST documentation recommends brain masking first - fslmaths 2.nii -mas brain_mask.nii.gz 2_masked.nii - fslmaths 5.nii -mas brain_mask.nii.gz 5_masked.nii - fslmaths 10.nii -mas brain_mask.nii.gz 10_masked.nii - fslmaths 12.nii -mas brain_mask.nii.gz 12_masked.nii - fslmaths 15.nii -mas brain_mask.nii.gz 15_masked.nii - - if [ $EN_BIAS1 -eq 1 ] - then - - echo Bias field correction with FAST - # don't forget to remove all unnecessary images - fast -t 3 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 2_masked.nii - rm 2_masked_mixeltype.nii.gz - rm 2_masked_pve_0.nii.gz - rm 2_masked_pve_1.nii.gz - rm 2_masked_pve_2.nii.gz - rm 2_masked_pveseg.nii.gz - rm 2_masked_seg.nii.gz - 3dcalc -a 2_masked.nii -b 2_masked_bias.nii.gz -expr a/b -prefix 2_bfc.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 5_masked.nii - rm 5_masked_mixeltype.nii.gz - rm 5_masked_pve_0.nii.gz - rm 5_masked_pve_1.nii.gz - rm 5_masked_pve_2.nii.gz - rm 5_masked_pveseg.nii.gz - rm 5_masked_seg.nii.gz - 3dcalc -a 5_masked.nii -b 5_masked_bias.nii.gz -expr a/b -prefix 5_bfc.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 10_masked.nii - rm 10_masked_mixeltype.nii.gz - rm 10_masked_pve_0.nii.gz - rm 10_masked_pve_1.nii.gz - rm 10_masked_pve_2.nii.gz - rm 10_masked_pveseg.nii.gz - rm 10_masked_seg.nii.gz - 3dcalc -a 10_masked.nii -b 10_masked_bias.nii.gz -expr a/b -prefix 10_bfc.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 12_masked.nii - rm 12_masked_mixeltype.nii.gz - rm 12_masked_pve_0.nii.gz - rm 12_masked_pve_1.nii.gz - rm 12_masked_pve_2.nii.gz - rm 12_masked_pveseg.nii.gz - rm 12_masked_seg.nii.gz - 3dcalc -a 12_masked.nii -b 12_masked_bias.nii.gz -expr a/b -prefix 12_bfc.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 15_masked.nii - rm 15_masked_mixeltype.nii.gz - rm 15_masked_pve_0.nii.gz - rm 15_masked_pve_1.nii.gz - rm 15_masked_pve_2.nii.gz - rm 15_masked_pveseg.nii.gz - #rm 15_masked_seg.nii.gz - 3dcalc -a 15_masked.nii -b 15_masked_bias.nii.gz -expr a/b -prefix 15_bfc.nii - - # threshold and binarize wm mask - fslmaths 15_masked_seg.nii.gz -thr 3 -uthr 3 15_wm.nii - - # apply wm mask to all VFAs - fslmaths 2_bfc.nii -mas 15_wm.nii.gz 2_bfc_wm.nii - fslmaths 5_bfc.nii -mas 15_wm.nii.gz 5_bfc_wm.nii - fslmaths 10_bfc.nii -mas 15_wm.nii.gz 10_bfc_wm.nii - fslmaths 12_bfc.nii -mas 15_wm.nii.gz 12_bfc_wm.nii - fslmaths 15_bfc.nii -mas 15_wm.nii.gz 15_bfc_wm.nii - else - # dumb file name management for norm only runs - fslmaths 2.nii -mas brain_mask.nii.gz 2_bfc.nii - fslmaths 5.nii -mas brain_mask.nii.gz 5_bfc.nii - fslmaths 10.nii -mas brain_mask.nii.gz 10_bfc.nii - fslmaths 12.nii -mas brain_mask.nii.gz 12_bfc.nii - fslmaths 15.nii -mas brain_mask.nii.gz 15_bfc.nii - - echo Skipping BFC... but still segmenting one VFA for matter masks - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 15_bfc.nii - rm 15_bfc_mixeltype.nii.gz - rm 15_bfc_pve_0.nii.gz - rm 15_bfc_pve_1.nii.gz - rm 15_bfc_pve_2.nii.gz - rm 15_bfc_pveseg.nii.gz - #rm 15_bfc_seg.nii.gz - - # threshold and binarize wm mask - fslmaths 15_bfc_seg.nii.gz -thr 3 -uthr 3 15_wm.nii - - # apply wm mask to all VFAs - fslmaths 2_bfc.nii -mas 15_wm.nii.gz 2_bfc_wm.nii - fslmaths 5_bfc.nii -mas 15_wm.nii.gz 5_bfc_wm.nii - fslmaths 10_bfc.nii -mas 15_wm.nii.gz 10_bfc_wm.nii - fslmaths 12_bfc.nii -mas 15_wm.nii.gz 12_bfc_wm.nii - fslmaths 15_bfc.nii -mas 15_wm.nii.gz 15_bfc_wm.nii - fi - - # Run Z-axis normalization VFA data - # ------------------------------ - if [ $EN_Z_NORM -eq 1 ] - then - echo begin slice normalization - python3 $SCRIPT_PATH/VFA_norm.py $SUBJECT_TP_PATH - fi - - if [ $ff -eq 1 ] - then - if [ ! -f "15_BFC_Z.nii" ] - then - echo "Missing Z-normalized files. Z-norm likely failed due to non-existent inputs." - exit 1 - fi - fi - - if [ $EN_BIAS2 -eq 1 ] - then - # 2nd Bias correction VFA data - # ------------------------------ - echo Begin second round of BFC - # Bias field correction with FAST - # don't forget to remove all unnecessary images - fast -t 3 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 2_BFC_Z.nii - rm 2_BFC_Z_mixeltype.nii.gz - rm 2_BFC_Z_pve_0.nii.gz - rm 2_BFC_Z_pve_1.nii.gz - rm 2_BFC_Z_pve_2.nii.gz - rm 2_BFC_Z_pveseg.nii.gz - rm 2_BFC_Z_seg.nii.gz - 3dcalc -a 2_BFC_Z.nii -b 2_BFC_Z_bias.nii.gz -expr a/b -prefix 2_b2corr.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 5_BFC_Z.nii - rm 5_BFC_Z_mixeltype.nii.gz - rm 5_BFC_Z_pve_0.nii.gz - rm 5_BFC_Z_pve_1.nii.gz - rm 5_BFC_Z_pve_2.nii.gz - rm 5_BFC_Z_pveseg.nii.gz - rm 5_BFC_Z_seg.nii.gz - 3dcalc -a 5_BFC_Z.nii -b 5_BFC_Z_bias.nii.gz -expr a/b -prefix 5_b2corr.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 10_BFC_Z.nii - rm 10_BFC_Z_mixeltype.nii.gz - rm 10_BFC_Z_pve_0.nii.gz - rm 10_BFC_Z_pve_1.nii.gz - rm 10_BFC_Z_pve_2.nii.gz - rm 10_BFC_Z_pveseg.nii.gz - rm 10_BFC_Z_seg.nii.gz - 3dcalc -a 10_BFC_Z.nii -b 10_BFC_Z_bias.nii.gz -expr a/b -prefix 10_b2corr.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 12_BFC_Z.nii - rm 12_BFC_Z_mixeltype.nii.gz - rm 12_BFC_Z_pve_0.nii.gz - rm 12_BFC_Z_pve_1.nii.gz - rm 12_BFC_Z_pve_2.nii.gz - rm 12_BFC_Z_pveseg.nii.gz - rm 12_BFC_Z_seg.nii.gz - 3dcalc -a 12_BFC_Z.nii -b 12_BFC_Z_bias.nii.gz -expr a/b -prefix 12_b2corr.nii - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 15_BFC_Z.nii - rm 15_BFC_Z_mixeltype.nii.gz - rm 15_BFC_Z_pve_0.nii.gz - rm 15_BFC_Z_pve_1.nii.gz - rm 15_BFC_Z_pve_2.nii.gz - rm 15_BFC_Z_pveseg.nii.gz - rm 15_BFC_Z_seg.nii.gz - 3dcalc -a 15_BFC_Z.nii -b 15_BFC_Z_bias.nii.gz -expr a/b -prefix 15_b2corr.nii - - # concatenates 5 images in one VFA.nii image - 3dTcat -prefix VFA.nii 2_b2corr.nii 5_b2corr.nii 10_b2corr.nii 12_b2corr.nii 15_b2corr.nii - - elif [ $EN_Z_NORM -eq 1 ] - then - echo Concatenating Z-norm\'d images - # concatenates 5 images in one VFA.nii image - 3dTcat -prefix VFA.nii 2_BFC_Z.nii 5_BFC_Z.nii 10_BFC_Z.nii 12_BFC_Z.nii 15_BFC_Z.nii - - elif [ $EN_BIAS1 -eq 1 ] - then - echo Concatenating non Z\'d images - 3dTcat -prefix VFA.nii 2_bfc.nii 5_bfc.nii 10_bfc.nii 12_bfc.nii 15_bfc.nii - else - echo Concatenating raw images - 3dTcat -prefix VFA.nii 2_masked.nii 5_masked.nii 10_masked.nii 12_masked.nii 15_masked.nii - fi - - if [ $ff -eq 1 ] - then - if [ ! -f "VFA.nii" ] - then - echo "Missing VFA file. Component files may have failed." - exit 1 - fi - fi - - # motion correction of VFA - # ------------------------------ - mcflirt -in VFA.nii -refvol 'VFA.nii[0]' -cost mutualinfo -report -verbose -plots -o VFA_mc.nii - gunzip VFA_mc.nii.gz - - if [ $ff -eq 1 ] - then - if [ ! -f "VFA_mc.nii" ] - then - echo "Missing VFA_mc file. Motion correction may have failed." - exit 1 - fi - fi - # smooth - #3dBlurToFWHM -input VFA_mc.nii -FWHM 5 -prefix VFA_mc_blurred.nii - - # T1 mapping where the input image is 'VFA.motioncorrected.nii' - # ------------------------------ - matlab -nodisplay -r "cd('$ROCKETSHIP_PATH/parametric_scripts/custom_scripts'); addpath '$ROCKETSHIP_PATH'; addpath '$ROCKETSHIP_PATH/dce'; addpath '$ROCKETSHIP_PATH/external_programs'; addpath '$ROCKETSHIP_PATH/external_programs/niftitools'; addpath '$ROCKETSHIP_PATH/parametric_scripts'; T1mapping_fit('$SUBJECT_TP_PATH/'); exit;" - if [ $ff -eq 1 ] - then - if [ ! -f "T1_map_t1_fa_fit_VFA_mc.nii" ] - then - echo "Missing T1 map file. T1 mapping may have failed." - exit 1 - fi - fi - # Motion correction of dynamic images using AFNI - # ------------------------------ - echo Motion correcting dynamic images... - mcflirt -in DCE.nii -refvol 'DCE.nii[1]' -cost mutualinfo -report -plots -o DCE_mc.nii - python3 $SCRIPT_PATH/max_disp.py $SUBJECT_TP_PATH - if [ $ff -eq 1 ] - then - if [ ! -f "DCE_mc.nii.gz" ] - then - echo "Missing motion corrected DCE file." - exit 1 - fi - fi - # Align T1 map with Dynamic data - # ------------------------------ - # MC or no? - 3dTcat -prefix ref_rep.nii DCE_mc.nii'[1]' - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii T1_map_t1_fa_fit_VFA_mc.nii t1_map_fixed_use_me.nii.gz - if [ $ff -eq 1 ] - then - if [ ! -f "t1_map_fixed_use_me.nii.gz" ] - then - echo "Missing registered T1 map." - exit 1 - fi - fi - # align and apply brain mask - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii brain_mask.nii.gz brain_mask_dyn.nii.gz - - # ensure AIF is included in mask - fslcpgeom 2.nii brain_mask_dyn.nii - cp aif.nii aif_aligned.nii - fslcpgeom brain_mask_dyn.nii aif_aligned.nii - fslmaths aif_aligned.nii -thr 0 aif_pos.nii - rm aif_aligned.nii - fslmaths brain_mask_dyn.nii -add aif_pos.nii -thr 1 -bin brain_mask_dyn_aif.nii - fslmaths DCE_mc.nii -mas brain_mask_dyn_aif.nii.gz DCE_mc_masked.nii - - if [ $EN_BIAS1 -eq 1 ] - then - - # Applying bias field correction on dynamic images - # ------------------------------ - echo Applying BFC to dynamic images... - 3dTcat -prefix 1st_rep.nii DCE_mc_masked.nii'[0]' # extract images from different DCE repetitions - 3dTcat -prefix 5th_rep.nii DCE_mc_masked.nii'[4]' - 3dTcat -prefix 10th_rep.nii DCE_mc_masked.nii'[9]' - 3dTcat -prefix 20th_rep.nii DCE_mc_masked.nii'[19]' - 3dTcat -prefix 30th_rep.nii DCE_mc_masked.nii'[29]' - 3dTcat -prefix 40th_rep.nii DCE_mc_masked.nii'[39]' - 3dTcat -prefix 50th_rep.nii DCE_mc_masked.nii'[49]' - 3dTcat -prefix 60th_rep.nii DCE_mc_masked.nii'[59]' - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 1st_rep.nii - rm 1st_rep_mixeltype.nii.gz - rm 1st_rep_pve_0.nii.gz - rm 1st_rep_pve_1.nii.gz - rm 1st_rep_pve_2.nii.gz - rm 1st_rep_pveseg.nii.gz - rm 1st_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 5th_rep.nii - rm 5th_rep_mixeltype.nii.gz - rm 5th_rep_pve_0.nii.gz - rm 5th_rep_pve_1.nii.gz - rm 5th_rep_pve_2.nii.gz - rm 5th_rep_pveseg.nii.gz - rm 5th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 10th_rep.nii - rm 10th_rep_mixeltype.nii.gz - rm 10th_rep_pve_0.nii.gz - rm 10th_rep_pve_1.nii.gz - rm 10th_rep_pve_2.nii.gz - rm 10th_rep_pveseg.nii.gz - rm 10th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 20th_rep.nii - rm 20th_rep_mixeltype.nii.gz - rm 20th_rep_pve_0.nii.gz - rm 20th_rep_pve_1.nii.gz - rm 20th_rep_pve_2.nii.gz - rm 20th_rep_pveseg.nii.gz - rm 20th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 30th_rep.nii - rm 30th_rep_mixeltype.nii.gz - rm 30th_rep_pve_0.nii.gz - rm 30th_rep_pve_1.nii.gz - rm 30th_rep_pve_2.nii.gz - rm 30th_rep_pveseg.nii.gz - rm 30th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 40th_rep.nii - rm 40th_rep_mixeltype.nii.gz - rm 40th_rep_pve_0.nii.gz - rm 40th_rep_pve_1.nii.gz - rm 40th_rep_pve_2.nii.gz - rm 40th_rep_pveseg.nii.gz - rm 40th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 50th_rep.nii - rm 50th_rep_mixeltype.nii.gz - rm 50th_rep_pve_0.nii.gz - rm 50th_rep_pve_1.nii.gz - rm 50th_rep_pve_2.nii.gz - rm 50th_rep_pveseg.nii.gz - rm 50th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 60th_rep.nii - rm 60th_rep_mixeltype.nii.gz - rm 60th_rep_pve_0.nii.gz - rm 60th_rep_pve_1.nii.gz - rm 60th_rep_pve_2.nii.gz - rm 60th_rep_pveseg.nii.gz - rm 60th_rep_seg.nii.gz - - # Concatenation1 - 3dTcat -prefix dyn_bias.nii 1st_rep_bias.nii.gz 5th_rep_bias.nii.gz 10th_rep_bias.nii.gz 20th_rep_bias.nii.gz 30th_rep_bias.nii.gz 40th_rep_bias.nii.gz 50th_rep_bias.nii.gz 60th_rep_bias.nii.gz - - # Computing average across 8 bias field that have been sampled - 3dTstat -mean -prefix mean_dyn_bias_map.nii dyn_bias.nii'[0..7]' - - # Normalizing motion corrected DCE image with mean bias field - 3dcalc -a DCE_mc_masked.nii -b mean_dyn_bias_map.nii -expr a/b -prefix DCE_mc_bfc.nii - - # don't forget to remove all unnecessary images - rm 1st_rep.nii - rm 1st_rep_bias.nii.gz - rm 5th_rep.nii - rm 5th_rep_bias.nii.gz - rm 10th_rep.nii - rm 10th_rep_bias.nii.gz - rm 20th_rep.nii - rm 20th_rep_bias.nii.gz - rm 30th_rep.nii - rm 30th_rep_bias.nii.gz - rm 40th_rep.nii - rm 40th_rep_bias.nii.gz - rm 50th_rep.nii - rm 50th_rep_bias.nii.gz - rm 60th_rep.nii - rm 60th_rep_bias.nii.gz - else - #echo Motion correcting dynamic set - #3dvolreg -heptic -verbose -base 'DCE.nii[1]' -dfile DCE_motion.txt -prefix DCE_mc_bfc.nii DCE.nii - #3dTcat -prefix ref_rep.nii dce_mc_bfc'[1]' - mv DCE_mc.nii DCE_mc_bfc.nii - fi - - # align existing white matter mask to dynamic images and re-binarize - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii 15_wm.nii.gz 15_wm_mask_dyn.nii.gz - fslmaths 15_wm_mask_dyn.nii.gz -thr 1.7 -bin 15_wm_mask_dyn.nii - - # apply wm mask to all DCE images - fslmaths DCE_mc_bfc.nii -mas 15_wm_mask_dyn.nii.gz DCE_mc_bfc_wm.nii.gz - - # normalize dynamic images - # ------------------------------ - echo Normalizing dynamic images... - python $SCRIPT_PATH/DCE_norm.py $SUBJECT_TP_PATH - - # smooth dynamic set - #3dBlurToFWHM -input DCE_mc_bfc_norm.nii -FWHM 4 -prefix DCE_mc_bfc_norm_blurred.nii - - # DCE - # ------------------------------ - echo Begin DCE processing... - matlab -nodisplay -r "cd('$ROCKETSHIP_PATH'); addpath '$GPUFIT_PATH/matlab'; run_dce_auto('$SUBJECT_TP_PATH/'); exit;" - - if [ $ff -eq 1 ] - then - if [ ! -f "dce_patlak_fit_Ktrans.nii" ] - then - echo "Missing Ktrans maps. Check terminal--DCE failed or inputs were not generated." - exit 1 - fi - fi - - # Analyze results (scouting) - # ------------------------------ - # Make gm mask - if [ $EN_BIAS1 -eq 1 ] - then - fslmaths 15_masked_seg.nii.gz -thr 2 -uthr 2 -bin 15_gm_mask.nii - fslmaths 15_bfc.nii -mas 15_gm_mask.nii.gz 15_gm.nii - else - fslmaths 15_bfc_seg.nii.gz -thr 2 -uthr 2 -bin 15_gm_mask.nii - fslmaths 15_bfc.nii -mas 15_gm_mask.nii.gz 15_gm.nii - fi - - # Align then re-binarize gm mask - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii 15_gm.nii.gz 15_gm_mask_dyn.nii.gz - fslmaths 15_gm_mask_dyn.nii.gz -thr 20 -bin 15_gm_mask_dyn.nii - - # Make CSF mask - if [ $EN_BIAS1 -eq 1 ] - then - fslmaths 15_masked_seg.nii.gz -thr 1 -uthr 1 -bin 15_csf_mask.nii - fslmaths 15_bfc.nii -mas 15_csf_mask.nii.gz 15_csf.nii - else - fslmaths 15_bfc_seg.nii.gz -thr 1 -uthr 1 -bin 15_csf_mask.nii - fslmaths 15_bfc.nii -mas 15_csf_mask.nii.gz 15_csf.nii - fi - - # Align CSF mask - flirt -in 15_csf.nii.gz -ref ref_rep.nii -out 15_csf_mask_dyn.nii.gz -init t12dcevol.mat -applyxfm - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii 15_csf.nii.gz 15_csf_mask_dyn.nii.gz - fslmaths 15_csf_mask_dyn.nii.gz -thr 20 -bin 15_csf_mask_dyn.nii - - # Apply masks to T1 map - fslmaths t1_map_fixed_use_me.nii.gz -mas 15_wm_mask_dyn.nii T1_wm.nii - fslmaths t1_map_fixed_use_me.nii.gz -mas 15_gm_mask_dyn.nii T1_gm.nii - fslmaths t1_map_fixed_use_me.nii.gz -mas 15_csf_mask_dyn.nii T1_csf.nii - - # Apply masks to Ktrans map - fslmaths dce_patlak_fit_Ktrans.nii -mas 15_wm_mask_dyn.nii Ktrans_wm.nii - fslmaths dce_patlak_fit_Ktrans.nii -mas 15_gm_mask_dyn.nii Ktrans_gm.nii - fslmaths dce_patlak_fit_Ktrans.nii -mas 15_csf_mask_dyn.nii Ktrans_csf.nii - - python $SCRIPT_PATH/auto_analysis.py $SUBJECT_TP_PATH - cd ../../ - echo $dir processing complete! -done diff --git a/DCE_all.sh b/DCE_all.sh index 16736c5..aa540bf 100755 --- a/DCE_all.sh +++ b/DCE_all.sh @@ -1,44 +1,89 @@ #!/bin/bash -# Oct 13, 2022 -# FSL, AFNI, Matlab, ROCKETSHIP + parametric_scripts, and Python are required -# Within parametric_scripts should be a custom scripts folder with T1mapping_fit.m +# FSL, Matlab, ROCKETSHIP + parametric_scripts, ANTS, and Python are required +# Within ROCKETSHIP/parametric_scripts should be a custom scripts folder with T1mapping_fit.m +shopt -s extglob # control variables -EN_Z_NORM=0 -EN_BIAS1=1 -EN_BIAS2=0 -ff=0 -#EN_MOTION_CORR=1 - -# make this your main data directory or pass it as an option to -d -#DATA_DIR=/media/network_mriphysics/USC-PPG/data +COMPARISON_MODE=0 +EN_BIAS1=0 +EN_SMOOTHING=0 +fail=0 +count=0 +total=0 +successes=0 +SKIP_IF_SUCCESS=0 +USE_FREESURFER=0 +PURGE_INTERMEDIATES=0 +GIGA_PURGE=0 +TARGET_FLAG=0 +SCRIPT_LOOP_DIR=dceprep/sub-*/ses-* # options -while getopts ":d:bBZFh" options; do +while getopts ":d:C:fhl:sST:" options; do case "${options}" in b) EN_BIAS1=1 ;; - B) EN_BIAS2=1 + C) + COMPARISON_MODE=1 + OUTPUT_DIR=${OPTARG} + SCRIPT_LOOP_DIR=dceprep-${OPTARG}/sub-*/ses-* ;; d) DATA_DIR=${OPTARG} + if [ ${DATA_DIR::-1} == "/" ] + then + DATA_DIR=${DATA_DIR::-1} + fi + date=$(date +%Y-%m-%d) + DERIV_DIR=$(dirname $DATA_DIR)/derivatives + LOG_FILE=$DERIV_DIR/logs/dce_log_$date.txt + if [ ! -d "$DATA_DIR/logs" ] + then + mkdir $DATA_DIR/logs + fi + # write command to log file + echo "Command: $0 $@" > $LOG_FILE ;; - F) - ff=1 + f) + USE_FREESURFER=1 ;; h) echo "This script runs through all subject folders of a specified main data directory, processing every folder ending in '_timepoint'." - echo "The output is mainly the DCE outputs (Ktrans maps) and QC graphs." - echo "-b: enable first round of bias field corrections" - echo "-B: enable second round of bias field corrections, post-Z-norm if enabled" - echo "-Z: enable Z-slice normalization" + echo "The data must be preprocessed with \`preprocess_all.sh\` before running this script." + echo "The input is \`DCE_bfc_norm.nii.gz\`. The output is mainly the DCE outputs (Ktrans maps) and QC reports." + echo "If everything goes well, the script will output a case report for each subject and a population report for the entire dataset." + echo "-C: enable comparison mode. Specify output directory." echo "-d: specify main data directory containing all subject folders" - echo "-F: fail fast, any command failures will end the script" + echo "-f: enable Freesurfer wm parcellation for subregion analysis" echo "-h: display this message" + # echo "-l: specify a list of subjects to process (filename only, place in code folder)" + echo "-s: skip subjects that have already been processed" + echo "-S: enable smoothing of DCE input" + echo "-T [dir_path]: specify the subject(s)/session(s) to run (default is 'sub-*/ses-*/')" exit 0 ;; - Z) - EN_Z_NORM=1 + l) + INPUT_LIST=$DATA_DIR/../code/${OPTARG} + ;; + s) + SKIP_IF_SUCCESS=1 + ;; + S) + EN_SMOOTHING=1 + ;; + T) + TARGET_FLAG=1 + if [ $COMPARISON_MODE -eq 1 ] + then + SCRIPT_LOOP_DIR=dceprep-$OUTPUT_DIR/$OPTARG + else + SCRIPT_LOOP_DIR=dceprep/$OPTARG + fi + echo "Processing target directory: $SCRIPT_LOOP_DIR" + ;; + *) + echo "Invalid option ${OPTARG}. Please use -h for a list of valid options." + exit 1 ;; esac done @@ -48,81 +93,295 @@ if [ -z "$DATA_DIR" ] echo "ERROR: Please use '-d [dir_path]' to pass the path to your main data directory to this script." exit 1 fi -cd $DATA_DIR -ROCKETSHIP_PATH=$(find $HOME -type d -name ROCKETSHIP) -GPUFIT_PATH=$(find $HOME -type d -name Gpufit-build) -SCRIPT_PATH=$(find $HOME -type d -name in-house_toolbox) if [[ "$OSTYPE" == "linux-gnu" ]]; then - ROCKETSHIP_PATH=$(find $HOME -name '*run_dce_auto.m' -printf '%h\n' -quit) - SCRIPT_PATH=$(find $HOME -name '*auto_analysis.py' -printf '%h\n' -quit) + ROCKETSHIP_PATH=$(find $HOME -name '*run_dce_cli.m' -printf '%h\n' -quit || find / -name '*run_dce_cli.m' -printf '%h\n' -quit) &> /dev/null + SCRIPT_PATH=$(dirname "$(realpath $0)") + GPUFIT_PATH=$(find $HOME -name 'GpufitConstrainedMex.mexa64' -printf '%h\n' -quit || find / -name 'GpufitConstrainedMex.mexa64' -printf '%h\n' -quit) + GPUFIT_M_PATH=$(find $HOME -name 'ModelID.m' -printf '%h\n' -quit || find / -name 'ModelID.m' -printf '%h\n' -quit) +else + ROCKETSHIP_PATH=$(find $HOME -type d -name ROCKETSHIP) + SCRIPT_PATH=$(find $HOME -type d -name in-house_toolbox) + GPUFIT_PATH=$(find $HOME -type d -name Gpufit-build) fi +cd $DERIV_DIR || exit 1 -# Run bias correction on VFA data -# ------------------------------ -for dir in */*_timepoint/; do - date - echo DCE processing ${dir}... - SUBJECT_TP_PATH=$(realpath $dir) - cd $dir +# rm dce_log.txt +# read list of subjects +# while IFS= read -r line; do +# SUBJECT=sub-$(echo $line | awk '{print $1}') +# echo $SUBJECT +# SESSION=$(echo $line | awk '{print $3}') +# Function to calculate and display progress and estimated remaining time +function show_progress { + local current_iteration=$1 + local start_time=$2 + local total_iterations=$3 + local runtime=$4 - # DCE - # ------------------------------ - echo Begin DCE processing... - matlab -nodisplay -r "cd('$ROCKETSHIP_PATH'); addpath '$GPUFIT_PATH/matlab'; run_dce_auto('$SUBJECT_TP_PATH/'); exit;" + # Calculate elapsed time + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + + # Calculate estimated total time + estimated_total_time=$((runtime * total_iterations)) - if [ $ff -eq 1 ] + # Calculate remaining time + remaining_time=$((estimated_total_time - elapsed_time)) + + # Display progress and estimated remaining time + echo -ne "Progress: $((elapsed_time * 100 / estimated_total_time))% - Elapsed time: $(($elapsed_time / 60))m $(($elapsed_time % 60))s - " + if [ $remaining_time -lt 0 ] + then + echo -ne "Estimated remaining time: calculating... \r" + else + echo -ne "Estimated remaining time: $(($remaining_time / 60))m $(($remaining_time % 60))s \r" + fi +} +# if [ $COMPARISON_MODE -eq 1 ] && [ -d dceprep-$OUTPUT_DIR ] && [ $TARGET_FLAG -eq 0 ]; +# then +# SCRIPT_LOOP_DIR=dceprep-$OUTPUT_DIR/sub-*/ses-* +# elif [ $COMPARISON_MODE -eq 1 ] && [ -d dceprep-$OUTPUT_DIR ] && [ $TARGET_FLAG -eq 0 ]; +# then +# SCRIPT_LOOP_DIR=dceprep/sub-*/ses-* +# fi +# if [ -z "$(ls -A $SCRIPT_LOOP_DIR 2>/dev/null)" ]; then +# SCRIPT_LOOP_DIR=dceprep-multihance_fix/sub-*/ses-* +# fi +for der_dir in $SCRIPT_LOOP_DIR; do + ((total++)) +done +runtime=70 +start_time=$(date +%s) +for der_dir in $SCRIPT_LOOP_DIR; do + dir=$der_dir + date >> $LOG_FILE + + ((count++)) + show_progress $count $start_time $total $runtime + + # get subject ID and session + SUBJECT=$(echo $der_dir | grep -o 'sub-[^/]*') + SESSION=$(echo $der_dir | grep -o 'ses-[0-9]*') + PREFIX=${SUBJECT}_${SESSION} + if [ $COMPARISON_MODE -eq 1 ] + then + echo "Comparison mode enabled. Processing in $OUTPUT_DIR..." >> $LOG_FILE + if [ ! -d $DERIV_DIR/dceprep-"$OUTPUT_DIR"/$SUBJECT/$SESSION ] + then + mkdir -p $DERIV_DIR/dceprep-"$OUTPUT_DIR"/$SUBJECT/$SESSION + mkdir -p $DERIV_DIR/dceprep-"$OUTPUT_DIR"/$SUBJECT/$SESSION/dce + mkdir -p $DERIV_DIR/dceprep-"$OUTPUT_DIR"/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/dce/*bfcz* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/dce/*desc-AIF*_T1map* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/dce/*hmc* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/dce/*.txt $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/*_T1map* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/*mask* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/*.mat $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/*wmparc* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/*DCEref_VFA* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/*DCEref_T1w* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/figures $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/figures + fi + cd $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION || echo "ERROR: $OUTPUT_DIR does not exist. Preprocess first or check the name and try again." >> $LOG_FILE + if [ ! -f dce/${PREFIX}_desc-hmc_DCEref.nii.gz ] + then + # probably a no motion correction run, get 2nd t-slice of DCE + # cp dce/${PREFIX}_desc-bfc_DCE.nii.gz dce/${PREFIX}_desc-hmc_DCE.nii.gz + fslmerge -n 1 dce/${PREFIX}_desc-hmc_DCEref.nii.gz dce/${PREFIX}_desc-bfc_DCE.nii.gz + fi + else + cd $der_dir || echo "ERROR: $der_dir does not exist. Preprocess first or check the name and try again." >> $LOG_FILE + fi + SUBJECT_TP_PATH=$(pwd) + if [ ! -f anat/${PREFIX}_desc-brain_T1w.nii.gz ] + then + cp -r $DERIV_DIR/dceprep-multihance_fix/$SUBJECT/$SESSION/anat/${PREFIX}_desc-brain_T1w.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/anat + fi + # Locking mechanism to prevent concurrent processing from other machines + LOCKFILE="lock.txt" + LOCKDIR=$(pwd) + LOCKPATH="$LOCKDIR/$LOCKFILE" + LOCKHOST=$(hostname) + LOCKPID=$$ + LOCKLIST="$DERIV_DIR/locks_${LOCKHOST}.txt" + echo "Locking $LOCKPATH on $LOCKHOST with PID $LOCKPID" + + # Try to create lock file atomically + if ( set -o noclobber; echo "$LOCKHOST:$LOCKPID" > "$LOCKPATH" ) 2> /dev/null; then + echo "$LOCKPATH" >> "$LOCKLIST" + trap 'for f in $(cat "$LOCKLIST" 2>/dev/null); do rm -f "$f"; done; rm -f "$LOCKLIST"; exit $?' INT TERM EXIT + else + echo "Skipping $dir because it is currently being processed by $(cat $LOCKPATH)." >> $LOG_FILE + cd $DERIV_DIR + continue + fi + if [ $SKIP_IF_SUCCESS -eq 1 ] then - if [ ! -f "dce_patlak_fit_Ktrans.nii" ] + if [ -f "reports/${PREFIX}_desc-casereport.html" ] && [ -f "anat/${PREFIX}_space-DCEref_desc-wmparc.nii.gz" ] + then + echo "Skipping $dir because it has already been processed." >> $LOG_FILE + ((successes++)) + if [ $PURGE_INTERMEDIATES -eq 1 ] # && [ $COMPARISON_MODE -eq 1 ] then - echo "Missing Ktrans maps. Check terminal--DCE failed or inputs were not generated." - fail=1 - continue + cd anat + rm -f !(${PREFIX}*bfczunified_VFA.nii|${PREFIX}_space-DCEref_T1map*|${PREFIX}*label-*_T1map*|*.mat|*wmparc.nii.gz|*space-DCEref_desc-brain_mask.nii.gz) + cd ../dce + rm -f !(${PREFIX}*Ktrans*|${PREFIX}*bfcz_DCE*|${PREFIX}*AIFincluded*|${PREFIX}*AIF_T1map*|figures|*.log|*.txt|*.par) fi + cd $DERIV_DIR + continue + fi + fi + if [ ! -f "dce/${PREFIX}_desc-bfcz_DCE.nii.gz" ] && [ ! -f "dce/${PREFIX}_desc-bfcz_DCE.nii" ] + then + echo Missing input file dce/${PREFIX}_desc-bfcz_DCE. Make sure the data has been preprocessed. Skipping $dir... >> $LOG_FILE + cd $DERIV_DIR + fail=1 + continue + fi + + # iNESMA smooth DCE input (exclude AIF roi) + if [ $EN_SMOOTHING -eq 1 ] + then + python3 $SCRIPT_PATH/iNESMA_GPU.py $PREFIX dce/${PREFIX}_desc-bfcz_DCE.nii.gz dce/${PREFIX}_desc-AIF_T1map.nii.gz fi - # Analyze results (scouting) + # DCE # ------------------------------ - # Make gm mask - if [ $EN_BIAS1 -eq 1 ] + echo Begin DCE processing... + matlab -nodisplay -r "cd('$ROCKETSHIP_PATH'); addpath '$GPUFIT_PATH'; addpath '$GPUFIT_M_PATH'; run_dce_cli('$DATA_DIR/$SUBJECT/$SESSION/', '$SUBJECT_TP_PATH/'); exit;" + mv dce/dce_*_fit_Ktrans.nii dce/${PREFIX}_Ktrans.nii + mv dce/dce_*_fit_ktrans_ci_low.nii dce/${PREFIX}_Ktrans_ci_low.nii + mv dce/dce_*_fit_ktrans_ci_high.nii dce/${PREFIX}_Ktrans_ci_high.nii + mv dce/dce_*_fit_vp.nii dce/${PREFIX}_vp.nii + mv dce/dce_*_fit_vp_ci_low.nii dce/${PREFIX}_vp_ci_low.nii + mv dce/dce_*_fit_vp_ci_high.nii dce/${PREFIX}_vp_ci_high.nii + mv dce/dce_*_fit_sse.nii dce/${PREFIX}_sse.nii + # move images into figures folder + mv dce/dce*.png figures/ + rm -f dce/*.fig + if [ ! -f "dce/${PREFIX}_Ktrans.nii" ] then - fslmaths 15_masked_seg.nii.gz -thr 2 -uthr 2 -bin 15_gm_mask.nii - fslmaths 15_bfc.nii -mas 15_gm_mask.nii.gz 15_gm.nii - else - fslmaths 15_bfc_seg.nii.gz -thr 2 -uthr 2 -bin 15_gm_mask.nii - fslmaths 15_bfc.nii -mas 15_gm_mask.nii.gz 15_gm.nii + echo $dir "Missing Ktrans maps. DCE failed or inputs were not generated. Hopefully message below is relevant." >> $LOG_FILE + tail -1 dce/A_dceR1info.log >> $LOG_FILE + cd $DATA_DIR/../derivatives + fail=1 + continue fi - # Align then re-binarize gm mask - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii 15_gm.nii.gz 15_gm_mask_dyn.nii.gz - fslmaths 15_gm_mask_dyn.nii.gz -thr 20 -bin 15_gm_mask_dyn.nii + # Analyze results + # ------------------------------ - # Make CSF mask - if [ $EN_BIAS1 -eq 1 ] - then - fslmaths 15_masked_seg.nii.gz -thr 1 -uthr 1 -bin 15_csf_mask.nii - fslmaths 15_bfc.nii -mas 15_csf_mask.nii.gz 15_csf.nii - else - fslmaths 15_bfc_seg.nii.gz -thr 1 -uthr 1 -bin 15_csf_mask.nii - fslmaths 15_bfc.nii -mas 15_csf_mask.nii.gz 15_csf.nii - fi + # Align then re-binarize gm mask + DCEREF_FILE=$(ls dce/${PREFIX}*DCEref.nii.gz | head -n 1) + antsApplyTransforms -i anat/${PREFIX}_label-GM_mask.nii.gz -r $DCEREF_FILE -t anat/${PREFIX}_from-T1w_to-DCEref.mat -o anat/${PREFIX}_space-DCEref_label-GM_mask_pv.nii.gz &> /dev/null + fslmaths anat/${PREFIX}_space-DCEref_label-GM_mask_pv.nii.gz -thr 0.9 -bin anat/${PREFIX}_space-DCEref_label-GM_mask.nii.gz + rm anat/${PREFIX}_space-DCEref_label-GM_mask_pv.nii.gz # Align CSF mask - flirt -in 15_csf.nii.gz -ref ref_rep.nii -out 15_csf_mask_dyn.nii.gz -init t12dcevol.mat -applyxfm - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii 15_csf.nii.gz 15_csf_mask_dyn.nii.gz - fslmaths 15_csf_mask_dyn.nii.gz -thr 20 -bin 15_csf_mask_dyn.nii + antsApplyTransforms -i anat/${PREFIX}_label-CSF_mask.nii.gz -r $DCEREF_FILE -t anat/${PREFIX}_from-T1w_to-DCEref.mat -o anat/${PREFIX}_space-DCEref_label-CSF_mask_pv.nii.gz &> /dev/null + fslmaths anat/${PREFIX}_space-DCEref_label-CSF_mask_pv.nii.gz -thr 0.9 -bin anat/${PREFIX}_space-DCEref_label-CSF_mask.nii.gz + rm anat/${PREFIX}_space-DCEref_label-CSF_mask_pv.nii.gz # Apply masks to T1 map - fslmaths t1_map_fixed_use_me.nii.gz -mas 15_wm_mask_dyn.nii T1_wm.nii - fslmaths t1_map_fixed_use_me.nii.gz -mas 15_gm_mask_dyn.nii T1_gm.nii - fslmaths t1_map_fixed_use_me.nii.gz -mas 15_csf_mask_dyn.nii T1_csf.nii + fslmaths anat/${PREFIX}_space-DCEref_T1map.nii -mas anat/${PREFIX}_space-DCEref_label-WM_mask.nii.gz anat/${PREFIX}_space-DCEref_label-WM_T1map.nii + fslmaths anat/${PREFIX}_space-DCEref_T1map.nii -mas anat/${PREFIX}_space-DCEref_label-GM_mask.nii.gz anat/${PREFIX}_space-DCEref_label-GM_T1map.nii + fslmaths anat/${PREFIX}_space-DCEref_T1map.nii -mas anat/${PREFIX}_space-DCEref_label-CSF_mask.nii.gz anat/${PREFIX}_space-DCEref_label-CSF_T1map.nii # Apply masks to Ktrans map - fslmaths dce_patlak_fit_Ktrans.nii -mas 15_wm_mask_dyn.nii Ktrans_wm.nii - fslmaths dce_patlak_fit_Ktrans.nii -mas 15_gm_mask_dyn.nii Ktrans_gm.nii - fslmaths dce_patlak_fit_Ktrans.nii -mas 15_csf_mask_dyn.nii Ktrans_csf.nii + fslmaths dce/${PREFIX}_Ktrans.nii -mas anat/${PREFIX}_space-DCEref_label-WM_mask.nii.gz dce/${PREFIX}_seg-WM_Ktrans.nii + fslmaths dce/${PREFIX}_Ktrans.nii -mas anat/${PREFIX}_space-DCEref_label-GM_mask.nii.gz dce/${PREFIX}_seg-GM_Ktrans.nii + fslmaths dce/${PREFIX}_Ktrans.nii -mas anat/${PREFIX}_space-DCEref_label-CSF_mask.nii.gz dce/${PREFIX}_seg-CSF_Ktrans.nii - python3 $SCRIPT_PATH/auto_analysis.py $SUBJECT_TP_PATH - python3 $SCRIPT_PATH/report.py $SUBJECT_TP_PATH - cd ../../ - echo $dir processing complete! -done + # registration QC + python3 $SCRIPT_PATH/ktrans_analysis.py $dir $PREFIX + + fslmaths anat/${PREFIX}_space-DCEref_label-WM_mask.nii.gz -add 2000 huh.nii + fslmaths huh.nii.gz -thr 2001 huh.nii + fslmaths $DCEREF_FILE -sub huh.nii.gz bozo.nii + fslmaths bozo.nii -thr 0 anat/${PREFIX}_space-DCEref_label-WMQC.nii.gz + + fslmaths anat/${PREFIX}_space-DCEref_label-GM_mask.nii.gz -add 2000 huh2.nii + fslmaths huh2.nii.gz -thr 2001 huh2.nii + fslmaths $DCEREF_FILE -sub huh2.nii.gz bozo2.nii + fslmaths bozo2.nii -thr 0 anat/${PREFIX}_space-DCEref_label-GMQC.nii.gz + rm huh.nii.gz huh2.nii.gz bozo.nii.gz bozo2.nii.gz + + # wm parcellation with Freesurfer + if [ $USE_FREESURFER -eq 1 ] + then + # get subject ID + subj=$(echo $dir | rev | cut -d'/' -f2 | rev) + + # run Freesurfer + # if [ ! -f $dir/freesurfer/$SUBJECT/$SESSION/mri/wmparc.mgz ] + # then + # mkdir -p $dir/freesurfer/$SUBJECT/$SESSION + # recon-all -s $DATA_DIR -i anat/${PREFIX}_T1w.nii.gz -sd $dir/freesurfer/$SUBJECT/$SESSION -all -parallel -openmp 8 + # fi + + # get wm parcellation + mri_label2vol --seg $DERIV_DIR/freesurfer/$SUBJECT/$SESSION/mri/wmparc.mgz \ + --temp $DERIV_DIR/freesurfer/$SUBJECT/$SESSION/mri/rawavg.mgz \ + --o $DERIV_DIR/freesurfer/$SUBJECT/$SESSION/mri/wmparc-in-rawavg.mgz \ + --regheader $DERIV_DIR/freesurfer/$SUBJECT/$SESSION/mri/wmparc.mgz &> /dev/null + mri_convert $DERIV_DIR/freesurfer/$SUBJECT/$SESSION/mri/wmparc-in-rawavg.mgz wmparc.nii.gz &> /dev/null + antsApplyTransforms -i wmparc.nii.gz -r dce/${PREFIX}_desc-hmc_DCEref.nii.gz -t anat/${PREFIX}_from-t1w_to-DCEref.mat -n NearestNeighbor -o anat/${PREFIX}_space-DCEref_desc-wmparc.nii &> /dev/null + gzip -f anat/${PREFIX}_space-DCEref_desc-wmparc.nii &> /dev/null + rm wmparc.nii.gz + elif [ ! -f dce/${PREFIX}_space-MNI_Ktrans.nii.gz ] + then + # flirt -in DCE_mc.nii.gz -ref $FSLDIR/data/standard/MNI152_T1_1mm.nii.gz -omat DCE2MNI.mat -out DCE_MNI_FSL.nii.gz + # flirt -in $dir/T1.nii -ref $FSLDIR/data/standard/MNI152_T1_1mm.nii.gz -out t1w_MNI.nii.gz + # antsRegistration --verbose 0 --dimensionality 3 --float 0 --collapse-output-transforms 1 \ + # --output [ DCE_MPRAGE,DCE_MPRAGE.nii.gz ] --interpolation Linear --use-histogram-matching 0 \ + # --winsorize-image-intensities [ 0.005,0.995 ] --transform Affine[ 0.1 ] \ + # --metric MI[ T1_bet.nii.gz,DCE_mc.nii.gz,1,32,Regular,0.25 ] \ + # --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + # antsRegistrationSyNQuick.sh -d 3 -f anat/${PREFIX}_desc-brain_T1w.nii.gz -m dce/${PREFIX}_desc-hmc_DCEref.nii.gz -o dce/${PREFIX}_space-MNI_DCEref -n 8 + antsRegistrationSyNQuick.sh -d 3 -f $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz -m anat/${PREFIX}_desc-brain_T1w.nii.gz -o anat/${PREFIX}_space-MNI_T1w -n 8 + # antsApplyTransforms -i dce/${PREFIX}_desc-hmc_DCEref.nii.gz -r $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz -t dce/${PREFIX}_space-MNI_DCEref1Warp.nii.gz -t anat/${PREFIX}_space-MNI_T1w0GenericAffine.mat -t [ anat/${PREFIX}_from-T1w_to-DCEref.mat, 1] -o DCE_mc_MNI.nii.gz + antsApplyTransforms -i dce/${PREFIX}_desc-hmc_DCEref.nii.gz -r $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz -t anat/${PREFIX}_space-MNI_T1w0GenericAffine.mat -t [ anat/${PREFIX}_from-T1w_to-DCEref.mat, 1] -o dce/${PREFIX}_space-MNI_DCEref.nii.gz + # antsRegistration --verbose 1 --dimensionality 3 --float 0 --collapse-output-transforms 1 \ + # --output [ anat/${PREFIX}_space-MNI,anat/${PREFIX}_space-MNI_T1w.nii.gz ] --interpolation Linear --use-histogram-matching 0 \ + # --winsorize-image-intensities [ 0.005,0.995 ] --transform SyN[ 0.1 ] \ + # --metric MI[ $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz,anat/${PREFIX}_space-DCEref_T1w.nii.gz,1,32,Regular,0.25 ] \ + # --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + # antsApplyTransforms -i dce/${PREFIX}_Ktrans.nii -r $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz -t ${PREFIX}_space-MNI_T1w0Warp.nii.gz -t t1w_MNI0GenericAffine.mat -t [T1_dyn0GenericAffine.mat, 1] -o Ktrans_MNI.nii.gz + antsApplyTransforms -i dce/${PREFIX}_Ktrans.nii -r $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz -t anat/${PREFIX}_space-MNI_T1w0GenericAffine.mat -t [ anat/${PREFIX}_from-T1w_to-DCEref.mat, 1] -o dce/${PREFIX}_space-MNI_Ktrans.nii.gz + # antsApplyTransforms -i dce_patlak_fit_vp.nii -r $FSLDIR/data/standard/MNI152_T1_1mm_brain.nii.gz -t t1w_MNI1Warp.nii.gz -t t1w_MNI0GenericAffine.mat -t [T1_dyn0GenericAffine.mat, 1] -o vp_MNI.nii.gz + # flirt -in dce_patlak_fit_Ktrans.nii -ref $FSLDIR/data/standard/MNI152_T1_1mm.nii.gz -out ktrans_2_MNI.nii.gz -init DCE2MNI.mat -applyxfm + else + echo SKIP + fi + mkdir reports &> /dev/null + python3 $SCRIPT_PATH/case_report.py $DATA_DIR/$SUBJECT/$SESSION $PREFIX $USE_FREESURFER + python3 $SCRIPT_PATH/ktrans_report.py $DATA_DIR/$SUBJECT/$SESSION $PREFIX + if [ $PURGE_INTERMEDIATES -eq 1 ] + then + # rm -f !(Ktrans_*|T1_gm*|T1_wm*|T1_csf*|*_patlak_fit*.nii|case_report.html|*_MNI.nii.gz|*fit_VFA.nii|figures|dce*.png|*.log) + cd anat + rm -f !(${PREFIX}*bfczunified_VFA.nii|${PREFIX}_space-DCEref_T1map*|${PREFIX}*label-*_T1map*|*.mat|*wmparc.nii.gz) + cd ../dce + rm -f !(${PREFIX}*Ktrans*|${PREFIX}*bfcz_DCE*|${PREFIX}*AIFincluded*|${PREFIX}*AIF_T1map*|figures|*.log|*.txt|*.par) + elif [ $GIGA_PURGE -eq 1 ] + then + rm -f !(case_report.html|figures|*.log) + fi + cd $DERIV_DIR + echo $dir processing complete! >> $LOG_FILE + ((successes++)) +done # < $INPUT_LIST + +mkdir -p $DERIV_DIR/reports +python3 $SCRIPT_PATH/population_report.py $DERIV_DIR $OUTPUT_DIR $ROCKETSHIP_PATH +((failures=count-successes)) +echo "Completed DCE processing for $count subjects." >> $LOG_FILE +echo $successes subjects succeeded >> $LOG_FILE +echo $failures subjects failed >> $LOG_FILE + +if [ $fail -eq 1 ] + then + exit 1 +fi diff --git a/DCE_norm.py b/DCE_norm.py index 43cc8c9..1bc22cd 100644 --- a/DCE_norm.py +++ b/DCE_norm.py @@ -1,15 +1,18 @@ import sys from pathlib import Path -from statistics import mean, pstdev +from statistics import mean, pstdev, median import numpy as np import matplotlib import matplotlib.pyplot as plt from numpy.polynomial import Polynomial from numpy.polynomial.polynomial import polyval import nibabel as nib +from lmfit import Model + matplotlib.use('Agg') # add as arg? add mask arg? +GAUSSFIT = True POLYFIT = True def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE NORMALIZATION @@ -20,10 +23,16 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N wm_data = wm_mask.get_fdata() mri_shape = mri_data.shape wm_shape = wm_data.shape - slice_num = min(mri_shape[0], mri_shape[1], mri_shape[2], mri_shape[3]) - slice_loc = mri_shape.index(slice_num) - mri_data = np.reshape(mri_data, (mri_shape[min(dim-set([slice_loc]))], mri_shape[max(dim-set([slice_loc]))], slice_num, 64)) - wm_data = np.reshape(wm_data, (wm_shape[min(dim-set([slice_loc]))], wm_shape[max(dim-set([slice_loc]))], slice_num, 64)) + slice_num = min(mri_shape[0], mri_shape[1], mri_shape[2]) + axis_order = np.argsort([mri_shape[0], mri_shape[1], mri_shape[2]]) + # Unpack the axis order + # usual shape should be x >= y > z + z_index, y_index, x_index = axis_order + # if x and y are the same, swap them + if mri_shape[x_index] == mri_shape[y_index]: + x_index, y_index = y_index, x_index + mri_data = np.transpose(mri_data, (x_index, y_index, z_index, 3)) + wm_data = np.transpose(wm_data, (x_index, y_index, z_index, 3)) wm_mean = [] orig_img = [] @@ -49,8 +58,100 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N mri_final = mri_data wm_final = wm_data - # apply normalizations - if POLYFIT is True: + gaussian_params = [0 for i in range(slice_num)] + if GAUSSFIT: + startat = 0 + endat = slice_num + hadSuccess = False + # get histogram of each wm slice + for i in range(slice_num): + a = np.where(wm_data[:, :, i, :] > 0) + hist, bins = np.histogram(wm_data[:,:,i,:][a].flatten(), bins=100) + + def double_gaussian(x, A1, mu1, sigma1, A2, mu2, sigma2): + return ( + A1 * np.exp(-0.5 * ((x - mu1) / sigma1) ** 2) + + A2 * np.exp(-0.5 * ((x - mu2) / sigma2) ** 2) + ) + + # Initial guess for the parameters + # count voxels within 1 std of mean + area = np.count_nonzero(wm_data[:,:,i,:][a]) + + # prepare for gaussian fitting + bin_width = bins[1] - bins[0] + model = Model(double_gaussian) + amp_guess = 0 + params = None + # check if slice is empty + if area == 0: + # just get from next slice + # if i == slice_num - 1: + # a = np.where(wm_data[:,:,i-1,:] > 0) + # area = np.count_nonzero(wm_data[:,:,i-1,:][a]) + # amp_guess = area / pstdev(wm_data[:,:,i-1,:][a]) * 0.3989 * bin_width + # params = model.make_params(A1=amp_guess*0.1, mu1=wm_mean[i-1], sigma1=pstdev(wm_data[:,:,i-1,:][a]), A2=amp_guess, mu2=median(wm_data[:,:,i-1,:][a]), sigma2=pstdev(wm_data[:,:,i-1,:][a])) + # else: + # a = np.where(wm_data[:,:,i+1,:] > 0) + # area = np.count_nonzero(wm_data[:,:,i+1,:][a]) + # amp_guess = area / pstdev(wm_data[:,:,i+1,:][a]) * 0.3989 * bin_width + # params = model.make_params(A1=amp_guess*0.1, mu1=wm_mean[i+1], sigma1=pstdev(wm_data[:,:,i+1,:][a]), A2=amp_guess, mu2=median(wm_data[:,:,i+1,:][a]), sigma2=pstdev(wm_data[:,:,i+1,:][a])) + if hadSuccess and i < endat: + endat = i + elif not hadSuccess: + startat = i + 1 + continue + else: + hadSuccess = True + amp_guess = area / pstdev(wm_data[:,:,i,:][a]) * 0.3989 * bin_width + params = model.make_params(A1=amp_guess*0.1, mu1=wm_mean[i], sigma1=pstdev(wm_data[:,:,i,:][a]), A2=amp_guess, mu2=median(wm_data[:,:,i,:][a]), sigma2=pstdev(wm_data[:,:,i,:][a])) + result = model.fit(hist, params, x=bins[:-1]) + gaussian_params[i] = result.best_values + + # Plot the histogram + plt.figure() + plt.bar(bins[:-1], hist, width=np.diff(bins), align='edge', alpha=0.5) + + # Plot the fitted curve + plt.plot(bins[:-1], result.best_fit, color='red', linewidth=2) + + # Add labels and title + plt.title(f"Histogram and Fitted Curve - Slice {i+1}") + plt.xlabel("Pixel Value") + plt.ylabel("Frequency") + + # make hist directory if it doesn't exist + hist_dir = file_dir + '/../figures/hist' + Path(hist_dir).mkdir(parents=True, exist_ok=True) + + # Save the figure + path1 = file_dir + '/../figures/hist/DCE_' + str(i+1) + '_hist.png' + plt.savefig(path1) + plt.close() + + # apply normalizations + print("Using Gaussian fitting to normalize DCE") + mu = [0 for i in range(slice_num)] + for i in range(startat, endat): + if gaussian_params[i]['A1'] > gaussian_params[i]['A2'] and gaussian_params[i]['mu1'] > 0 and gaussian_params[i]['mu1'] > gaussian_params[i]['mu2'] and gaussian_params[i]['mu1'] < 1500: + mu[i] = gaussian_params[i]['mu1'] + else: + mu[i] = gaussian_params[i]['mu2'] + # print("mu " + str(mu[i])) + # print("SLICE: " + str(i + 1)) + # print("mu: " + str(gaussian_params[i]['mu1']) + " " + str(gaussian_params[i]['mu2'])) + # print("A: " + str(gaussian_params[i]['A1']) + " " + str(gaussian_params[i]['A2'])) + # print("sigma: " + str(gaussian_params[i]['sigma1']) + " " + str(gaussian_params[i]['sigma2'])) + median_mu = median([m for m in mu if m != 0]) + for i in range(slice_num): + if mu[i] > 0: + scale_factor = median_mu / mu[i] + else: + scale_factor = 1 # or some other default value + mri_final[:, :, i, :] *= scale_factor + wm_final[:, :, i, :] *= scale_factor + + elif POLYFIT is True: print("Using Polynomial fitting to normalize " + mri_file1) poly_norm_curve = Polynomial.fit(list(range(slice_num)), wm_mean, 4, w=polyfit_slice_weights) norm_slices = polyval(list(range(slice_num)), poly_norm_curve.convert().coef) @@ -64,9 +165,9 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N else: print("Using Z-normalization") # calc stats of all slices - data_mean = mean(wm_mean[1:13]) - std_dev = pstdev(wm_mean[1:13]) - err = 1*std_dev + data_mean = mean(wm_mean[0:slice_num-1]) + std_dev = pstdev(wm_mean[0:slice_num-1]) + err = .5*std_dev # find slices out of range min_val = data_mean - err max_val = data_mean + err @@ -96,36 +197,60 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N else: norm_wm.append(0) - data_mean1 = mean(norm_img) - fig, ax = plt.subplots(figsize=(20, 6)) ax.plot(range(slice_num), wm_mean, '-ok', label='original') - if POLYFIT is True: + if POLYFIT is True and GAUSSFIT is False: ax.plot(range(slice_num), norm_slices, ':ob', label='fit') - ax.plot(range(slice_num), norm_wm, '--og', label='corrected') + if GAUSSFIT: + ax.plot(range(slice_num), mu, 'o', label='mu', color='grey') + ax.plot(range(slice_num), np.ones(slice_num)*median_mu, ':x', label='median_mu', color='lightgreen') + ax.plot(range(slice_num), norm_wm, '--xg', label='corrected') ax.set_xlabel('Slice #') ax.set_ylabel('White Matter Mean') ax.set_title("DCE Slice Normalization") ax.legend() - path2 = file_dir +'/DCE_mc_bfc_norm.png' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE GRAPHS GENERATED - plt.savefig(path2) - # print(file_dir) - mri_final = np.reshape(mri_final, mri_shape) + path2 = 'figures/' + mri_file1.split('desc-bfc_DCE')[0].split('/')[-1] + 'desc-bfcz_DCE.svg' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE GRAPHS GENERATED + plt.savefig(path2, bbox_inches='tight') + plt.close() + + # transpose mri_final back to mri_shape order + x_index = mri_final.shape.index(mri_shape[0]) + y_index = mri_final.shape.index(mri_shape[1]) + z_index = mri_final.shape.index(mri_shape[2]) + if x_index == y_index: + y_index = set(dim) - {x_index, z_index} + y_index = list(y_index)[0] + elif x_index == z_index: + z_index = set(dim) - {x_index, y_index} + z_index = list(z_index)[0] + elif y_index == z_index: + z_index = set(dim) - {x_index, y_index} + z_index = list(z_index)[0] + + mri_final = np.transpose(mri_final, (x_index, y_index, z_index, 3)) + mri_final = mri_final.astype(np.float32) final_img = nib.Nifti1Image(mri_final, mri.affine) - path3 = file_dir + '/DCE_mc_bfc_norm.nii' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE NORMALIZED NIFTI IMAGE GENERATED + path3 = mri_file1.split('desc-bfc_DCE')[0] + 'desc-bfcz_DCE.nii' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE NORMALIZED NIFTI IMAGE GENERATED nib.save(final_img, path3) dir = Path(sys.argv[1]) # takes timepoint directory as argument files_in_dir = dir.iterdir() for file in files_in_dir: - if str(file).endswith('mc_bfc.nii') or str(file).endswith('mc_bfc.nii.gz'): + if str(file).endswith('desc-bfc_DCE.nii') or str(file).endswith('desc-bfc_DCE.nii.gz'): file1 = str(file) - mask_file = file1.split('.', 1) - mask_file = mask_file[0] + '_wm.nii' + mask_file = file1.split('desc-bfc_DCE', 1)[0] + mask_file = mask_file + 'seg-WM_DCE.nii' + + if Path(mask_file + ".gz").exists(): + mask_file += ".gz" + elif not Path(mask_file).exists(): + print(f"Mask file not found for {file1}") + continue try: normalize(file1, mask_file, str(dir)) - except FileNotFoundError: - normalize(file1, mask_file + ".gz", str(dir)) + except Exception as e: + print(e) + print("Error in normalizing " + file1) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d513a55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,253 @@ +# syntax=docker/dockerfile:1 + +# cannot do 24.04 because of freesurfer (libc6 << 2.36) +FROM nvidia/cuda:13.0.0-cudnn-devel-ubuntu22.04 + +ENV DEBIAN_FRONTEND="noninteractive" \ + LANG="en_US.UTF-8" \ + LC_ALL="en_US.UTF-8" + +# # modified from fmriprep (https://github.com/nipreps/fmriprep/blob/master/Dockerfile) +# # Prepare environment +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + apt-utils \ + autoconf \ + build-essential \ + bzip2 \ + ca-certificates \ + curl \ + wget \ + sudo \ + git \ + libasound2 \ + libcairo-gobject2 libcairo2 libcap2 libcrypt1 libcrypt-dev \ + libcups2 libdbus-1-3 libdrm2 libfontconfig1 libgbm1 libgdk-pixbuf2.0-0 libgl1 libglib2.0-0 \ + libgomp1 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 libgtk-3-0 libnspr4 libnss3 libodbc1 \ + libpam0g libpango-1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 \ + libsm6 libsndfile1 \ + libuuid1 libx11-6 libx11-xcb1 libxcb-dri3-0 libxcb1 libxcomposite1 libxcursor1 libxdamage1 \ + libxext6 libxfixes3 libxft2 libxi6 libxinerama1 libxrandr2 libxrender1 libxt6 libxtst6 libxxf86vm1 \ + linux-libc-dev \ + make net-tools procps zlib1g \ + bc \ + dc \ + file \ + libfontconfig1 \ + libfreetype6 \ + libgl1-mesa-dev \ + libgl1-mesa-dri \ + libglu1-mesa-dev \ + libgomp1 \ + libice6 \ + libxcursor1 \ + libxft2 \ + libxinerama1 \ + libxrandr2 \ + libxrender1 \ + libxt6 \ + libtool \ + lsb-release \ + netbase \ + pkg-config \ + python3-pip \ + unzip \ + xvfb \ + language-pack-en \ + libx11-dev \ + gettext \ + xterm \ + x11-apps \ + libncurses5 \ + libegl1 \ + csh \ + tcsh \ + xorg \ + xorg-dev \ + xserver-xorg-dev \ + xserver-xorg-video-intel \ + libjpeg62 \ + libpcre2-16-0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-shape0 \ + libxcb-util1 \ + libxcb-xinerama0 \ + libxcb-xinput0 \ + libxcb-xkb1 \ + libxkbcommon-x11-0 \ + libxss1 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Simulate SetUpFreeSurfer.sh +ENV FSL_DIR="/opt/fsl" \ + OS="Linux" \ + FS_OVERRIDE=0 \ + FIX_VERTEX_AREA="" \ + FSF_OUTPUT_FORMAT="nii.gz" \ + FREESURFER_HOME="/usr/local/freesurfer/8.1.0" +ENV SUBJECTS_DIR="$FREESURFER_HOME/subjects" \ + FUNCTIONALS_DIR="$FREESURFER_HOME/sessions" \ + MNI_DIR="$FREESURFER_HOME/mni" \ + LOCAL_DIR="$FREESURFER_HOME/local" \ + MINC_BIN_DIR="$FREESURFER_HOME/mni/bin" \ + MINC_LIB_DIR="$FREESURFER_HOME/mni/lib" \ + MNI_DATAPATH="$FREESURFER_HOME/mni/data" +ENV PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \ + MNI_PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \ + PATH="$FREESURFER_HOME/bin:$FREESURFER_HOME/tktools:$MINC_BIN_DIR:$PATH" + +# Installing freesurfer +COPY docker/files/freesurfer-exclude.txt /usr/local/etc/freesurfer-exclude.txt +RUN curl -sSL https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/8.1.0/freesurfer_ubuntu22-8.1.0_amd64.deb -o /tmp/freesurfer.deb \ + && dpkg -i /tmp/freesurfer.deb \ + && rm -f /tmp/freesurfer.deb \ + && ./${FREESURFER_HOME}/SetUpFreeSurfer.sh \ + && rm -rf /usr/local/freesurfer/8.1.0/trctrain \ + && rm -rf /usr/local/freesurfer/8.1.0/python \ + && rm -rf /usr/local/freesurfer/8.1.0/subjects \ + && rm -rf /usr/local/freesurfer/8.1.0/average \ + && rm -rf /usr/local/freesurfer/8.1.0/models \ + && if [ -f /usr/local/etc/freesurfer-exclude.txt ]; then \ + cd /usr/local/freesurfer/8.1.0 && \ + grep -v '^#' /usr/local/etc/freesurfer-exclude.txt | xargs -r rm -rf; \ + fi + +# FSL +ENV FSLDIR="/opt/fsl" \ + PATH="/opt/fsl/bin:$PATH" \ + FSLOUTPUTTYPE="NIFTI_GZ" \ + FSLMULTIFILEQUIT="TRUE" \ + FSLLOCKDIR="" \ + FSLMACHINELIST="" \ + FSLREMOTECALL="" \ + FSLGECUDAQ="cuda.q" \ + LD_LIBRARY_PATH="/opt/fsl/lib:$LD_LIBRARY_PATH" + +RUN apt-get update -qq \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && echo "Downloading FSL ..." \ + && curl -Ls https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/releases/getfsl.sh | sh -s \ + && rm -rf /opt/fsl/data/atlases \ + /opt/fsl/data/first \ + /opt/fsl/data/mist \ + /opt/fsl/data/possum \ + /opt/fsl/data/standard/bianca \ + /opt/fsl/data/standard/tissuepriors \ + /opt/fsl/doc \ + /opt/fsl/etc/default_flobs.flobs \ + /opt/fsl/etc/fslconf \ + /opt/fsl/etc/js \ + /opt/fsl/etc/luts \ + /opt/fsl/etc/matlab \ + /opt/fsl/extras \ + /opt/fsl/include \ + /opt/fsl/python \ + /opt/fsl/refdoc \ + /opt/fsl/tcl \ + /opt/fsl/bin/FSLeyes + +# Installing ANTs 2.3.3 (NeuroDocker build) +# Note: the URL says 2.3.4 but it is actually 2.3.3 +ENV ANTSPATH="/opt/ants/bin" +ENV PATH="${ANTSPATH}:$PATH" +WORKDIR /opt/ants +RUN curl -sSL "https://github.com/ANTsX/ANTs/releases/download/v2.6.2/ants-2.6.2-ubuntu-22.04-X64-gcc.zip" -o ants.zip \ + && unzip ants.zip \ + && mv ants-2.6.2/* . \ + && rmdir ants-2.6.2 \ + && rm ants.zip + +WORKDIR / +# GPUFIT +RUN curl -sSLO "https://github.com/ironictoo/Gpufit/releases/download/1.3/Gpufit_1.3.0_linux.zip" \ + && unzip -d /opt/Gpufit Gpufit*.zip && rm /Gpufit*.zip && mv /opt/Gpufit/Gpufit_1.2.0/* /opt/Gpufit/ + # && rmdir /opt/Gpufit/Gpufit_1.3.0_linux +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/Gpufit/matlab64/matlab +ENV GPUFIT_PATH="/opt/Gpufit/matlab64" + +# HD-BET +RUN curl -sSLO "https://github.com/MIC-DKFZ/HD-BET/archive/refs/heads/master.zip" \ + && unzip -d /opt/HD-BET *master.zip && mv /opt/HD-BET/HD-BET-master/* /opt/HD-BET && rm -rf /opt/HD-BET/HD-BET-master && cd /opt/HD-BET && pip install --no-cache-dir -e . \ + && sed -i 's/~/\//' /opt/HD-BET/HD_BET/paths.py \ + && mkdir -p /hd-bet_params/release_2.0.0 \ + && chmod -R 777 /hd-bet_params/release_2.0.0 \ + && cd /hd-bet_params/release_2.0.0 \ + && curl -sSLo /hd-bet_params/release_v1.5.0.zip "https://zenodo.org/records/14445620/files/release_v1.5.0.zip?download=1" \ + && unzip /hd-bet_params/release_v1.5.0.zip -d /hd-bet_params/release_2.0.0 \ + && rm /hd-bet_params/release_v1.5.0.zip && chmod -R 777 /hd-bet_params/release_2.0.0 + +# AUTO AIF +RUN git clone --depth 1 https://github.com/petmri/vascular_function.git /opt/vascular_function \ + && cd /opt/vascular_function \ + && grep -vE '^(cupy|tensorrt_cu12)' requirements.txt > filtered-requirements.txt \ + && pip install --no-cache-dir -r filtered-requirements.txt \ + && rm filtered-requirements.txt + +# ROCKETSHIP +RUN curl -sSLO "https://github.com/petmri/ROCKETSHIP/archive/refs/heads/dev.zip" \ + && unzip -d /opt/ROCKETSHIP *dev.zip && rm /dev.zip + +# MATLAB +# Install patched glibc - See https://github.com/mathworks/build-glibc-bz-19329-patch +# Note: base-dependencies.txt includes libcrypt-dev and linux-libc-dev to enable installation of patched -dev packages +# WORKDIR /packages +# RUN apt-get update && apt-get clean && apt-get autoremove && \ +# wget -q https://github.com/mathworks/build-glibc-bz-19329-patch/releases/download/ubuntu-focal/all-packages.tar.gz && \ +# tar -x -f all-packages.tar.gz \ +# --exclude glibc-*.deb \ +# --exclude libc6-dbg*.deb +# RUN apt-get install ./*.deb && \ +# rm -fr /packages +# WORKDIR / + +# Copyright 2019 - 2022 The MathWorks, Inc. + +# To specify which MATLAB release to install in the container, edit the value of the MATLAB_RELEASE argument. +# Use lower case to specify the release, for example: ARG MATLAB_RELEASE=r2021b +ARG MATLAB_RELEASE=r2023a + +# When you start the build stage, this Dockerfile by default uses the Ubuntu-based matlab-deps image. +# To check the available matlab-deps images, see: https://hub.docker.com/r/mathworks/matlab-deps +# FROM mathworks/matlab-deps:${MATLAB_RELEASE} + +# Declare the global argument to use at the current build stage +ARG MATLAB_RELEASE + +# Install mpm dependencies & tini +RUN apt-get update \ + && apt-get install --no-install-recommends --yes \ + tini \ + && apt-get clean \ + && apt-get autoremove \ + && rm -rf /var/lib/apt/lists/* + +# Run mpm to install MATLAB in the target location and delete the mpm installation afterwards. +# If mpm fails to install successfully then output the logfile to the terminal, otherwise cleanup. +RUN wget -q https://www.mathworks.com/mpm/glnxa64/mpm \ + && chmod +x mpm \ + && ./mpm install \ + --release=${MATLAB_RELEASE} \ + --destination=/opt/matlab \ + --products MATLAB Curve_Fitting_Toolbox Parallel_Computing_Toolbox Statistics_and_Machine_Learning_Toolbox \ + Image_Processing_Toolbox Optimization_Toolbox \ + || (echo "MPM Installation Failure. See below for more information:" && cat /tmp/mathworks_root.log && false) \ + && rm -f mpm /tmp/mathworks_root.log \ + && ln -s /opt/matlab/bin/matlab /usr/local/bin/matlab \ + && rm /opt/matlab/sys/os/glnxa64/libstdc++* +ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/matlab/extern/bin/glnxa64:/opt/matlab/sys/os/glnxa64 + +# Add "matlab" user and grant sudo permission. +RUN adduser --shell /bin/bash --disabled-password --gecos "" matlab \ + && echo "matlab ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/matlab \ + && chmod 0440 /etc/sudoers.d/matlab + +# REST OF THIS +COPY . . +ENV MPLCONFIGDIR="/matplotlib" + +RUN python3 -m pip install --no-cache-dir -r requirements.txt && python3 -m pip cache purge && \ + rm -f *.zip diff --git a/README.md b/README.md index 05e213b..d087ec2 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,238 @@ -# in-house_toolbox -The `main` branch is stable. Checkout a tag if you want something super stable. -# Requires FSL, AFNI, Matlab, ROCKETSHIP + parametric_scripts, Freesurfer, and Python. -Used FSL 6.0, AFNI_20.0.09 'Galba', freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.0-2beb96c, Python 3.8.10 +# DCEprep + +A preprocessing and analysis pipeline for Dynamic Contrast-Enhanced (DCE) MRI data. Handles VFA-based T1 mapping, bias field correction, z-axis normalization, motion correction, AIF selection, and Ktrans parameter mapping, with automated QC reporting. + +![Overview of DCEprep processing steps](overview.png) + +#### If you use this software in your research, please cite: + +> Barnes S, et al. Automated DCE-MRI processing with DCEPrep for Blood-Brain Barrier permeability in a multi-site aging study. *Pending*. 2026. + +--- + +## Table of Contents + +- [Installation](#installation) + - [Docker (Recommended)](#docker-recommended-easy-consistent-18-gb) + - [Without Docker](#without-docker) + - [Dependencies](#dependencies-without-docker) +- [Data Organization](#data-organization) +- [Pipeline Structure](#pipeline-structure) + - [Preprocessing: preprocess_all.sh](#preprocessing-preprocess_allsh) + - [Analysis: DCE_all.sh](#analysis-dce_allsh) + +--- + +## Installation + +Clone the repository. The `main` branch is stable. Check out a specific tag for a pinned release. + +```bash +git clone https://github.com/petmri/DCEPrep.git +``` + +### Docker (Recommended, easy, consistent, ~18 GB) + +The easiest way to get started is with the included `run_docker.sh` script, which will automatically pull the Docker image and launch a container. + +> **Important:** Make sure the following are shared (mounted) with the Docker container: +> - MATLAB license file +> - FreeSurfer license file +> - Your data directory +> - Script preference folder (`docker/files/`) +> - `/etc/` +> +> Edit line 24 of `run_docker.sh` to set your data directory before running. + +```bash +./run_docker.sh +``` + +To pull the image manually: + +```bash +docker pull lsaca05/dce:- +# Example: +docker pull lsaca05/dce:R2022a-dev +``` + +See [Docker Hub tags](https://hub.docker.com/repository/docker/lsaca05/dce/tags) for available releases. + +### Without Docker + +Setup a Python virtual environment and install dependencies from `venv_requirements.txt`: + +```bash +cd DCEPrep +python3 -m venv tf +source tf/bin/activate +pip install -r venv_requirements.txt +``` + +`conda` is also supported — install packages from `venv_requirements.txt` into your conda environment. + +#### Dependencies (Without Docker) +If running without Docker, in addition to the Python packages in `venv_requirements.txt`, the following software must be installed and accessible in your system's PATH: +| Dependency | Version / Notes | +|---|---| +| FSL | 6.0 | +| ANTs | 2.6.2 | +| FreeSurfer | Linux-centos6_x86_64-stable-pub-v6.0.0-2beb96c (wm parcellation) | +| MATLAB | R2023a | +| Python | 3.8.10 or 3.10 | +| ROCKETSHIP + parametric_scripts | 1.2 | + +--- + +## Data Organization + +All data is assumed to be [BIDS](https://bids-specification.readthedocs.io/) compliant. + +--- + ## Pipeline Structure -### `preprocess_all` -Inputs: `2.nii` `5.nii` `10.nii` `12.nii` `15.nii` `DCE.nii` `aif.nii` -Outputs: `DCE_mc_bfc_norm.nii`, a bunch of others +### Preprocessing: `preprocess_all.sh` + +#### Inputs + +| File | Pattern | +|---|---| +| VFA images (any number of flip angles) | `sub-##_ses-##_flip-##_VFA.nii.gz` | +| DCE image | `sub-##_ses-##_DCE.nii.gz` | +| T1-weighted image | `sub-##_ses-##_T1w.nii.gz` | + +#### Main Outputs + +| File | +|---| +| `dce/sub-##_ses-##_desc-bfcz_DCE.nii.gz` | +| `anat/sub-##_ses-##_space-DCEref_T1map.nii` | +| `anat/sub-##_ses-##_space-DCEref_VFA.nii.gz` | +| `dce/sub-##_ses-##_desc-AIF_T1map.nii.gz` | +| `anat/sub-##_ses-##_space-DCEref_desc-brain_mask.nii.gz` | + +#### Options + +| Flag | Description | +|---|---| +| `-d [rawdata_path]` | **Required.** Path to your BIDS raw data folder. | +| `-a [suffix]` | AIF suffix (default: `desc-AIF_mask`). `.nii.gz` is appended automatically. | +| `-A [mode]` | Enable AutoAIF: `A` (fully automatic), `M` (manual if available), or `T` (manual + training if available). Requires the [vascular_function repo and weights](https://github.com/petmri/vascular_function). | +| `-b` | Enable first round of bias field correction. | +| `-B` | Enable second round of bias field correction (post-Z-norm, if enabled). | +| `-c` | Clean the case's derivative folder before processing. Ensures fresh runs but disables skips. | +| `-C [name]` | Enable comparison mode. Outputs all files to a named directory within each timepoint. Useful for comparing runs (e.g., no corrections vs. corrections). | +| `-m` | Enable motion correction. | +| `-s` | Skip preprocessing if DCE input file already exists. | +| `-t` | Only run up to T1 mapping. | +| `-T [dir_path]` | Target specific subject(s)/session(s) (default: `sub-*/ses-*/`). | +| `-w [path]` | Path to AutoAIF weights file. | +| `-Z` | Enable z-slice normalization. | + +#### Example + +```bash +./preprocess_all.sh -d /media/network_mriphysics/USC-PPG/bids_test/rawdata -b -c -Z -A -C noMC +``` + +#### Step Summary + +1. **Brain Extraction** of T1w MPRAGE using `HD-BET` with default weights (does not brain mask VFAs). +2.
+ DCE Motion Correction — FSL mcflirt, targeting 2nd frame with mutual info cost function + mcflirt -in $source_dir/dce/${PREFIX}_DCE.nii.gz -refvol 1 -cost mutualinfo -report -plots -o dce/${PREFIX}_desc-hmc_DCE.nii +
+3.
+ MPRAGE → DCE Registration — ANTs antsRegistration + + antsRegistration --verbose 0 --dimensionality 3 --float 0 + --collapse-output-transforms 1 --output [ anat/${PREFIX}_${REF_SPACE}_T1w,anat/${PREFIX}_${REF_SPACE}_T1w.nii.gz ] + --interpolation Linear --use-histogram-matching 0 --winsorize-image-intensities [ 0.005,0.995 ] + --transform Rigid[ 0.1 ] --metric MI[ $DCE_REF_VOL,${source_dir}/anat/${PREFIX}_T1w.nii.gz,1,32,Regular,0.25 ] + --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + +
+4.
+ VFA → DCE Registration — ANTs antsRegistration + + antsRegistration --verbose 0 --dimensionality 3 --float 0 + --collapse-output-transforms 1 --output [ anat/${PREFIX}_flip-${VFA}_${REF_SPACE},anat/${PREFIX}_flip-${VFA}_${REF_SPACE}_VFA.nii.gz ] + --interpolation Linear --use-histogram-matching 0 --winsorize-image-intensities [ 0.005,0.995 ] + --transform Rigid[ 0.1 ] --metric MI[ $DCE_REF_VOL,$source_dir/anat/${PREFIX}_flip-${VFA}_VFA.nii.gz,1,32,Regular,0.25 ] + --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + +
+5.
+ MPRAGE White Matter Segmentation — FSL fast + + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b --nopve -g -o anat/${PREFIX}_label- anat/${PREFIX}_desc-brain_T1w.nii.gz + +
+6. **Apply MPRAGE → DCE transform to WM mask** — ANTs `antsApplyTransforms` +7.
+ VFA Bias Field Correction — FSL fast + + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -B --nopve -o anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-brain_VFA.nii.gz + +
+8. **VFA Z-axis Normalization** — double Gaussian fitting (`VFA_norm.py`) +9. **Second VFA Bias Field Correction** — FSL `fast` +10. **T1 Map Generation** — ROCKETSHIP +11. **Apply MPRAGE → DCE transform to brain mask** — ANTs `antsApplyTransforms` +12. **AIF Drawing via Neural Network** — ensures AIF is included within the brain mask +13.
+ DCE Bias Field Correction — averages bias fields from the 1st + 8 evenly spaced temporal samples via FSL fast + + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b --nopve -o rep_$((rep_interval*i-1)).nii + +
+14. **DCE Z-axis Normalization** — double Gaussian fitting (`DCE_norm.py`) + +--- -Options: `-d: specify main data directory (required)` +### Analysis: `DCE_all.sh` -`-b: enable first round of bias field correction` +#### Inputs -`-B: enable second round of bias field correction` +| File | +|---| +| `dce/sub-##_ses-##_desc-bfcz_DCE.nii.gz` | +| `anat/sub-##_ses-##_space-DCEref_T1map.nii` | +| `anat/sub-##_ses-##_space-DCEref_VFA.nii.gz` | +| `dce/sub-##_ses-##_desc-AIF_T1map.nii.gz` | +| `anat/sub-##_ses-##_space-DCEref_desc-brain_mask.nii.gz` | -`-Z: enable z-slice normalization` +#### Main Outputs -`-c: clean timepoint directory prior to preprocessing (rm all except inputs)` +| File | +|---| +| `sub-##_ses-##_Ktrans.nii` | +| `sub-##_ses-##_vp.nii` | +| `case_report.html` | +| `population_report.html` | -Example call: `./preprocess_all -d /media/network_mriphysics/USC-PPG/data -b -Z -F -c` -1. **Brain Extraction** using FSL `bet` (does not brain mask VFAs). -2. **Bias Field Correction** using FSL `FAST` -3. **VFA Z-axis Normalization** using polynomial fitting `VFA_norm.py` -4. Second round of **Bias Field Correction** using FSL `FAST` -5. **VFA Motion Correction** using FSL `mcflirt` -6. **Generate T1 maps** with ROCKETSHIP -7. **DCE Motion Correction** using FSL `mcflirt` -8. Register T1 map with motion corrected DCE series using Freesurfer -9. Register brain mask to DCE space using Freesurfer, include AIF region, and use to mask motion corrected DCE series -10. **Bias Field Correction** of DCE series by taking 8 z-slice samples and averaging their bias fields generated by FSL `fast` -11. Register white matter mask to dynamic space and apply to bias field corrected DCE series -12. **DCE Z-axis Normalization** using polynomial fitting `DCE_norm.py` +#### Options -### `DCE_all.sh` -Inputs: `DCE_bfc_norm.nii` `aif.nii` `t1_map_fixed_use_me.nii.gz` +| Flag | Description | +|---|---| +| `-d [path]` | **Required.** Path to raw BIDS data directory. | +| `-C [name]` | Enable comparison mode. Copies essential files from a standard run if a preprocessed run of the same name does not exist. | +| `-f` | Enable FreeSurfer WM parcellation for subregion analysis. | +| `-s` | Skip cases already processed. | +| `-S` | Enable smoothing of DCE input. | +| `-T [dir_path]` | Target specific subject(s)/session(s) (default: `sub-*/ses-*/`). | -Outputs: Ktrans maps, QC reports (overview being `report.png`) +#### Example -Options: `-d: specify main data directory (required)` +```bash +./DCE_all.sh -d /media/network_mriphysics/USC-PPG/bids_test/rawdata -s -C noMC +``` -`-b: enable first round of bias field correction` (do this if you did it before) +#### Step Summary -Example call: `./DCE_all -d /media/network_mriphysics/USC-PPG/data -b` -1. Run DCE (ROCKETSHIP) -2. Create, align, and apply gray matter and CSF masks -3. Run QC scripts `auto_analysis.py` and `report.py` +1. **Ktrans Mapping** — ROCKETSHIP +2. **Gray Matter & CSF Masking** — create, align, and apply masks with `antsApplyTransforms` and `fslmaths` +3. **QC Analysis** — run `ktrans_analysis.py` and `ktrans_report.py` +4. **Case Report** — generate `case_report.html` for each case via `case_report.py` +5. **Population Report** — after all cases finish, generate `population_report.html` via `population_report.py` diff --git a/VFA_norm.py b/VFA_norm.py index 1c66007..6896816 100644 --- a/VFA_norm.py +++ b/VFA_norm.py @@ -1,32 +1,43 @@ import sys from pathlib import Path import re -from statistics import mean, pstdev +from statistics import mean, pstdev, median import numpy as np # import numpy.polynomial.polynomial as poly import matplotlib import matplotlib.pyplot as plt +import multiprocessing from numpy.polynomial import Polynomial from numpy.polynomial.polynomial import polyval import nibabel as nib +from scipy.optimize import curve_fit +from lmfit import Model matplotlib.use('Agg') # add as arg? add mask arg? +GAUSSFIT = True POLYFIT = True def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE NORMALIZATION dim = {0, 1, 2} mri = nib.load(mri_file1) white_matter = nib.load(wm_masked) - num = int(re.search(r'\d+', mri_file1.split('/')[-1]).group()) + num = int(re.search(r'flip-(\d+)', mri_file1.split('/')[-1]).group(1)) + num = f"{num:02d}" mri_data = mri.get_fdata() wm_data = white_matter.get_fdata() mri_shape = mri_data.shape wm_shape = wm_data.shape slice_num = min(mri_shape[0], mri_shape[1], mri_shape[2]) - slice_loc = mri_shape.index(slice_num) - mri_data = np.reshape(mri_data, (mri_shape[min(dim-set([slice_loc]))], mri_shape[max(dim-set([slice_loc]))], slice_num)) - wm_data = np.reshape(wm_data, (wm_shape[min(dim-set([slice_loc]))], wm_shape[max(dim-set([slice_loc]))], slice_num)) + axis_order = np.argsort([mri_shape[0], mri_shape[1], mri_shape[2]]) + # Unpack the axis order + # usual shape should be x >= y > z + z_index, y_index, x_index = axis_order + # if x and y are the same, swap them + if mri_shape[x_index] == mri_shape[y_index]: + x_index, y_index = y_index, x_index + mri_data = np.transpose(mri_data, (x_index, y_index, z_index)) + wm_data = np.transpose(wm_data, (x_index, y_index, z_index)) wm_mean = [] orig_img = [] @@ -51,8 +62,121 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N mri_final = mri_data wm_final = wm_data - # apply normalizations - if POLYFIT is True: + gaussian_params = [0 for i in range(slice_num)] + if GAUSSFIT: + startat = 0 + endat = slice_num + hadSuccess = False + # get histogram of each wm slice + for i in range(slice_num): + # print("SLICE: " + str(i + 1)) + a = np.where(wm_data[:, :, i] > 0) + hist, bins = np.histogram(wm_data[:,:,i][a].flatten(), bins=100) + + def double_gaussian(x, A1, mu1, sigma1, A2, mu2, sigma2): + return ( + A1 * np.exp(-0.5 * ((x - mu1) / sigma1) ** 2) + + A2 * np.exp(-0.5 * ((x - mu2) / sigma2) ** 2) + ) + + # Initial guess for the parameters + # count voxels within 1 std of mean + area = np.count_nonzero(wm_data[:,:,i][a]) + + # prepare for gaussian fitting + bin_width = bins[1] - bins[0] + model = Model(double_gaussian) + amp_guess = 0 + params = 0 + # check if slice is empty + if area == 0 or pstdev(wm_data[:,:,i][a]) == 0: + # just get from next slice + # if i == slice_num - 1: + # a = np.where(wm_data[:,:,i-1] > 0) + # area = np.count_nonzero(wm_data[:,:,i-1][a]) + # amp_guess = area / pstdev(wm_data[:,:,i-1][a]) * 0.3989 * bin_width + # params = model.make_params(A1=amp_guess*0.1, mu1=wm_mean[i-1], sigma1=pstdev(wm_data[:,:,i-1][a]), A2=amp_guess, mu2=median(wm_data[:,:,i-1][a]), sigma2=pstdev(wm_data[:,:,i-1][a])) + # else: + # a = np.where(wm_data[:,:,i+1] > 0) + # area = np.count_nonzero(wm_data[:,:,i+1][a]) + # amp_guess = area / pstdev(wm_data[:,:,i+1][a]) * 0.3989 * bin_width + # params = model.make_params(A1=amp_guess*0.1, mu1=wm_mean[i+1], sigma1=pstdev(wm_data[:,:,i+1][a]), A2=amp_guess, mu2=median(wm_data[:,:,i+1][a]), sigma2=pstdev(wm_data[:,:,i+1][a])) + # omit slice if empty + # if i == 0: + # startat = 1 + # elif i == slice_num - 1: + # endat = slice_num - 2 + # continue + if hadSuccess and i < endat: + endat = i + elif not hadSuccess: + startat = i + 1 + continue + else: + hadSuccess = True + amp_guess = area / pstdev(wm_data[:,:,i][a]) * 0.3989 * bin_width + params = model.make_params(A1=amp_guess*0.1, mu1=wm_mean[i], sigma1=pstdev(wm_data[:,:,i][a]), A2=amp_guess, mu2=median(wm_data[:,:,i][a]), sigma2=pstdev(wm_data[:,:,i][a])) + + result = model.fit(hist, params, x=bins[:-1]) + # print(result.fit_report()) + # print(result.best_values) + gaussian_params[i] = result.best_values + + # Plot the histogram + plt.figure() + plt.bar(bins[:-1], hist, width=np.diff(bins), align='edge', alpha=0.5) + + # Plot the fitted curve + plt.plot(bins[:-1], result.best_fit, color='red', linewidth=2) + + # Add labels and title + plt.title(f"Histogram and Fitted Curve - Slice {i+1}") + plt.xlabel("Pixel Value") + plt.ylabel("Frequency") + + # make hist directory if it doesn't exist + hist_dir = file_dir + '/../figures/hist' + Path(hist_dir).mkdir(parents=True, exist_ok=True) + + # Save the figure + path1 = file_dir + '/../figures/hist/' + str(num) + '_' + str(i+1) + '_hist.png' + plt.savefig(path1) + plt.close() + + + # apply normalizations + print("Using Gaussian fitting to normalize FA " + str(num)) + mu = [0 for i in range(slice_num)] + # print(startat, endat) + # endat = len(gaussian_params) + for i in range(startat, endat): + # max(gaussian_params[i][0], gaussian_params[i][3]) + # if gaussian_params[i][0] > gaussian_params[i][3] and gaussian_params[i][1] > 0 and gaussian_params[i][1] < 1000:# and abs(gaussian_params[i][1] - wm_mean[i]) < abs(gaussian_params[i][4] - wm_mean[i]): + # mu.append(gaussian_params[i][1]) + # else: + # mu.append(gaussian_params[i][4]) + # print(i, slice_num) + if gaussian_params[i]['A1'] > gaussian_params[i]['A2'] and gaussian_params[i]['mu1'] > 0 and gaussian_params[i]['mu1'] > gaussian_params[i]['mu2'] and gaussian_params[i]['mu1'] < 1500: + mu[i] = gaussian_params[i]['mu1'] + else: + mu[i] = gaussian_params[i]['mu2'] + + # print("mu " + str(mu[i])) + # print("SLICE: " + str(i + 1)) + # print("mu1 and mu2: " + str(gaussian_params[i]['mu1']) + " " + str(gaussian_params[i]['mu2'])) + # print("A: " + str(gaussian_params[i]['A1']) + " " + str(gaussian_params[i]['A2'])) + # print("sigma: " + str(gaussian_params[i]['sigma1']) + " " + str(gaussian_params[i]['sigma2'])) + + median_mu = median([m for m in mu if m != 0]) + # print("median_mu: " + str(median_mu)) + for i in range(slice_num): + if mu[i] == 0: + continue + scale_factor = median_mu / mu[i] + mri_final[:, :, i] *= scale_factor + wm_final[:, :, i] *= scale_factor + + elif POLYFIT is True: print("Using Polynomial fitting to normalize") poly_norm_curve = Polynomial.fit(list(range(slice_num)), wm_mean, 4, w=polyfit_slice_weights) norm_slices = polyval(list(range(slice_num)), poly_norm_curve.convert().coef) @@ -66,9 +190,9 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N else: print("Using Z-normalization") # calc stats of all slices - data_mean = mean(wm_mean[0:13]) - std_dev = pstdev(wm_mean[0:13]) - err = 0.1*std_dev + data_mean = mean(wm_mean[0:slice_num-1]) + std_dev = pstdev(wm_mean[0:slice_num-1]) + err = 1*std_dev # find slices out of range min_val = data_mean - err max_val = data_mean + err @@ -97,35 +221,56 @@ def normalize(mri_file1, wm_masked, file_dir): # THE FUNCTION PERFORMING THE N norm_wm.append(0) # data_mean1 = mean(norm_img) - fig, ax = plt.subplots(figsize=(20, 6)) ax.plot(range(slice_num), wm_mean, '-ok', label='original') - if POLYFIT is True: + if POLYFIT is True and GAUSSFIT is False: ax.plot(range(slice_num), norm_slices, ':ob', label='fit') - ax.plot(range(slice_num), norm_wm, '--og', label='corrected') + ax.plot(range(slice_num), norm_wm, '--xg', label='corrected', mfc='none') + if GAUSSFIT: + ax.plot(range(slice_num), mu, 'o', label='mu', color='grey') + ax.plot(range(slice_num), np.ones(slice_num)*median_mu, ':x', label='median_mu', color='lightgreen') ax.set_xlabel('Slice #') ax.set_ylabel('White Matter Mean') ax.set_title("VFA Slice Normalization") ax.legend() - path2 = file_dir + '/' + str(num) +'_BFC_Z.png' - plt.savefig(path2) + # if figures directory doesn't exist, create it + Path('figures').mkdir(parents=True, exist_ok=True) + + path2 = 'figures/' + str(prefix) + '_flip-' + str(num) + '_space-DCEref_desc-bfcz_VFA.svg' + plt.savefig(path2, bbox_inches='tight') + plt.close() - mri_final = np.reshape(mri_final, mri_shape) + # transpose mri_final back to mri_shape order + x_index = mri_final.shape.index(mri_shape[0]) + y_index = mri_final.shape.index(mri_shape[1]) + z_index = mri_final.shape.index(mri_shape[2]) + if x_index == y_index: + y_index = set(dim) - {x_index, z_index} + y_index = list(y_index)[0] + elif x_index == z_index: + z_index = set(dim) - {x_index, y_index} + z_index = list(z_index)[0] + elif y_index == z_index: + z_index = set(dim) - {x_index, y_index} + z_index = list(z_index)[0] + + mri_final = np.transpose(mri_final, (x_index, y_index, z_index)) + mri_final = mri_final.astype(np.float32) + # cut off z-slices where mu is 0 + # mri_final = mri_final[:, :, startat:endat] final_img = nib.Nifti1Image(mri_final, mri.affine) - path3 = file_dir + '/' + str(num) + '_BFC_Z.nii' + path3 = file_dir + '/' + str(prefix) + '_flip-' + str(num) + '_space-DCEref_desc-bfcz_VFA.nii.gz' nib.save(final_img, path3) +if __name__ == "__main__": + dir = Path(sys.argv[1]) # takes timepoint directory as argument + prefix = sys.argv[2] + BFC = sys.argv[3] + file_pattern = r'.*flip-\d+_space-DCEref_desc-bfc_VFA.nii.*' if BFC else r'.*flip-\d+_space-DCEref_desc-brain_VFA.nii.*' + files_in_dir = dir.iterdir() + file_list = [file for file in files_in_dir if re.search(r'.*flip-\d+_space-DCEref_desc-brain_VFA.nii.*', str(file))] + num_processes = len(file_list) -dir = Path(sys.argv[1]) # takes timepoint directory as argument -files_in_dir = dir.iterdir() -for file in files_in_dir: - if re.search(r'\d+_bfc.nii.*', str(file)): - file1 = str(file) - mask_file = file1.split('.', 1) - mask_file = mask_file[0] + '_wm.nii' - - try: - normalize(file1, mask_file, str(dir)) #CALLING THE 'normalize()' FUNCTION TO PERFORM THE NORMALIZATION - except FileNotFoundError: - normalize(file1, mask_file + ".gz", str(dir)) + with multiprocessing.Pool(processes=num_processes) as pool: + pool.starmap(normalize, [(str(file), str(file).split('_desc-brain', 1)[0] + '_seg-WM_VFA.nii.gz', str(dir)) for file in file_list]) diff --git a/aif_metric.py b/aif_metric.py new file mode 100644 index 0000000..89006ee --- /dev/null +++ b/aif_metric.py @@ -0,0 +1,72 @@ +import numpy as np + +# get metric of AIF +def quality_peak(aif_curve): + peak_ratio = max(aif_curve) / aif_curve[0] + # peak_ratio = tf.cast(peak_ratio, tf.float32) + return peak_ratio*(100/7.546971123202499) + +def quality_tail(aif_curve): + # end is mean of last 20% of curve + end_ratio = np.mean(aif_curve[-int(float(int(len(aif_curve)))*0.2):]) / aif_curve[0] + # end_ratio = tf.cast(end_ratio, tf.float32) + + quality = (1 / (end_ratio + 1)) * (100/0.24035631585328981) + # if quality > 200: + # quality = 200 + return quality + +def quality_peak_to_end(aif_curve): + peak_ratio = quality_peak(aif_curve)/(100/7.546971123202499) + end_ratio = np.mean(aif_curve[-int(float(int(len(aif_curve)))*0.2):]) / aif_curve[0] + # end_ratio = tf.cast(end_ratio, tf.float32) + + return (peak_ratio / end_ratio)*(100/2.4085609761976534) + +def quality_peak_time(aif_curve): + peak_time = np.argmax(aif_curve) + # peak_time = tf.cast(peak_time, tf.float32) + num_timeslices = len(aif_curve) + qpt = (num_timeslices - peak_time) / num_timeslices + return qpt*(100/0.9157142857142857) + +def quality_ultimate(aif_curve): + peak_ratio = quality_peak(aif_curve) + end_ratio = quality_tail(aif_curve) + peak_to_end = quality_peak_to_end(aif_curve) + peak_time = quality_peak_time(aif_curve) + + # take weighted average + return peak_ratio*0.3 + end_ratio*0.3 + peak_to_end*0.3 + peak_time*0.1 + # return peak_ratio + 0.3*end_ratio + 0.3*peak_to_end + 0.1*peak_time + +def quality_peak_new(aif_curve): + peak_ratio = max(aif_curve) / np.mean(aif_curve) + return (1 / (1 + np.exp(-3.5 * peak_ratio + 7.5))) * (100 / 0.4499714351078607) + +def quality_tail_new(aif_curve): + end_ratio = np.mean(aif_curve[-int(len(aif_curve) * 0.2):]) + quality = (1 - (end_ratio / (1.1 * np.mean(aif_curve))) ** 2) + return quality * (100 / 0.33436023529043213) + +def quality_base_to_mean_new(aif_curve): + return (1 - (get_baseline_from_curve(aif_curve) / np.mean(aif_curve)) ** 2) * (100 / 0.8831850876454762) + +def quality_peak_time_new(aif_curve): + peak_time = np.argmax(aif_curve) + num_timeslices = len(aif_curve) + qpt = (num_timeslices - peak_time) / num_timeslices + return qpt * (100 / 0.9081383928571428) + +def quality_ultimate_new(aif_curve): + peak_ratio = quality_peak_new(aif_curve) + end_ratio = quality_tail_new(aif_curve) + base_to_mean = quality_base_to_mean_new(aif_curve) + peak_time = quality_peak_time_new(aif_curve) + + # take weighted average + return peak_ratio * 0.3 + end_ratio * 0.3 + base_to_mean * 0.3 + peak_time * 0.1 + +def get_baseline_from_curve(curve): + peak_index = np.argmax(curve) + return np.mean(curve[:peak_index-1][np.where(curve[:peak_index-1] < curve[0] * 1.75)]) diff --git a/case_report.py b/case_report.py new file mode 100644 index 0000000..d6badfe --- /dev/null +++ b/case_report.py @@ -0,0 +1,655 @@ +import datetime +import sys +import json +import os +import subprocess +import re + +import jinja2 +import nibabel as nib +import numpy as np +import matplotlib.pyplot as plt +from nilearn import plotting +from matplotlib import colors as mcolors + +from aif_metric import * +import glob +from utils.constants import KTRANS_MIN_THRESHOLD + +source_dir = sys.argv[1] +# source_dir = sys.argv[2] +prefix = sys.argv[2] +freesurfer = bool(int(sys.argv[3])) +# if source_dir[-1] == '/': +# source_dir = source_dir[:-1] + +files_to_reorient = [f'anat/{prefix}_flip-01_space-DCEref_VFA.nii.gz', f'dce/{prefix}_Ktrans.nii', + f'anat/{prefix}_space-DCEref_T1w.nii.gz', f'anat/{prefix}_space-DCEref_label-WM_mask.nii.gz', + f'anat/{prefix}_space-DCEref_T1map.nii', f'anat/{prefix}_space-DCEref_desc-brain_mask.nii.gz', + f'anat/{prefix}_space-DCEref_label-GM_mask.nii.gz', f'anat/{prefix}_space-DCEref_desc-wmparc.nii.gz', + f'dce/{prefix}_desc-hmc_DCEref.nii.gz', f'dce/{prefix}_DCEref.nii.gz'] +# if c3d exists, reorient files to RAS +dimensions = 0 +voxel_size = 0 +mean_wm = 0 +mean_gm = 0 +expected_ktrans_vmax = 0.005 +if subprocess.run(['which', 'c3d'], stdout=subprocess.PIPE).returncode == 0: + for file in files_to_reorient: + if not os.path.exists(file): + print(f"File does not exist, skipping: {file}") + continue + file_no_extension = file.split('.')[0] + command = ['c3d', file, '-orient', 'RAS', '-o', file_no_extension + '_RAS.nii.gz'] + try: + subprocess.run(command, check=True) + if file == (f'dce/{prefix}_Ktrans.nii'): + ktrans = nib.load(f'dce/{prefix}_Ktrans_RAS.nii.gz') + ktrans_data = ktrans.get_fdata() + ktrans_flipped = np.flip(ktrans_data, axis=1) + ktrans_flipped = nib.Nifti1Image(ktrans_flipped, ktrans.affine, ktrans.header) + dimensions = ktrans.header.get_data_shape() + voxel_size = ktrans.header.get_zooms() + + # plot ktrans + fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(15, 5), gridspec_kw={'hspace': -.1, 'wspace': -.1}, dpi=300) + #read ktrans coordinates + ktrans_coords = int(ktrans.header['qoffset_z']) + ktrans_z_slices = min(dimensions) + midpt = int(ktrans_coords-5*ktrans_z_slices/2) + max_coord = int(ktrans_coords-5*ktrans_z_slices) + plotting.plot_anat(ktrans_flipped, display_mode='z', cut_coords=range(ktrans_coords, midpt, -5), axes=axes[0], vmin=0, vmax=expected_ktrans_vmax, cmap='gnuplot', annotate=False, colorbar=True) + plotting.plot_anat(ktrans_flipped, display_mode='z', cut_coords=range(midpt, max_coord, -5), axes=axes[1], vmin=0, vmax=expected_ktrans_vmax, cmap='gnuplot', annotate=False) + plt.savefig('figures/ktrans.svg', bbox_inches='tight', pad_inches = 0) + plt.close() + except Exception as e: + print("Error running c3d command: " + ' '.join(command)) + print(e) +else: + # use freesurfer's mri_convert to reorient files to RAS + for file in files_to_reorient: + if not os.path.exists(file): + print(f"File does not exist, skipping: {file}") + continue + file_no_extension = file.split('.')[0] + command = ['mri_convert', '--in_orientation', 'LPI', file, file_no_extension + '_RAS.nii.gz'] + try: + subprocess.run(command, check=True) + if file == (f'dce/{prefix}_Ktrans.nii'): + ktrans = nib.load(f'dce/{prefix}_Ktrans_RAS.nii.gz') + ktrans_data = ktrans.get_fdata() + ktrans_flipped = np.flip(ktrans_data, axis=1) + ktrans_flipped = nib.Nifti1Image(ktrans_flipped, ktrans.affine, ktrans.header) + dimensions = ktrans.header.get_data_shape() + voxel_size = ktrans.header.get_zooms() + + # plot Ktrans, different coords + fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(15, 5), gridspec_kw={'hspace': -.1, 'wspace': -.1}, dpi=300) + plotting.plot_anat(ktrans_flipped, display_mode='z', cut_coords=range(-56, -21, 5), axes=axes[0], vmin=0, vmax=expected_ktrans_vmax, cmap='gnuplot', annotate=False, colorbar=True) + plotting.plot_anat(ktrans_flipped, display_mode='z', cut_coords=range(-21, 13, 5), axes=axes[1], vmin=0, vmax=expected_ktrans_vmax, cmap='gnuplot', annotate=False) + plt.savefig('figures/ktrans.svg', bbox_inches='tight', pad_inches = 0) + plt.close() + except Exception as e: + print("Error running freesurfer mri_convert (reorient)") + dimensions = 'ktrans failed to load' + voxel_size = 'ktrans failed to load' + print(e) + +# use jinja2 to generate html +env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(os.path.realpath(__file__)))) +template = env.get_template('template.html') + +# get date +date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") +# get commit hash +try: + commit_hash = subprocess.check_output( + ['git', 'rev-parse', 'HEAD'], + cwd=os.path.dirname(os.path.realpath(__file__)) + ).decode('ascii').strip() +except Exception as e: + print("Git didn't work correctly. Trying a different way of getting latest dev branch commit hash...") + command = ['cat', '.git/refs/heads/dev'] + commit_hash = subprocess.check_output( + command, + cwd=os.path.dirname(os.path.realpath(__file__)) + ).decode('ascii').strip() + +# Get ROCKETSHIP repo commit hash +try: + # Assume ROCKETSHIP is in ../ROCKETSHIP relative to this script + rocketship_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'ROCKETSHIP')) + rocketship_commit_hash = subprocess.check_output( + ['git', 'rev-parse', 'HEAD'], + cwd=rocketship_dir + ).decode('ascii').strip() +except Exception as e: + print("Could not get ROCKETSHIP commit hash.") + rocketship_commit_hash = 'unknown' + +# get subject id +subject_id = source_dir.split('/')[-2] +timepoint = source_dir.split('/')[-1] +if subject_id.endswith('_timepoint'): + subject_id = source_dir.split('/')[-3] + # get timepoint + timepoint = source_dir.split('/')[-2] + +# get institute from DCE.json +try: + with open(os.path.join(source_dir, 'dce', f'{prefix}_DCE.json')) as f: + dce = json.load(f) + institute = dce['InstitutionName'] + manufacturer = dce['Manufacturer'] + MR_machine_model = dce['ManufacturersModelName'] + field_strength = dce['MagneticFieldStrength'] +except Exception as e: + dce = {} + institute = 'no json' + manufacturer = 'no json' + MR_machine_model = 'no json' + field_strength = 'no json' + print("Error loading DCE.json") + print(e) + +try: + # brain mask + plotting.plot_roi(f'anat/{prefix}_desc-brain_mask.nii.gz', bg_img=f'{source_dir}/anat/{prefix}_T1w.nii.gz', cut_coords=(-20, 0, -15), vmin=0, vmax=1, dim=-1, cmap='gray', output_file='figures/t1w_mask.svg', colorbar=False, draw_cross=False, title='mask') + + # T1w segmentation + plotting.plot_roi(f'{source_dir}/anat/{prefix}_T1w.nii.gz', bg_img=f'{source_dir}/anat/{prefix}_T1w.nii.gz', cmap='gray', output_file='figures/t1w.svg', cut_coords=(-20, 0, -15), dim=-1, colorbar=False, draw_cross=False) + plotting.plot_roi(f'anat/{prefix}_label-WM_mask.nii.gz', bg_img=f'{source_dir}/anat/{prefix}_T1w.nii.gz', cut_coords=(-20, 0, -15), vmin=0, vmax=1, dim=-1, cmap='gray', output_file='figures/t1w_wm.svg', colorbar=False, draw_cross=False, title='wm') + plotting.plot_roi(f'anat/{prefix}_label-GM_mask.nii.gz', bg_img=f'{source_dir}/anat/{prefix}_T1w.nii.gz', cut_coords=(-20, 0, -15), vmin=0, vmax=1, dim=-1, cmap='gray', output_file='figures/t1w_gm.svg', colorbar=False, draw_cross=False, title='gm') +except Exception as e: + print("Error plotting T1w segmentation") + print(e) + +try: + # T1w to VFA + # plotting.plot_anat(f'anat/{prefix}_label-WM_mask_RAS.nii.gz', cmap='gray', output_file='figures/t1w_to_dceref.svg', cut_coords=7, display_mode='z', annotate=False, colorbar=False, draw_cross=False, title='T1w to DCEref') + + # T1w to dyn + t1w_dceref = nib.load(f'anat/{prefix}_space-DCEref_T1w_RAS.nii.gz') + t1w_dceref_data = t1w_dceref.get_fdata() + t1w_dceref_flipped = np.flip(t1w_dceref_data, axis=1) + t1w_dceref_flipped = nib.Nifti1Image(t1w_dceref_flipped, t1w_dceref.affine, t1w_dceref.header) + plotting.plot_anat(t1w_dceref_flipped, cmap='gray', output_file=f'figures/{prefix}_space-DCEref_T1w.svg', cut_coords=7, display_mode='z', annotate=False, colorbar=False, draw_cross=False, title='T1w to dyn') + + # flip T1w masks + t1w_mask = nib.load(f'anat/{prefix}_space-DCEref_desc-brain_mask_RAS.nii.gz') + t1w_mask_data = t1w_mask.get_fdata() + t1w_mask_flipped = np.flip(t1w_mask_data, axis=1) + t1w_mask_flipped = nib.Nifti1Image(t1w_mask_flipped, t1w_mask.affine, t1w_mask.header) + # nib.save(t1w_mask_flipped, str(tp_dir) + '/T1_bet_mask_RAS.nii') + + t1w_wm_mask = nib.load(f'anat/{prefix}_space-DCEref_label-WM_mask_RAS.nii.gz') + t1w_wm_mask_data = t1w_wm_mask.get_fdata() + t1w_wm_mask_flipped = np.flip(t1w_wm_mask_data, axis=1) + t1w_wm_mask_flipped = nib.Nifti1Image(t1w_wm_mask_flipped, t1w_wm_mask.affine, t1w_wm_mask.header) + # nib.save(t1w_wm_mask_flipped, str(tp_dir) + '/T1_wm_mask_RAS.nii') + + t1w_gm_mask = nib.load(f'anat/{prefix}_space-DCEref_label-GM_mask_RAS.nii.gz') + t1w_gm_mask_data = t1w_gm_mask.get_fdata() + t1w_gm_mask_flipped = np.flip(t1w_gm_mask_data, axis=1) + t1w_gm_mask_flipped = nib.Nifti1Image(t1w_gm_mask_flipped, t1w_gm_mask.affine, t1w_gm_mask.header) + # nib.save(t1w_gm_mask_flipped, str(tp_dir) + '/T1_gm_mask_RAS.nii') + + plotting.plot_roi(t1w_mask_flipped, cmap='gray', bg_img=t1w_dceref_flipped, output_file='figures/t1bet_to_dyn.svg', display_mode='z', vmin=0, vmax=1, dim=0, annotate=True, colorbar=False, draw_cross=False, title='T1w brain mask to DCEref') + plotting.plot_roi(t1w_wm_mask_flipped, cmap='gray', bg_img=t1w_dceref_flipped, output_file='figures/t1wm_to_dyn.svg', display_mode='z', vmin=0, vmax=1, dim=0, annotate=True, colorbar=False, draw_cross=False, title='T1w wm to DCEref') + plotting.plot_roi(t1w_gm_mask_flipped, cmap='gray', bg_img=t1w_dceref_flipped, output_file='figures/t1gm_to_dyn.svg', display_mode='z', vmin=0, vmax=1, dim=0, annotate=True, colorbar=False, draw_cross=False, title='T1w gm to DCEref') +except Exception as e: + print("Error plotting T1w to dyn") + print(e) + +try: + # T1 map + # read txt file + FAs = [] + is_target_line = False + with open(f'anat/{prefix}_space-DCEref_T1map.txt', 'r') as f: + for line in f: + if "User selected TE/TR/FA/TI: " in line: + is_target_line = True + elif is_target_line: + match = re.search(r'\d+', line) + if match: + number = int(match.group()) + FAs.append(number) + else: + is_target_line = False + # take last set of non-repeating numbers + FAs = FAs[-5:] + # convert from list to string + FA_str = [str(i) for i in FAs] + FA_str = ', '.join(FA_str) + + # now get TR from txt file + TR = None + is_target_line = False + with open(f'anat/{prefix}_space-DCEref_T1map.txt', 'r') as f: + for line in f: + if "User selected tr: " in line: + is_target_line = True + elif is_target_line: + match = re.search(r'\d+.\d+', line) + if match: + TR = match.group() + else: + is_target_line = False + + # check if GPU was used + GPU = False + with open(f'anat/{prefix}_space-DCEref_T1map.txt', 'r') as f: + for line in f: + if "GPU detected" in line: + GPU = True + if GPU: + GPU_T1 = 'GPU was used' + else: + GPU_T1 = 'CPU was used' +except Exception as e: + print("Error getting T1 map parameters") + FAs = [-1, -1, -1, -1, -1] + FA_str = 'Failed to load FAs' + TR = -1 + GPU_T1 = 'Failed to load GPU info' + print(e) + +try: + # T1 map + # flip T1 map + img = nib.load(f'anat/{prefix}_space-DCEref_T1map_RAS.nii.gz') + img_data = img.get_fdata() + img_data = np.flip(img_data, axis=0) + img_data = np.flip(img_data, axis=1) + t1_map_flipped = nib.Nifti1Image(img_data, img.affine, img.header) + plotting.plot_anat(t1_map_flipped, cmap='gray', vmin=0, vmax=5000, output_file='figures/t1_map.svg', annotate=False, colorbar=False, draw_cross=False, title='T1 map') +except Exception as e: + print("Error plotting T1 map") + print(e) + +# try: +# AIF +# plot graph of AIF region +aif = nib.load(f'dce/{prefix}_desc-AIFpos_T1map.nii.gz') +aif_data = aif.get_fdata() +# img = nib.load(str(tp_dir) + '/DCE_mc.nii.gz') +try: + img = nib.load(f'dce/{prefix}_desc-hmc_DCE.nii.gz') + img_data = img.get_fdata() +except FileNotFoundError: + img = nib.load(f'{source_dir}/dce/{prefix}_DCE.nii.gz') + img_data = img.get_fdata() +# binarize AIF +aif_data[aif_data > 0] = 1 +aif_data[aif_data < 0] = 0 +# mask DCE where AIF is 1 +# but first ensure that DCE and AIF have same number of dimensions +if len(aif_data.shape) < len(img_data.shape): + aif_data = np.expand_dims(aif_data, axis=-1) +aif_data_roi = img_data * aif_data +# sum AIF data for each time point, z-slice independent +aif_curve = np.sum(aif_data_roi, axis=(0, 1, 2)) / np.sum(aif_data[aif_data > 0]) +# divide by AIF mean of timepoints before contrast agent arrival +baseline = get_baseline_from_curve(aif_curve) +aif_curve_ratio = aif_curve / baseline + +aif_metric = quality_ultimate_new(aif_curve_ratio) + +# plot AIF +plt.plot(aif_curve_ratio, linewidth=2) +# plt.text(0.5, 0.95, 'Voxel Baseline Avg SI: ' + str(round(baseline, 2)), transform=plt.gca().transAxes, fontsize=11, verticalalignment='top') +plt.text(0.5, 0.9, 'AIFitness: ' + str(round(aif_metric, 2)), transform=plt.gca().transAxes, fontsize=18, verticalalignment='top') +plt.title('AIF Curve', fontsize=18) +plt.xlabel('Timepoint', fontsize=18) +plt.ylabel('Normalized Signal Intensity', fontsize=18) +plt.xticks(fontsize=18) +plt.yticks(fontsize=18) +plt.savefig(f'figures/{prefix}_desc-AIF_curve.svg', bbox_inches='tight') +plt.close() + +# save AIF values to file +np.savetxt('dce/AIF_values.txt', aif_curve_ratio) + +# plot AIF overlay +plt.figure(figsize=(15,5), dpi=250) +plt.subplot(1,2,1) +plt.axis('off') + +# rotate images +img_data = np.rot90(img_data, axes=(0,1)) +aif_data = np.rot90(aif_data, axes=(0,1)) + +# overlay AIF mask +aif_slice = np.where(aif_data > 0)[2][0] +cmap = mcolors.LinearSegmentedColormap.from_list('custom cmap', [(0, 0, 0, 0), 'blue', 'green', 'red']) +plt.imshow(img_data[:,:,aif_slice, 5], cmap='gray') +plt.imshow(aif_data[:,:,aif_slice], cmap=cmap, alpha=1) +plt.savefig(f'figures/{prefix}_desc-AIF_overlay.svg', bbox_inches='tight') +plt.close() +# except Exception as e: +# print("Error plotting AIF") +# # print error +# print(e) + +if freesurfer: + # wmparc overlay on DCE + wmparc = nib.load(f'anat/{prefix}_space-DCEref_desc-wmparc_RAS.nii.gz') + wmparc_data = wmparc.get_fdata() + wmparc_flipped = np.flip(wmparc_data, axis=1) + wmparc_flipped = nib.Nifti1Image(wmparc_flipped, wmparc.affine, wmparc.header) + + try: + dce = nib.load(f'dce/{prefix}_desc-hmc_DCEref_RAS.nii.gz') + except FileNotFoundError: + dce = nib.load(f'dce/{prefix}_DCEref_RAS.nii.gz') + dce_data = dce.get_fdata() + dce_flipped = np.flip(dce_data, axis=1) + dce_flipped = nib.Nifti1Image(dce_flipped, dce.affine, dce.header) + # get 95% percentile of DCE + dce_95th = np.percentile(dce_flipped.get_fdata(), 95) + + # overlay wmparc on DCE + plt.figure(figsize=(15,5), dpi=250) + plt.subplot(1,2,1) + plt.axis('off') + # rotate images + # wmparc_data = np.rot90(wmparc_data, axes=(0,1)) + + # overlay wmparc mask on DCE per region + try: + plotting.plot_roi(dce_flipped, bg_img=dce_flipped, output_file=f'figures/{prefix}_desc-hmc_DCEref.svg', display_mode='z', cut_coords=range(-140, -110, 10), vmin=0, vmax=dce_95th, dim=-1.55, annotate=False, colorbar=False, draw_cross=False, title='DCE', alpha=0) + plotting.plot_roi(wmparc_flipped, bg_img=dce_flipped, output_file='figures/wmparc_overlay.svg', display_mode='z', cut_coords=range(-140, -110, 10), cmap='tab20', dim=-1.55, annotate=False, colorbar=False, draw_cross=False, title='wmparc overlay', alpha=0.7) + except Exception as e: + # plot with default coords + plotting.plot_roi(dce_flipped, bg_img=dce_flipped, output_file=f'figures/{prefix}_desc-hmc_DCEref.svg', display_mode='z', vmin=0, vmax=dce_95th, dim=-1, annotate=False, colorbar=False, draw_cross=False, title='DCE', alpha=0) + plotting.plot_roi(wmparc_flipped, bg_img=dce_flipped, output_file='figures/wmparc_overlay.svg', display_mode='z', cmap='tab20', dim=-1, annotate=False, colorbar=False, draw_cross=False, title='wmparc overlay', alpha=0.7) + # T1 dynamic space + +# get DCE parameters +def extract_value(pattern, text): + match = re.search(pattern, text) + if match: + return match.group(1) + return None + +try: + with open('dce/A_dceR1info.log', 'r') as file: + log_text = file.read() + RUNA_log = True +except Exception as e: + log_text = '' + RUNA_log = False + print("Error getting DCE parameters from A_dceR1info.log") + print(e) + +tr_pattern = r"User selected TR \(ms\):\s+(\d+(\.\d+)?)" +fa_pattern = r"User selected FA \(degrees\):\s+(\d+)" +hematocrit_pattern = r"User selected hematocrit \(0 to 1.0\):\s+(\d+\.\d+)" +snr_threshold_pattern = r"User selected SNR threshold for AIF:\s+(\d+)" +relaxivity_pattern = r"User selected contrast agent R1 relaxivity \(/mM/sec\):\s+(\d+\.\d+)" +steady_state_pattern = r"User selected end of steady state time \(image number\):\s+(-?\d+)" + +DCE_tr = extract_value(tr_pattern, log_text) +DCE_fa = extract_value(fa_pattern, log_text) +hematocrit = extract_value(hematocrit_pattern, log_text) +snr_threshold = extract_value(snr_threshold_pattern, log_text) +relaxivity = extract_value(relaxivity_pattern, log_text) +steady_state = extract_value(steady_state_pattern, log_text) +blood_t1_pattern = "Average Filtered AIF T1: " + +if RUNA_log: + # get last line of log file + with open('dce/A_dceR1info.log', 'r') as file: + match = False + for line in file: + if line[:-1] == blood_t1_pattern: + match = True + elif match: + blood_t1 = line[:-1] + match = False + A_last_line = line +else: + A_last_line = 'Failed to load RUNA log' + +try: + # now get Time Resolution from log file + time_resolution = None + is_target_line = False + B_log = 'dce/B_dcefitted_R1info.log' + B_imported_log = 'dce/B_dceimported_R1info.log' + if os.path.isfile(B_log): + with open(B_log, 'r') as f: + for line in f: + if "User selected time resolution (sec)" in line: + is_target_line = True + elif is_target_line: + match = re.search(r'(\d+)(.*)\d*', line) + if match: + time_resolution = match.group() + else: + is_target_line = False + elif os.path.isfile(B_imported_log): + with open(B_imported_log, 'r') as f: + for line in f: + if "User selected time resolution (sec)" in line: + is_target_line = True + elif is_target_line: + match = re.search(r'(\d+)(.*)\d*', line) + if match: + time_resolution = match.group() + else: + is_target_line = False + RUNB_log = True +except Exception as e: + print("Error getting DCE RUNB parameters from B_dcefitted_R1info.log") + print(e) + RUNB_log = False + +if RUNB_log: + def extract_r2_values(log_text): + r2_pattern = r"Adjusted R\^2 of AIF fit = (-*\d+\.\d+)" + r2_values = re.findall(r2_pattern, log_text) + return r2_values + + if os.path.isfile(B_log): + with open('dce/B_dcefitted_R1info.log', 'r') as file: + log_text = file.read() + elif os.path.isfile(B_imported_log): + with open('dce/B_dceimported_R1info.log', 'r') as file: + log_text = file.read() + + r2_values = extract_r2_values(log_text) + + if len(r2_values) >= 2: + r2_aif_fit = r2_values[-2] + r2_raw_values = r2_values[-1] + + # get last line of B log file (time elapsed) + if os.path.isfile(B_log): + with open('dce/B_dcefitted_R1info.log', 'r') as file: + for line in file: + pass + B_last_line = line + elif os.path.isfile(B_imported_log): + with open('dce/B_dceimported_R1info.log', 'r') as file: + for line in file: + pass + B_last_line = line + r2_aif_fit = 'Imported AIF' + r2_raw_values = 'Imported AIF' +else: + r2_aif_fit = 'Failed to load RUNB log' + r2_raw_values = 'Failed to load RUNB log' + B_last_line = 'Failed to load RUNB log' + +try: + # get GPU info + GPU = False + latest_log_file = max(glob.glob('dce/dce_*_fit.log'), key=os.path.getctime) + latest_log_file_name = os.path.basename(latest_log_file) + dce_model = latest_log_file_name.replace('dce_', '').replace('_fit.log', '') + with open(latest_log_file, 'r') as f: + for line in f: + if "Gpufit detected" in line: + GPU = True + if GPU: + GPU_DCE = 'GPU was used' + else: + GPU_DCE = 'CPU was used' + RUND_log = True +except Exception as e: + print("Error getting DCE GPU info from latest dce_*_fit.log") + print(e) + RUND_log = False + GPU_DCE = 'Failed to load GPU info' + dce_model = 'Failed to load DCE model' + +if RUND_log: + # get RUN D time elapsed + def extract_elapsed_time(log_text): + elapsed_time_pattern = r"Elapsed time is (\d+\.\d+) seconds." + match = re.search(elapsed_time_pattern, log_text) + if match: + return match.group(1) + return None + + with open(latest_log_file, 'r') as file: + log_text = file.read() + + dce_elapsed_time = extract_elapsed_time(log_text) +else: + dce_elapsed_time = 'Failed to load RUND log' + +# Ktrans + +# get Ktrans mean wm and gm +ktrans_wm = nib.load(f'dce/{prefix}_seg-WM_Ktrans.nii.gz') +ktrans_wm_data = ktrans_wm.get_fdata() +ktrans_wm_mask = nib.load(f'anat/{prefix}_space-DCEref_label-WM_mask.nii.gz') +ktrans_wm_mask_data = ktrans_wm_mask.get_fdata() +# mean_wm = np.nanmean(ktrans_wm_data[ktrans_wm_data > 0])*1000 +ktrans_median_wm = np.nanmedian(ktrans_wm_data[np.logical_and(ktrans_wm_mask_data > 0, ktrans_wm_data > KTRANS_MIN_THRESHOLD)])*1000 +ktrans_std_wm = np.nanstd(ktrans_wm_data[np.logical_and(ktrans_wm_mask_data > 0, ktrans_wm_data > KTRANS_MIN_THRESHOLD)])*1000 + +ktrans_gm = nib.load(f'dce/{prefix}_seg-GM_Ktrans.nii.gz') +ktrans_gm_data = ktrans_gm.get_fdata() +ktrans_gm_mask = nib.load(f'anat/{prefix}_space-DCEref_label-GM_mask.nii.gz') +ktrans_gm_mask_data = ktrans_gm_mask.get_fdata() +# mean_gm = np.nanmean(ktrans_gm_data[ktrans_gm_data > 0])*1000 +ktrans_median_gm = np.nanmedian(ktrans_gm_data[np.logical_and(ktrans_gm_mask_data > 0, ktrans_gm_data > KTRANS_MIN_THRESHOLD)])*1000 +ktrans_std_gm = np.nanstd(ktrans_gm_data[np.logical_and(ktrans_gm_mask_data > 0, ktrans_gm_data > KTRANS_MIN_THRESHOLD)])*1000 + +# get T1 map median wm and gm +T1_wm = nib.load(f'anat/{prefix}_space-DCEref_label-WM_T1map.nii.gz') +T1_wm_data = T1_wm.get_fdata() +T1_wm_median = np.median(T1_wm_data[T1_wm_data > 0]) +T1_wm_std = np.std(T1_wm_data[T1_wm_data > 0]) + +T1_gm = nib.load(f'anat/{prefix}_space-DCEref_label-GM_T1map.nii.gz') +T1_gm_data = T1_gm.get_fdata() +T1_gm_median = np.median(T1_gm_data[T1_gm_data > 0]) +T1_gm_std = np.std(T1_gm_data[T1_gm_data > 0]) + +# MNI space registration +# fsl_dir = os.environ['FSLDIR'] +# print(fsl_dir) +# try: +# plotting.plot_anat(os.path.dirname(os.path.realpath(__file__)) + '/MNI152_T1_1mm_brain.nii.gz', title='MNI152_T1_1mm_brain', output_file=tp_dir + '/figures/MNI152_T1_1mm_brain.svg', colorbar=False, draw_cross=False) +# plotting.plot_anat(tp_dir + '/t1w_MNIWarped.nii.gz', title='t1w_MNI', cut_coords=(2, -1, 20), output_file=tp_dir + '/figures/t1w_MNI.svg', colorbar=False, draw_cross=False) +# plotting.plot_anat(source_dir + '/Ktrans_MNI.nii.gz', title='ktrans_MNI', cut_coords=(2, -1, 20), vmin=0, vmax=0.001, output_file=source_dir + '/figures/Ktrans_MNI.svg', colorbar=False, draw_cross=False) +# except Exception as e: +# print("Error plotting MNI space registration") +# print(e) + +# if tp_dir != source_dir: +# tp_figdir = "../" +# else: +# tp_figdir = "" + +data = { + 'title': subject_id + ' ' + timepoint + ' Report', + 'heading': 'Summary', + 'Subject': 'Subject ID: ' + subject_id, + 'Timepoint': 'Timepoint: ' + timepoint, + 'Date': 'Date Processed: ' + date, + 'Commit': 'Commit: ' + commit_hash, + 'ROCKETSHIP_Commit': 'ROCKETSHIP Commit: ' + rocketship_commit_hash, + 'Institute': 'Institute: ' + institute, + 'Machine': 'Machine: ' + manufacturer + ' ' + MR_machine_model + ' ' + str(field_strength) + 'T', + 'ktrans': '../figures/ktrans.svg', + 'image_alt1': 'Missing image', + 'Dimensions': 'Dimensions: ' + str(dimensions), + 'Voxel_Size': 'Voxel Size: ' + str(voxel_size), + 'Overlay': '../figures/overlay.svg', + 'T1w': '../figures/t1w.svg', + 'T1w_mask': '../figures/t1w_mask.svg', + 'T1w_gm': '../figures/t1w_gm.svg', + 'T1w_wm': '../figures/t1w_wm.svg', + 'T1w_to_DCEref': f'../figures/{prefix}_space-DCEref_T1w.svg', + 'T1_TR': 'TR: ' + str(TR) + 'ms', + 'T1_FAs': 'FAs: ' + FA_str, + 'T1_GPU': str(GPU_T1), + 'T1_wm_median': 'T1 wm median: ' + str(round(T1_wm_median, 4)), + 'T1_wm_std': 'T1 wm std: ' + str(round(T1_wm_std, 4)), + 'T1_gm_median': 'T1 gm median: ' + str(round(T1_gm_median, 4)), + 'T1_gm_std': 'T1 gm std: ' + str(round(T1_gm_std, 4)), + 'T1_map': '../figures/t1_map.svg', + 'displacements' : '../figures/displacements.svg', + 'AIF_mask': f'../figures/{prefix}_desc-AIF_mask.svg', + 'AIF_metric' : "AIFitness: " + str(aif_metric), + 'AIF_curve': f'../figures/{prefix}_desc-AIF_resampledcurve.svg', + 'AIF_overlay': f'../figures/{prefix}_desc-AIF_overlay.svg', + 'AIF_graph': f'../figures/{prefix}_desc-AIF_curve.svg', + 'DCEref': f'../figures/{prefix}_desc-hmc_DCEref.svg', + 'wmparc_overlay': f'../figures/wmparc_overlay.svg', + 't1w_dyn' : f'../figures/{prefix}_space-DCEref_T1w.svg', + 't1w_bet_dyn' : '../figures/t1bet_to_dyn.svg', + 't1w_wm_dyn' : '../figures/t1wm_to_dyn.svg', + 't1w_gm_dyn' : '../figures/t1gm_to_dyn.svg', + 'Z_DCE' : f'../figures/{prefix}_desc-bfcz_DCE.svg', + 'DCE_TR' : 'Repetition Time: ' + str(DCE_tr) + 'ms', + 'DCE_FA' : 'Flip Angle: ' + str(DCE_fa) + '°', + 'Hematocrit' : 'Hematocrit: ' + str(hematocrit), + 'SNR_Threshold' : 'SNR Threshold: ' + str(snr_threshold), + 'Relaxivity' : 'Relaxivity: ' + str(relaxivity) + '/mM/sec', + 'T1_blood' : 'Blood T1: ' + str(blood_t1) + 's', + 'A_last_line' : str(A_last_line), + 'Time_Resolution' : 'Time Resolution: ' + str(time_resolution) + 's', + 'R_squared_fit' : 'R squared of AIF fit (fitted): ' + str(r2_aif_fit), + 'R_squared_raw' : 'R squared of AIF fit (raw): ' + str(r2_raw_values), + 'B_last_line' : str(B_last_line), + 'DCE_AIF_fit' : '../figures/dceAIF_fitting.png', + 'DCE_AIF_timecurve' : '../figures/dce_timecurves.png', + 'DCE_model' : 'Model: ' + dce_model, + 'GPU_DCE' : str(GPU_DCE), + 'DCE_elapsed_time' : 'Elapsed time: ' + str(dce_elapsed_time) + 's', + 'ktrans_zeros' : f'../figures/{prefix}_desc-zeros.png', + 'ktrans_analysis' : f'../figures/{prefix}_desc-analysis.png', + 'ktrans_wm_mean' : 'Mean wm Ktrans: ' + str(round(mean_wm, 4)), + 'ktrans_wm_median' : 'Median wm Ktrans: ' + str(round(ktrans_median_wm, 4)), + 'ktrans_wm_std' : 'Std wm Ktrans: ' + str(round(ktrans_std_wm, 4)), + 'ktrans_gm_mean' : 'Mean gm Ktrans: ' + str(round(mean_gm, 4)), + 'ktrans_gm_median' : 'Median gm Ktrans: ' + str(round(ktrans_median_gm, 4)), + 'ktrans_gm_std' : 'Std gm Ktrans: ' + str(round(ktrans_std_gm, 4)), + 'MNI_img' : '../figures/MNI152_T1_1mm_brain.svg', + 'MNI_T1w' : '../figures/t1w_MNI.svg', + 'MNI_Ktrans' : '../figures/ktrans_MNI.svg', +} + +# insert VFAs into template +flips = ['flip-01', 'flip-02', 'flip-03', 'flip-04', 'flip-05', 'flip-06', 'flip-07'] +data['FAs'] = [] +data['Zs'] = [] +for i in range(len(FAs)): + data['FAs'].append('FA ' + str(FAs[i])) + data['num_FAs'] = len(FAs) + data['Zs'].append(f'../figures/{prefix}_{flips[i]}_space-DCEref_desc-bfcz_VFA.svg') + +output = template.render(data) + +# write html to file +with open(f'reports/{prefix}_desc-casereport.html', 'w') as f: + f.write(output) + +print(f'Report generated in reports/{prefix}_desc-casereport.html') diff --git a/chopping_block/compare_2cxm.py b/chopping_block/compare_2cxm.py deleted file mode 100644 index b6c43db..0000000 --- a/chopping_block/compare_2cxm.py +++ /dev/null @@ -1,53 +0,0 @@ -import nibabel as nib -import numpy as np -import matplotlib.pyplot as plt - - -cpu_file = '/media/network_mriphysics/GRASP/500181/DCE_2cxm_cpu/DCEBBB_flip_500181-2slices_2cxm_fit_Ktrans.nii' -gpu_file = '/media/network_mriphysics/GRASP/500181/DCE_2cxm/DCEBBB_flip_500181-2slices_2cxm_fit_Ktrans.nii' - -print('Processing file: '+cpu_file) -cpu_img = nib.load(cpu_file) -cpu_img_data = cpu_img.get_data() - -print('Processing file: '+gpu_file) -gpu_img = nib.load(gpu_file) -gpu_img_data = gpu_img.get_data() - - -gpu_filter = gpu_img_data[cpu_img_data>0] -cpu_filter = cpu_img_data[cpu_img_data>0] -cpu_filter = cpu_filter[gpu_filter>0] -gpu_filter = gpu_filter[gpu_filter>0] - -gpu_filter = gpu_filter[cpu_filter<0.1] -cpu_filter = cpu_filter[cpu_filter<0.1] -cpu_filter = cpu_filter[gpu_filter<0.1] -gpu_filter = gpu_filter[gpu_filter<0.1] - -gpu_small = gpu_filter[::50] -cpu_small = cpu_filter[::50] - -print("Total voxels: ",gpu_img_data.size) -print("Filtered voxels (median): ",cpu_filter.size) -print("Plot voxels: ",cpu_small.size) - -difference = np.subtract(gpu_filter,cpu_filter) -diff_abs = np.abs(difference) -diff_median = np.median(diff_abs) -diff_mean = np.mean(diff_abs) -print("Median difference: ",diff_median) -print("Mean difference: ",diff_mean) - -plt.rcParams.update({'font.size': 16}) -plt.figure() -ax = plt.axes() -plt.scatter(cpu_small,gpu_small,marker='o',s=10) -#plt.plot(age_list_short,naa_list_short,'o', xx, yy) -plt.title('2CXM DCE Fitting') -plt.xlabel('Ktrans (CPU - Matlab)') -plt.ylabel('Ktrans (GPU - GPUFit)') -ax.set_ylim([0, 0.1]) -ax.set_xlim([0, 0.1]) - -plt.show() \ No newline at end of file diff --git a/chopping_block/compare_dixon.py b/chopping_block/compare_dixon.py deleted file mode 100644 index 9046a79..0000000 --- a/chopping_block/compare_dixon.py +++ /dev/null @@ -1,51 +0,0 @@ -import nibabel as nib -import numpy as np -import matplotlib.pyplot as plt - - -cpu_file = '/media/network_mriphysics/HAT_data/LLU/15110842 MR2/6echo_processed/6echo_fat_fraction.nii.gz' -gpu_file = '/media/network_mriphysics/HAT_data/LLU/15110842 MR2/6echo_processed_gpu/6echo_fat_fraction.nii.gz' - -print('Processing file: '+cpu_file) -cpu_img = nib.load(cpu_file) -cpu_img_data = cpu_img.get_data() - -print('Processing file: '+gpu_file) -gpu_img = nib.load(gpu_file) -gpu_img_data = gpu_img.get_data() - - -gpu_filter = gpu_img_data[cpu_img_data>0] -cpu_filter = cpu_img_data[cpu_img_data>0] -gpu_filter = gpu_filter[cpu_filter<100] -cpu_filter = cpu_filter[cpu_filter<100] - -#cpu_filter = cpu_filter[gpu_filter<0.1] -#gpu_filter = gpu_filter[gpu_filter<0.1] - -gpu_small = gpu_filter[::500] -cpu_small = cpu_filter[::500] - -print("Total voxels: ",gpu_img_data.size) -print("Filtered voxels (median): ",cpu_filter.size) -print("Plot voxels: ",cpu_small.size) - -difference = np.subtract(gpu_filter,cpu_filter) -diff_abs = np.abs(difference) -diff_median = np.median(diff_abs) -diff_mean = np.mean(diff_abs) -print("Median difference: ",diff_median) -print("Mean difference: ",diff_mean) - -plt.rcParams.update({'font.size': 16}) -plt.figure() -ax = plt.axes() -plt.scatter(cpu_small,gpu_small,marker='o',s=10) -#plt.plot(age_list_short,naa_list_short,'o', xx, yy) -plt.title('6 echo Dixon Fitting') -plt.xlabel('Fat Fraction (CPU - SciPy)') -plt.ylabel('Fat Fraction (GPU - GPUFit)') -ax.set_ylim([0, 60]) -ax.set_xlim([0, 60]) - -plt.show() \ No newline at end of file diff --git a/chopping_block/compare_etofts.py b/chopping_block/compare_etofts.py deleted file mode 100644 index 6d0479b..0000000 --- a/chopping_block/compare_etofts.py +++ /dev/null @@ -1,59 +0,0 @@ -import nibabel as nib -import numpy as np -import matplotlib.pyplot as plt - - -cpu_file = '/media/network_mriphysics/GRASP/500181/DCE_etofts_cpu/DCEBBB_flip_500181-2slices_ex_tofts_fit_Ktrans.nii' -gpu_file = '/media/network_mriphysics/GRASP/500181/DCE_etofts_gpu_contraints/DCEBBB_flip_500181-2slices_ex_tofts_fit_Ktrans.nii' - -cpu_file = '/media/network_mriphysics/GRASP/500181/DCE_etofts_cpu/DCEBBB_flip_500181-2slices_ex_tofts_fit_vp.nii' -gpu_file = '/media/network_mriphysics/GRASP/500181/DCE_etofts_gpu_contraints/DCEBBB_flip_500181-2slices_ex_tofts_fit_vp.nii' - -print('Processing file: '+cpu_file) -cpu_img = nib.load(cpu_file) -cpu_img_data = cpu_img.get_data() - -print('Processing file: '+gpu_file) -gpu_img = nib.load(gpu_file) -gpu_img_data = gpu_img.get_data() - - -gpu_filter = gpu_img_data[cpu_img_data>0.0001] -cpu_filter = cpu_img_data[cpu_img_data>0.0001] -cpu_filter = cpu_filter[gpu_filter>0.0001] -gpu_filter = gpu_filter[gpu_filter>0.0001] - -gpu_filter = gpu_filter[cpu_filter<0.99] -cpu_filter = cpu_filter[cpu_filter<0.99] -cpu_filter = cpu_filter[gpu_filter<0.99] -gpu_filter = gpu_filter[gpu_filter<0.99] - -gpu_small = gpu_filter[::50] -cpu_small = cpu_filter[::50] - -print("Total voxels: ",gpu_img_data.size) -print("Filtered voxels (median): ",cpu_filter.size) -print("Plot voxels: ",cpu_small.size) - -difference = np.subtract(gpu_filter,cpu_filter) -diff_abs = np.abs(difference) -diff_percent = np.divide(difference,cpu_filter) -diff_percent_mean = np.mean(diff_percent)*100 -diff_median = np.median(diff_abs) -diff_mean = np.mean(diff_abs) -print("Median difference: ",diff_median) -print("Mean difference: ",diff_mean) -print("Mean percent diff: ",diff_percent_mean,"%") - -plt.rcParams.update({'font.size': 16}) -plt.figure() -ax = plt.axes() -plt.scatter(cpu_small,gpu_small,marker='o',s=10) -#plt.plot(age_list_short,naa_list_short,'o', xx, yy) -plt.title('Extended Tofts DCE Fitting') -plt.xlabel('Ktrans (CPU - Matlab)') -plt.ylabel('Ktrans (GPU - GPUFit)') -ax.set_ylim([0, 0.5]) -ax.set_xlim([0, 0.5]) - -plt.show() diff --git a/chopping_block/compare_gpu_cpu_dce.m b/chopping_block/compare_gpu_cpu_dce.m deleted file mode 100644 index fdd6a3b..0000000 --- a/chopping_block/compare_gpu_cpu_dce.m +++ /dev/null @@ -1,756 +0,0 @@ -%% INPUTS -%----------- -% Human CBF = 22-55 ml/min/100ml (Leenders et al Brain 1990) -% Human CBV = 2.7-8.6% (Leenders et al Brain 1990) -% Human CBV = 1.3%,2.6% WM,GM (Sourbron et al MRM 2009) -% Rat CBF in parietal cortex = 129�18 ml/100g/min (Adam et al JCBFM 2003) -% Rat CBV in parietal cortex = 2.1�0.38 ml/100g (Adam et al JCBFM 2003) -% Human/Rat Ve Brain = 5% - 9% (He MRM 2007, Bender MRM 2009) -% Human/Rat Ve Brain = 15% - 30% (Sykova Physiol Rev 2008) -% Ktrans health brain = 0.5-3*10^-3/min (Taheri 2011 and our data) -% Ktrans rat brain = 0 - 0.6*10^-3/min (Ewing 2003) -% Ktrans glioma = 10-50*10^-3/min (Choi 2013) -snr_adjust = 0; -double_fit = 1; -average_across_offset = true; -show_plots = true; -save_data = true; - -% Full Run -% ktrans_list = logspace(-1,1.7,20).*10^-3; % in /min or ml/min/ml -% vp_list = [0.01 0.02 0.04 0.08]; % in volume fraction (ml/ml) -% ve_list = [0.03 0.1 0.3]; % in volume fraction (ml/ml) -% fp_list = [0.20 0.60]; % in ml/min/ml or /min -% % PS = derived from Fp and Ktrans = (Fp*PS)/(Fp + PS) -% ta_list = [5 15 30]; % in minutes -% snr_list = [30 300]; % SNR of the pre SI -% noise_repeats = 200; -% time_resolution_list = [0.5 1.0 15.4 90]; % in seconds (15.4 human) -% baseline_time = 91; % in seconds, time to collect baseline images - -% GPU ETofts Run -ktrans_list = [10 20 30 40 50 60 70 80 90].*10^-3; % in /min or ml/min/ml -vp_list = [0.05]; % in volume fraction (ml/ml) -ve_list = [0.4]; % in volume fraction (ml/ml) -fp_list = [1]; % in ml/min/ml or /min -% PS = derived from Fp and Ktrans = (Fp*PS)/(Fp + PS) -ta_list = [5]; % in minutes -snr_list = [5 50]; % SNR of the pre SI -noise_repeats = 100; -time_resolution_list = [1]; % in seconds (15.4 human) -time_offset_list= [0]; -baseline_time = 15; % in seconds, time to collect baseline images - -% number_cpus = 4; -fit_dce_model ='ex_tofts'; % used to fit the tissue curve - % tofts, ex_tofts, 2cxm, patlak, - % tissue_uptake, fxr, auc -gen_dce_model ='2cxm'; % used to generate the tissue curve - % ex_tofts, patlak, 2cxm, 2cxm_binding -aif_curve = 'parker_multihance'; % 'tofts' 'usc' 'parker' - % 'parker_multihance' 'exponential' -max_gd_aif = 2; % in mmol, exponential model only -gd_decay_time = 10; % in minutes, exponential model only - -aif_t1_pre = 1200; % in ms -tissue_t1_pre = 1800; % in ms -alpha = 1000; % arbitrary scalar for SI -tr = 8.3; % in ms -fa = 15; % in degrees -relaxivity = 5.5; % in /mM/sec -% baseline_images = ; % number of collected baseline images -Ka = 1.5; % binding constant in /mM, for 2cxm_binding - -time_resolution_list = time_resolution_list./60;%convert to minutes -baseline_time = baseline_time./60; %convert to minutes -time_offset_list = time_offset_list./-60; %convert to minutes -% ROI_size = 100; -% snr_list = [snr_list; snr_list*sqrt(ROI_size)]; -%----------- - -%% Processing -% Sanity check -if max(ktrans_list)>=min(fp_list) - error('Ktrans cannot be greater than Fp'); -end -if max(time_resolution_list)>baseline_time - error('Baseline shorter than time resolution, would result in zero baseline images'); -end - -disp('Starting Simulation') -disp(datestr(now)) -disp(' '); -tic - -% Inner variables can all be run with a single call to the fitting function -inner_variable_sizes = [length(ktrans_list) length(vp_list) length(ve_list) length(fp_list)]; -length_inner_variables = length(ktrans_list)*length(vp_list)*length(ve_list)*length(fp_list); -% Outer variables involve changes to the AIF and therefore require multiple -% calls to the fitting function -outer_variable_sizes = [noise_repeats length(ta_list) length(time_resolution_list) length(time_offset_list) length(snr_list)]; -length_outer_variables = noise_repeats*length(ta_list)*length(time_resolution_list)*length(time_offset_list)*length(snr_list); -toffset_location = numel(inner_variable_sizes)+4; -repeats_location = numel(inner_variable_sizes)+1; - -% number_time_arrays = length(ta_list)*length(time_resolution_list); -% time_array_length = 0; -% time_array_list = cell(number_time_arrays,1); -% for i=1:number_time_arrays -% [ta_index, tres_index] = ind2sub([length(ta_list) length(time_resolution_list)],i); -% ta = ta_list(ta_index); -% time_resolution = time_resolution_list(tres_index); -% -% time_array_list{i} = 0:time_resolution:ta; -% time_array_list{i} = time_array_list{i}+time_offset; -% time_array_list{i}(1) = 0; -% if length(time_array_list{i})>time_array_length -% time_array_length = length(time_array_list{i}); -% end -% end -% All time arrays need to be the same size, pad with zeros -% for i=1:length(ta_list) -% time_array_list{i} = cat(2,zeros(1,time_array_length-length(time_array_list{i})),time_array_list{i}); -% end - -% Reserve Space for results -exponential_ktrans = zeros(length_inner_variables,length_outer_variables); -exponential_vp = zeros(length_inner_variables,length_outer_variables); -exponential_ve = zeros(length_inner_variables,length_outer_variables); -exponential_fp = zeros(length_inner_variables,length_outer_variables); - -exponential_residual = zeros(length_inner_variables,length_outer_variables); -exponential_ktrans_95ci = zeros(length_inner_variables,length_outer_variables,2); -exponential_vp_95ci = zeros(length_inner_variables,length_outer_variables,2); -exponential_ve_95ci = zeros(length_inner_variables,length_outer_variables,2); -exponential_fp_95ci = zeros(length_inner_variables,length_outer_variables,2); - -if double_fit - d_exponential_ktrans = zeros(length_inner_variables,length_outer_variables); - d_exponential_vp = zeros(length_inner_variables,length_outer_variables); - d_exponential_ve = zeros(length_inner_variables,length_outer_variables); - d_exponential_fp = zeros(length_inner_variables,length_outer_variables); - - d_exponential_residual = zeros(length_inner_variables,length_outer_variables); - d_exponential_ktrans_95ci = zeros(length_inner_variables,length_outer_variables,2); - d_exponential_vp_95ci = zeros(length_inner_variables,length_outer_variables,2); - d_exponential_ve_95ci = zeros(length_inner_variables,length_outer_variables,2); - d_exponential_fp_95ci = zeros(length_inner_variables,length_outer_variables,2); -end - - -% Launch pool if not already running, then disable warnings -poolobj = gcp; -pctRunOnAll warning 'off' - - -% pp = ProgressBar(length_inner_variables*length_outer_variables); -barWidth= int32( 100/3 ); -if length_outer_variables>10000 - progessbar_outer = 1; -else - progessbar_outer = 0; -end - -if progessbar_outer - if length_outer_variables>1000000 - warning('timed progress bar could significantly slow simulation'); - end - pp = TimedProgressBar( length_outer_variables, barWidth, ... - 'Time Remaining: ', ', completed ', 'Concluded in ' ); -else - if length_inner_variables*length_outer_variables>1000000 - warning('timed progress bar could significantly slow simulation'); - end - pp = TimedProgressBar( length_inner_variables*length_outer_variables, barWidth, ... - 'Time Remaining: ', ', completed ', 'Concluded in ' ); -end - -% Outer loop is over variables that require changes to the AIF (noise, TA, -% time res, SNR) -for j=1:length_outer_variables - if progessbar_outer - pp.progress; - end - [repeats_index, ta_index, tres_index, toffset_index, snr_index] = ind2sub(outer_variable_sizes,j); - ta = ta_list(ta_index); - time_resolution = time_resolution_list(tres_index); - time_offset = time_offset_list(toffset_index); - snr_level = snr_list(snr_index); - - % For time resolution simulation - if snr_adjust - if time_resolution==0.25 - snr_level = 30; - elseif time_resolution==1.0 - snr_level = 60; - elseif time_resolution==4.0 - snr_level = 120; - end - end - - % The curve generation and down sampling is time consuming, do not - % repeat if doing a noise repeat - if repeats_index==1 - time_array = 0:time_resolution:ta; - time_array = time_array+time_offset; - time_array(1) = 0; - - baseline_images = floor(baseline_time/time_resolution_list(tres_index)); - % Reserve Space for tissue matrix - % if j==1 - % noise_stdv = zeros(length(time_array),1); - % noise_stdv_tissue = zeros(length(time_array),length_inner_variables); - % noise_stdv_tissue_avg = zeros(length(time_array)/6,length_inner_variables); - % end - % - % tissue_si_matrix = zeros(length(time_array),length_inner_variables); - % tissue_si_noisy_matrix = zeros(length(time_array),length_inner_variables); - tissue_gd_matrix = zeros(length(time_array),length_inner_variables); - tissue_gd_matrix_noisy = zeros(length(time_array),length_inner_variables); - - % Calculate AIF concentration and SI - if strcmp(aif_curve, 'tofts') - % Tofts JMRI 1997 published AIF - dose = 0.05; % in mmole/kg - A = dose * 3.99; - B = dose * 4.78; - c = 0.144; - d = 0.011; - aif_function = @(t) (A*exp(-c*t)+B*exp(-d*t)).*logical(t); - elseif strcmp(aif_curve, 'usc') - % USC Fitted AIF - A = 1.0372; - B = 0.8375; - c = 0.0155; - d = 0.5115; - aif_function = @(t) (A*exp(-c*t)+B*exp(-d*t)).*logical(t); - elseif strcmp(aif_curve, 'parker') - % Parker et al. MRM 2006 - A_aif_1 = 0.809; - sigma_aif_1 = 0.0563; - T_aif_1 = 0.17046; - A_aif_2 = 0.330; - sigma_aif_2 = 0.132; - T_aif_2 = 0.365; - alpha_aif = 1.050; - beta_aif = 0.1685; - s_aif = 38.078; - tau_aif = 0.483; - - aif_function = @(t) (A_aif_1./(sigma_aif_1.*sqrt(2.*pi())).*exp(-(t-T_aif_1).^2./(2.*sigma_aif_1.^2)) + ... - A_aif_2./(sigma_aif_2.*sqrt(2.*pi()))*exp(-(t-T_aif_2).^2./(2.*sigma_aif_2.^2)) + ... - alpha_aif.*exp(-beta_aif.*t)./(1+exp(-s_aif.*(t-tau_aif))) ).*logical(t); - elseif strcmp(aif_curve, 'parker_multihance') - % Multihance estimate using Parker et al. MRM 2006 as baseline - A_aif_1 = 0.409; % - sigma_aif_1 = 0.0563; - T_aif_1 = 0.17046; - A_aif_2 = 0.160; % - sigma_aif_2 = 0.132; - T_aif_2 = 0.365; - alpha_aif = 0.6646; % - beta_aif = 0.5819; - alpha_aif_2 = 0.6695; % - beta_aif_2 = 0.0175; % - s_aif = 38.078; - tau_aif = 0.483; - - aif_function = @(t) (A_aif_1./(sigma_aif_1.*sqrt(2.*pi())).*exp(-(t-T_aif_1).^2./(2.*sigma_aif_1.^2)) + ... - A_aif_2./(sigma_aif_2.*sqrt(2.*pi()))*exp(-(t-T_aif_2).^2./(2.*sigma_aif_2.^2)) + ... - alpha_aif.*exp(-beta_aif.*t)./(1+exp(-s_aif.*(t-tau_aif))) + ... - alpha_aif_2.*exp(-beta_aif_2.*t)./(1+exp(-s_aif.*(t-tau_aif))) ).*logical(t); - elseif strcmp(aif_curve, 'exponential') - % Simple exponential with parameters specified above - aif_function = @(t) (max_gd_aif*exp(-t/gd_decay_time)).*logical(t); - else - error([aif_curve ' is not a valid AIF curve']); - end - - % aif_gd = arrayfun(aif_function,time_array); - aif_gd = down_sample(aif_function,time_array,0.1/60); - aif_t1_post = 1./(1/aif_t1_pre+aif_gd.*(relaxivity*1/1000)); - aif_si_pre = sind(fa)*alpha*(1-exp(-tr/aif_t1_pre))/(1-cosd(fa)*exp(-tr/aif_t1_pre)); - aif_si_post = sind(fa).*alpha.*(1-exp(-tr./aif_t1_post))./(1-cosd(fa).*exp(-tr./aif_t1_post)); - end - - % Add noise to SI, noise is in the real and img channels when - % aquired, the mag is then taken for the image, this gives the noise - % (which is white Gaussian for the acquisition) a Rician distribution - noise_real = normrnd(0,mean(aif_si_pre)/snr_level,size(aif_si_post)); - noise_img = normrnd(0,mean(aif_si_pre)/snr_level,size(aif_si_post)); - aif_si_post_noisy = sqrt( (aif_si_post+noise_real).^2 + noise_img.^2); - noise_real = normrnd(0,mean(aif_si_pre)/(snr_level*sqrt(baseline_images)),size(aif_si_pre)); - noise_img = normrnd(0,mean(aif_si_pre)/(snr_level*sqrt(baseline_images)),size(aif_si_pre)); - aif_si_pre_noisy = sqrt( (aif_si_pre+noise_real).^2 + noise_img.^2); - - % Now get the noisy aif concentration - sstar = (1-exp(-tr/aif_t1_pre))/(1-cosd(fa)*exp(-tr/aif_t1_pre)); - si_ratio_noisy = aif_si_post_noisy./aif_si_pre_noisy; - aif_r1_post_noisy = -1./tr*log((sstar.*si_ratio_noisy-1)./(si_ratio_noisy.*sstar.*cosd(fa)-1)); - aif_gd_noisy = (aif_r1_post_noisy-1/aif_t1_pre)/(relaxivity/1000); - %Remove any complex points (introduced if lots of noise) - max_aif = max(real(aif_gd_noisy)); - aif_gd_noisy(aif_gd_noisy~=real(aif_gd_noisy)) = max_aif; - -% noise_stdv(:,1) = ((aif_si_post-aif_si_post_noisy).^2)'+noise_stdv(:,1); - - % Inner loop is for variables that just change the tissue curve - % (ktrans, Vp, Ve), this loop generates tissue curves, and caches - % them for future noise repeats - for i=1:length_inner_variables - if ~progessbar_outer - pp.progress; - end - - [k_index, vp_index, ve_index, Fp_index] = ind2sub(inner_variable_sizes,i); - ktrans = ktrans_list(k_index); - vp = vp_list(vp_index); - ve = ve_list(ve_index); - Fp = fp_list(Fp_index); - PS = ktrans*Fp/(Fp-ktrans); - - % The curve generation and down sampling is time consuming, only - % caclulate on first noise repeat and cache values - if repeats_index==1 - % Calculate Tissue SI - if strcmp(gen_dce_model, 'ex_tofts') - integral_function = @(u,t) aif_function(u).*exp(-ktrans*(t-u)/ve); - tissue_function = @(t) ktrans*integral(@(u)integral_function(u,t),0,t)+vp*aif_function(t); - elseif strcmp(gen_dce_model, 'patlak') - tissue_function = @(t) ktrans*integral(@(u)aif_function(u),0,t)+vp*aif_function(t); - elseif strcmp(gen_dce_model, '2cxm') - E = PS/(PS+Fp); - e = ve/(vp+ve); - tau_plus = (E-E*e+e)/(2*E)*(1+sqrt(1-(4*E*e*(1-E)*(1-e))/(E-E*e+e)^2)); - tau_minus = (E-E*e+e)/(2*E)*(1-sqrt(1-(4*E*e*(1-E)*(1-e))/(E-E*e+e)^2)); - k_plus = Fp/((vp+ve)*tau_minus); - k_minus = Fp/((vp+ve)*tau_plus); - F_plus = 1*Fp*(tau_plus-1)/(tau_plus-tau_minus); - F_minus = -1*Fp*(tau_minus-1)/(tau_plus-tau_minus); - integral_function = @(u,t) aif_function(u).*(F_plus*exp(-k_plus*(t-u)) + F_minus*exp(-k_minus*(t-u))); - tissue_function = @(t) integral(@(u)integral_function(u,t),0,t); - elseif strcmp(gen_dce_model, '2cxm_binding') - Cinit = [0,0]; % initial value for Plasma and EEV - - % write down the expressions for the fluxes - % C(1) = concentration plasma C(2) = concentration EEV - Pi = @(t,C) PS.*C(2) + Fp.*aif_function(t); % influx to Plasma - Pe = @(t,C,ub) PS*ub.*C(1) + Fp.*C(1); % efflux from Plasma - Ei = @(t,C,ub) PS*ub.*C(1); % influx to EEV - Ee = @(t,C) PS.*C(2); % efflux from EEV - - % solve the differential equation - ode_solution = ode45(@TwoCompModelBinding,[0 ta],Cinit,[],Pi,Pe,Ei,Ee,vp,ve,Ka); - - % Get tissue curve - c_compartments = deval(ode_solution,time_array); - c_tissue = vp.*c_compartments(1,:)+ve.*c_compartments(2,:); - else - error([gen_dce_model ' is not a valid generation model']); - end - - if strcmp(gen_dce_model, '2cxm_binding') - tissue_gd_matrix(:,i) = c_tissue; - else - % tissue_gd_matrix(:,i) = arrayfun(tissue_function,time_array); - tissue_gd_matrix(:,i) = down_sample(tissue_function,time_array,1/60); - end - end - - tissue_t1_post = 1./(1/tissue_t1_pre+tissue_gd_matrix(:,i).*(relaxivity*1/1000)); - tissue_si_pre = sind(fa)*alpha*(1-exp(-tr/tissue_t1_pre))/(1-cosd(fa)*exp(-tr/tissue_t1_pre)); - tissue_si_post = sind(fa).*alpha.*(1-exp(-tr./tissue_t1_post))./(1-cosd(fa).*exp(-tr./tissue_t1_post)); - - % Add noise to SI, noise is in the real and img channels when - % aquired, the mag is then taken for the image, this gives the noise - % (which is white Gaussian for the acquisition) a Rician distribution - noise_real_post = normrnd(0,mean(tissue_si_pre)/snr_level,size(tissue_si_post)); - noise_img_post = normrnd(0,mean(tissue_si_pre)/snr_level,size(tissue_si_post)); - tissue_si_post_noisy = sqrt( (tissue_si_post+noise_real_post).^2 + noise_img_post.^2); - noise_real = normrnd(0,mean(tissue_si_pre)/(snr_level*sqrt(baseline_images)),size(tissue_si_pre)); - noise_img = normrnd(0,mean(tissue_si_pre)/(snr_level*sqrt(baseline_images)),size(tissue_si_pre)); - tissue_si_pre_noisy = sqrt( (tissue_si_pre+noise_real).^2 + noise_img.^2); - - % Now get the noisy tissue concentration - sstar = (1-exp(-tr/tissue_t1_pre))/(1-cosd(fa)*exp(-tr/tissue_t1_pre)); - si_ratio_noisy = tissue_si_post_noisy./tissue_si_pre_noisy; - tissue_r1_post_noisy = -1./tr*log((sstar.*si_ratio_noisy-1)./(si_ratio_noisy.*sstar.*cosd(fa)-1)); - tissue_gd_matrix_noisy(:,i) = (tissue_r1_post_noisy-1/tissue_t1_pre)/(relaxivity/1000); -% figure(1) -% plot(tissue_gd_matrix(:,i)); -% tissue_si_matrix(:,i) = tissue_si_post; -% tissue_si_noisy_matrix(:,i) = tissue_si_post_noisy; -% noise_stdv_tissue(:,i) = ((tissue_si_post-tissue_si_post_noisy).^2)+noise_stdv_tissue(:,i); - end - - - % A single call returns all the inner variable fits - roi_data{1}.Cp = aif_gd_noisy; - roi_data{1}.timer = time_array'; - roi_data{1}.Ct = tissue_gd_matrix_noisy; - -% roi_data{1}.Cp = decimate(aif_gd_noisy,4); -% roi_data{1}.timer = decimate(time_array',4); -% Ct_downsampled = zeros(size(roi_data{1}.Cp,2),size(tissue_gd_matrix_noisy,2)); -% for ii=1:size(tissue_gd_matrix_noisy,2) -% Ct_downsampled(:,ii) = decimate(tissue_gd_matrix_noisy(:,ii),4); -% end -% roi_data{1}.Ct = Ct_downsampled; - -% n = 6; -% si_avg = reshape(mean(reshape(tissue_si_matrix,[n prod(size(tissue_si_matrix))/n])), [size(tissue_si_matrix,1)/n size(tissue_si_matrix,2)]); -% si_noise_avg = reshape(mean(reshape(tissue_si_noisy_matrix,[n prod(size(tissue_si_noisy_matrix))/n])), [size(tissue_si_noisy_matrix,1)/n size(tissue_si_noisy_matrix,2)]); -% noise_stdv_tissue_avg = ((si_avg-si_noise_avg).^2)+noise_stdv_tissue_avg; -% -% roi_data{1}.Cp = reshape(mean(reshape(aif_gd_noisy',[n prod(size(aif_gd_noisy'))/n])), [size(aif_gd_noisy',1)/n size(aif_gd_noisy',2)])'; -% roi_data{1}.timer = reshape(mean(reshape(time_array',[n prod(size(time_array'))/n])), [size(time_array',1)/n size(time_array',2)]); -% roi_data{1}.Ct = reshape(mean(reshape(tissue_gd_matrix_noisy,[n prod(size(tissue_gd_matrix_noisy))/n])), [size(tissue_gd_matrix_noisy,1)/n size(tissue_gd_matrix_noisy,2)]); - [roi_results, roi_residuals] = FXLfit_generic(roi_data, length_inner_variables, fit_dce_model, 0); - - % Save results - if strcmp(fit_dce_model, 'tofts') - exponential_ktrans(:,j) = roi_results(:,1); - exponential_ve(:,j) = roi_results(:,2); - exponential_residual(:,j) = roi_results(:,3); - exponential_ktrans_95ci(:,j,:) = [roi_results(:,4) roi_results(:,5)]; - exponential_ve_95ci(:,j,:) = [roi_results(:,6) roi_results(:,7)]; - paramname = {'Ktrans'; 've'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'}; - elseif strcmp(fit_dce_model, '2cxm') - exponential_ktrans(:,j) = roi_results(:,1); - exponential_ve(:,j) = roi_results(:,2); - exponential_vp(:,j) = roi_results(:,3); - exponential_fp(:,j) = roi_results(:,4); - exponential_residual(:,j) = roi_results(:,5); - exponential_ktrans_95ci(:,j,:) = [roi_results(:,6) roi_results(:,7)]; - exponential_ve_95ci(:,j,:) = [roi_results(:,8) roi_results(:,9)]; - exponential_vp_95ci(:,j,:) = [roi_results(:,10) roi_results(:,11)]; - exponential_fp_95ci(:,j,:) = [roi_results(:,12) roi_results(:,13)]; - paramname = {'Ktrans'; 've'; 'vp';'fp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'; 'vp_ci_low'; 'vp_ci_high'; 'fp_ci_low'; 'fp_ci_high'}; - elseif strcmp(fit_dce_model, 'ex_tofts') || strcmp(fit_dce_model, 'nested') - exponential_ktrans(:,j) = roi_results(:,1); - exponential_ve(:,j) = roi_results(:,2); - exponential_vp(:,j) = roi_results(:,3); - exponential_residual(:,j) = roi_results(:,4); - exponential_ktrans_95ci(:,j,:) = [roi_results(:,5) roi_results(:,6)]; - exponential_ve_95ci(:,j,:) = [roi_results(:,7) roi_results(:,8)]; - exponential_vp_95ci(:,j,:) = [roi_results(:,9) roi_results(:,10)]; - paramname = {'Ktrans'; 've'; 'vp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'; 'vp_ci_low'; 'vp_ci_high'}; - elseif strcmp(fit_dce_model, 'tissue_uptake') - exponential_ktrans(:,j) = roi_results(:,1); - exponential_ve(:,j) = roi_results(:,2); %actually fp - exponential_vp(:,j) = roi_results(:,3); - exponential_residual(:,j) = roi_results(:,4); - exponential_ktrans_95ci(:,j,:) = [roi_results(:,5) roi_results(:,6)]; - exponential_ve_95ci(:,j,:) = [roi_results(:,7) roi_results(:,8)]; - exponential_vp_95ci(:,j,:) = [roi_results(:,9) roi_results(:,10)]; - paramname = {'Ktrans'; 'fp'; 'vp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 'fp_ci_low';'fp_ci_high'; 'vp_ci_low'; 'vp_ci_high'}; - elseif strcmp(fit_dce_model, 'patlak') || strcmp(fit_dce_model, 'patlak_linear') - exponential_ktrans(:,j) = roi_results(:,1); - exponential_vp(:,j) = roi_results(:,2); - exponential_residual(:,j) = roi_results(:,3); - exponential_ktrans_95ci(:,j,:) = [roi_results(:,4) roi_results(:,5)]; - exponential_vp_95ci(:,j,:) = [roi_results(:,6) roi_results(:,7)]; - paramname = {'Ktrans'; 'vp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 'vp_ci_low'; 'vp_ci_high'}; - elseif strcmp(fit_dce_model, 'fxr') - exponential_ktrans(:,j) = roi_results(:,1); - exponential_ve(:,j) = roi_results(:,2); - exponential_vp(:,j) = roi_results(:,3); - exponential_residual(:,j) = roi_results(:,4); - exponential_ktrans_95ci(:,j,:) = [roi_results(:,5) roi_results(:,6)]; - exponential_ve_95ci(:,j,:) = [roi_results(:,7) roi_results(:,8)]; - exponential_vp_95ci(:,j,:) = [roi_results(:,9) roi_results(:,10)]; - paramname = {'Ktrans'; 've'; 'tau'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'; 'tau_ci_low'; 'tau_ci_high'}; - elseif strcmp(fit_dce_model, 'auc') - if quant - paramname = {'AUCc'; 'AUCs'; 'NAUCc'; 'NAUCs'}; - else - paramname = {'AUCs'; 'NAUCs'}; - end - else - % Error - error('Model not supported'); - end - - if double_fit - [roi_results, roi_residuals] = FXLfit_generic(roi_data, length_inner_variables, fit_dce_model, 0, 1); - - if strcmp(fit_dce_model, 'tofts') - d_exponential_ktrans(:,j) = roi_results(:,1); - d_exponential_ve(:,j) = roi_results(:,2); - d_exponential_residual(:,j) = roi_results(:,3); - d_exponential_ktrans_95ci(:,j,:) = [roi_results(:,4) roi_results(:,5)]; - d_exponential_ve_95ci(:,j,:) = [roi_results(:,6) roi_results(:,7)]; - d_paramname = {'Ktrans'; 've'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'}; - elseif strcmp(fit_dce_model, '2cxm') - d_exponential_ktrans(:,j) = roi_results(:,1); - d_exponential_ve(:,j) = roi_results(:,2); - d_exponential_vp(:,j) = roi_results(:,3); - d_exponential_fp(:,j) = roi_results(:,4); - d_exponential_residual(:,j) = roi_results(:,5); - d_exponential_ktrans_95ci(:,j,:) = [roi_results(:,6) roi_results(:,7)]; - d_exponential_ve_95ci(:,j,:) = [roi_results(:,8) roi_results(:,9)]; - d_exponential_vp_95ci(:,j,:) = [roi_results(:,10) roi_results(:,11)]; - d_exponential_fp_95ci(:,j,:) = [roi_results(:,12) roi_results(:,13)]; - d_paramname = {'Ktrans'; 've'; 'vp';'fp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'; 'vp_ci_low'; 'vp_ci_high'; 'fp_ci_low'; 'fp_ci_high'}; - elseif strcmp(fit_dce_model, 'ex_tofts') || strcmp(fit_dce_model, 'nested') - d_exponential_ktrans(:,j) = roi_results(:,1); - d_exponential_ve(:,j) = roi_results(:,2); - d_exponential_vp(:,j) = roi_results(:,3); - d_exponential_residual(:,j) = roi_results(:,4); - d_exponential_ktrans_95ci(:,j,:) = [roi_results(:,5) roi_results(:,6)]; - d_exponential_ve_95ci(:,j,:) = [roi_results(:,7) roi_results(:,8)]; - d_exponential_vp_95ci(:,j,:) = [roi_results(:,9) roi_results(:,10)]; - d_paramname = {'Ktrans'; 've'; 'vp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 've_ci_low';'ve_ci_high'; 'vp_ci_low'; 'vp_ci_high'}; - elseif strcmp(fit_dce_model, 'tissue_uptake') - d_exponential_ktrans(:,j) = roi_results(:,1); - d_exponential_ve(:,j) = roi_results(:,2); %actually fp - d_exponential_vp(:,j) = roi_results(:,3); - d_exponential_residual(:,j) = roi_results(:,4); - d_exponential_ktrans_95ci(:,j,:) = [roi_results(:,5) roi_results(:,6)]; - d_exponential_ve_95ci(:,j,:) = [roi_results(:,7) roi_results(:,8)]; - d_exponential_vp_95ci(:,j,:) = [roi_results(:,9) roi_results(:,10)]; - d_paramname = {'Ktrans'; 'fp'; 'vp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 'fp_ci_low';'fp_ci_high'; 'vp_ci_low'; 'vp_ci_high'}; - elseif strcmp(fit_dce_model, 'patlak') || strcmp(fit_dce_model, 'patlak_linear') - d_exponential_ktrans(:,j) = roi_results(:,1); - d_exponential_vp(:,j) = roi_results(:,2); - d_exponential_residual(:,j) = roi_results(:,3); - d_exponential_ktrans_95ci(:,j,:) = [roi_results(:,4) roi_results(:,5)]; - d_exponential_vp_95ci(:,j,:) = [roi_results(:,6) roi_results(:,7)]; - d_paramname = {'Ktrans'; 'vp'; 'residual'; 'ktrans_ci_low'; 'ktrans_ci_high'; 'vp_ci_low'; 'vp_ci_high'}; - end - end - - -end -pp.stop; - -%% Post Processing -% Verify Noise -% noise_stdv_sqrt = sqrt(noise_stdv/(length_outer_variables)); -% noise_stdv_sqrt_tissue = sqrt(noise_stdv_tissue/(length_outer_variables)); -% noise_stdv_sqrt_tissue_avg = sqrt(noise_stdv_tissue_avg/(length_outer_variables)); -% actual_SNR_aif = aif_si_post./noise_stdv_sqrt'; -% actual_SNR_tissue = tissue_si_matrix./noise_stdv_sqrt_tissue; -% actual_SNR_tissue_avg = si_avg./noise_stdv_sqrt_tissue_avg; - - -% actual_SNR = tissue_si_matrix./noise_stdv_sqrt; - -% Check if data falls in 95% CI -% exponential_ktrans_95 = zeros(length_inner_variables,1); -% exponential_vp_95 = zeros(length_inner_variables,1); -% exponential_ve_95 = zeros(length_inner_variables,1); -% -% for i=1:length_inner_variables -% for j=1:length_outer_variables -% if exponential_ktrans(i,j)>min(exponential_ktrans_95ci(i,j,:)) && ... -% exponential_ktrans(i,j)min(exponential_vp_95ci(i,j,:)) && ... -% exponential_vp(i,j)min(exponential_ve_95ci(i,j,:)) && ... -% exponential_ve(i,j)repeats_location - sz(repeats_location) = []; -end -exponential_ktrans_error_std = reshape(exponential_ktrans_error_std,sz); -exponential_vp_error_std = reshape(exponential_vp_error_std,sz); -exponential_ve_error_std = reshape(exponential_ve_error_std,sz); -exponential_fp_error_std = reshape(exponential_fp_error_std,sz); - -exponential_ktrans_average = reshape(exponential_ktrans_average,sz); -exponential_vp_average = reshape(exponential_vp_average,sz); -exponential_ve_average = reshape(exponential_ve_average,sz); -exponential_fp_average = reshape(exponential_fp_average,sz); - -exponential_ktrans_median = reshape(exponential_ktrans_median,sz); -exponential_vp_median = reshape(exponential_vp_median,sz); -exponential_ve_median = reshape(exponential_ve_median,sz); -exponential_fp_median = reshape(exponential_fp_median,sz); -% Reduce by one since the repeats dimension was removed -toffset_location = toffset_location-1; - -% Subtract out the true values to get the bias error -exponential_ktrans_error = bsxfun(@minus,exponential_ktrans_average,ktrans_list'); -% These don't work as the vp_list does not line up with the vp dimension -% exponential_vp_error = bsxfun(@minus,exponential_vp_average,vp_list'); -% exponential_ve_error = bsxfun(@minus,exponential_ve_average,ve_list'); -% exponential_fp_error = bsxfun(@minus,exponential_fp_average,fp_list'); - -exponential_ktrans_error_percent = bsxfun(@rdivide,exponential_ktrans_error,ktrans_list'); -% These don't work as the vp_list does not line up with the vp dimension -% exponential_vp_error_percent = bsxfun(@rdivide,exponential_vp_error,vp_list'); -% exponential_ve_error_percent = bsxfun(@rdivide,exponential_ve_error,ve_list'); -% exponential_fp_error_percent = bsxfun(@rdivide,exponential_fp_error,fp_list'); - -if average_across_offset - exponential_ktrans_error_std = nanmean(exponential_ktrans_error_std, toffset_location); - exponential_vp_error_std = nanmean(exponential_vp_error_std, toffset_location); - exponential_ve_error_std = nanmean(exponential_ve_error_std, toffset_location); - exponential_fp_error_std = nanmean(exponential_fp_error_std, toffset_location); - - exponential_ktrans_average = nanmean(exponential_ktrans_average, toffset_location); - exponential_vp_average = nanmean(exponential_vp_average, toffset_location); - exponential_ve_average = nanmean(exponential_ve_average, toffset_location); - exponential_fp_average = nanmean(exponential_fp_average, toffset_location); - - exponential_ktrans_median = nanmean(exponential_ktrans_median, toffset_location); - exponential_vp_median = nanmean(exponential_vp_median, toffset_location); - exponential_ve_median = nanmean(exponential_ve_median, toffset_location); - exponential_fp_median = nanmean(exponential_fp_median, toffset_location); - - exponential_ktrans_error = nanmean(exponential_ktrans_error, toffset_location); - exponential_ktrans_error_percent = nanmean(exponential_ktrans_error_percent, toffset_location); -end - -if double_fit - double_fit_gpu_run -end -% foo_std = exponential_ktrans_error_std.*1000; -% foo_mean = exponential_ktrans_average.*1000; -% exponential_ktrans_average = nanmean(exponential_ktrans_shape,number_variables); -% foo = squeeze(exponential_ktrans_average).*1000 -% exponential_ktrans_error_std = nanstd(exponential_ktrans_shape,0,number_variables); -% foo = squeeze(exponential_ktrans_error_std).*1000 - -if(save_data) - disp(' '); - sim_save_name = ['simulation_' datestr(now,'yy-mm-dd-HH-MM-SS')]; - save(sim_save_name); - disp(['Results saved to: ' sim_save_name]); -end -disp(' '); -disp('Finished'); -disp(datestr(now)) -toc - -%% Make Plots -number_colors = 2*max([length(vp_list) length(ve_list) length(fp_list) length(ta_list) length(time_resolution_list) length(snr_list)]); -if number_colors<=4 - number_colors = 6; -end -set(0,'DefaultAxesColorOrder',hot(number_colors)); -ve_plot = 1; -ta_plot = 1; -snr_plot = 1; -vp_plot = 1; -fp_plot = 1; -ktrans_plot = 1; -tres_plot = 1; - -if show_plots - % Variable Order: - % (Ktrans, Vp, Ve, Fp, TA, Time Res, SNR, noise repeats) - % ********* - - figure(1); - p = plot(ktrans_list.*1000,squeeze(exponential_ktrans_error_percent(:,vp_plot,ve_plot,fp_plot,ta_plot,tres_plot,:)).*100); - title([fit_dce_model ' GPU Accuracy Error'],'Interpreter','none'); - xlabel('Ktrans (10^{-3} * min^{-1})'); - ylabel('Accuracy Error (%)') - legend(cellfun(@num2str,num2cell(snr_list),'UniformOutput', 0)); - %ylim([0 50]) -% savefig('fig1-AccuracyPercent'); -% saveas(gcf,'fig1-AccuracyPercent', 'png') -% - - figure(2); - p = plot(ktrans_list.*1000,squeeze(d_exponential_ktrans_error_percent(:,vp_plot,ve_plot,fp_plot,ta_plot,tres_plot,:)).*100); - title([fit_dce_model ' CPU Accuracy Error'],'Interpreter','none'); - xlabel('Ktrans (10^{-3} * min^{-1})'); - ylabel('Accuracy Error (%)') - legend(cellfun(@num2str,num2cell(snr_list),'UniformOutput', 0)); - %ylim([0 50]) -% savefig('fig1-AccuracyPercent'); -% saveas(gcf,'fig1-AccuracyPercent', 'png') -% - - figure(3); - p = scatter(exponential_ktrans(:),d_exponential_ktrans(:)); - title([fit_dce_model ' GPU vs. CPU fit Ktrans'],'Interpreter','none'); - xlabel('GPU Ktrans (10^{-3} * min^{-1})'); - ylabel('CPU Ktrans (10^{-3} * min^{-1})'); - %set(gca,'xscale','log'); - %set(gca,'yscale','log') - ylim([0 0.2]) - xlim([0 0.2]) - - figure(5); - p = scatter(exponential_ve(:),d_exponential_ve(:)); - title([fit_dce_model ' GPU vs. CPU fit Ve'],'Interpreter','none'); - xlabel('GPU Ve'); - ylabel('CPU Ve'); - ylim([0 1]) - xlim([0 1]) - - figure(6); - p = scatter(exponential_vp(:),d_exponential_vp(:)); - title([fit_dce_model ' GPU vs. CPU fit Vp'],'Interpreter','none'); - xlabel('GPU Vp'); - ylabel('CPU Vp'); - ylim([0 0.1]) - xlim([0 0.1]) - - - figure(4); - hold on - p = errorbar(ktrans_list.*1000,squeeze(exponential_ktrans_median(:,vp_plot,ve_plot,fp_plot,ta_plot,tres_plot,1).*1000),squeeze(exponential_ktrans_error_std(:,vp_plot,ve_plot,fp_plot,ta_plot,tres_plot,1).*1000)); - p = errorbar(ktrans_list.*1000,squeeze(exponential_ktrans_median(:,vp_plot,ve_plot,fp_plot,ta_plot,tres_plot,2).*1000),squeeze(exponential_ktrans_error_std(:,vp_plot,ve_plot,fp_plot,ta_plot,tres_plot,2).*1000)); - hold off - title([fit_dce_model ' fit vs. ' gen_dce_model ' Ktrans, Various SNR'],'Interpreter','none'); - xlabel('Ktrans (10^{-3} * min^{-1})'); - ylabel('Average Fit Ktrans (10^{-3} * min^{-1})') - ylim([0 130]) - xlim([0 130]) - legend(cellfun(@num2str,num2cell(snr_list),'UniformOutput', 0), 'Location', 'NorthWest'); - diagonal_line = refline(1,0); - set(diagonal_line,'Color',[0.5 0.5 0.5],'LineStyle',':'); - - -% figure(5) -% plot(time_array,aif_gd_noisy,'k'); - - -end - - diff --git a/chopping_block/double_fit_gpu_run.m b/chopping_block/double_fit_gpu_run.m deleted file mode 100644 index 616d1cf..0000000 --- a/chopping_block/double_fit_gpu_run.m +++ /dev/null @@ -1,109 +0,0 @@ -toffset_location = toffset_location+1; -% Reshape -d_exponential_ktrans_shape = reshape(d_exponential_ktrans, [inner_variable_sizes outer_variable_sizes]); -d_exponential_vp_shape = reshape(d_exponential_vp, [inner_variable_sizes outer_variable_sizes]); -d_exponential_ve_shape = reshape(d_exponential_ve, [inner_variable_sizes outer_variable_sizes]); -d_exponential_fp_shape = reshape(d_exponential_fp, [inner_variable_sizes outer_variable_sizes]); -d_exponential_residual_shape = reshape(d_exponential_residual, [inner_variable_sizes outer_variable_sizes]); -exponential_residual_shape = reshape(exponential_residual, [inner_variable_sizes outer_variable_sizes]); -exponential_ktrans_shape = reshape(exponential_ktrans, [inner_variable_sizes outer_variable_sizes]); - -% % Throw out bad fits -% voxels_before = numel(d_exponential_ktrans_shape); -% residual_a_limit = 0.0113; -% d_exponential_ktrans_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% d_exponential_ve_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% d_exponential_vp_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% d_exponential_fp_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% exponential_ktrans_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% exponential_residual_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% d_exponential_residual_shape(d_exponential_residual_shape>residual_a_limit) = NaN; -% voxels_after_a = numel(d_exponential_ktrans_shape(~isnan(d_exponential_ktrans_shape))); -% -% % residual_b_limit = 0.026; -% residual_b_limit = 0.0119; -% d_exponential_ktrans_shape(exponential_residual_shape>residual_b_limit) = NaN; -% d_exponential_ve_shape(exponential_residual_shape>residual_b_limit) = NaN; -% d_exponential_vp_shape(exponential_residual_shape>residual_b_limit) = NaN; -% d_exponential_fp_shape(exponential_residual_shape>residual_b_limit) = NaN; -% exponential_ktrans_shape(exponential_residual_shape>residual_b_limit) = NaN; -% d_exponential_residual_shape(exponential_residual_shape>residual_b_limit) = NaN; -% exponential_residual_shape(exponential_residual_shape>residual_b_limit) = NaN; -% voxels_after_b = numel(d_exponential_ktrans_shape(~isnan(d_exponential_ktrans_shape))); -% -% percent_left_a_res_filter = voxels_after_a/voxels_before*100 -% percent_left_b_res_filter = voxels_after_b/voxels_before*100 - - - -% Get the standard deviation error from the repeats -exponential_ktrans_error_std = nanstd(exponential_ktrans_shape,0,repeats_location); -d_exponential_ktrans_error_std = nanstd(d_exponential_ktrans_shape,0,repeats_location); -d_exponential_vp_error_std = nanstd(d_exponential_vp_shape,0,repeats_location); -d_exponential_ve_error_std = nanstd(d_exponential_ve_shape,0,repeats_location); -d_exponential_fp_error_std = nanstd(d_exponential_fp_shape,0,repeats_location); - -% Average the repeats for bias error -exponential_ktrans_average = nanmean(exponential_ktrans_shape,repeats_location); -d_exponential_ktrans_average = nanmean(d_exponential_ktrans_shape,repeats_location); -d_exponential_vp_average = nanmean(d_exponential_vp_shape,repeats_location); -d_exponential_ve_average = nanmean(d_exponential_ve_shape,repeats_location); -d_exponential_fp_average = nanmean(d_exponential_fp_shape,repeats_location); - -exponential_ktrans_median = nanmedian(exponential_ktrans_shape,repeats_location); -d_exponential_ktrans_median = nanmedian(d_exponential_ktrans_shape,repeats_location); -d_exponential_vp_median = nanmedian(d_exponential_vp_shape,repeats_location); -d_exponential_ve_median = nanmedian(d_exponential_ve_shape,repeats_location); -d_exponential_fp_median = nanmedian(d_exponential_fp_shape,repeats_location); - -% Remove signleton repeats dimension -sz = size(d_exponential_ktrans_median); -if size(sz)>repeats_location - sz(repeats_location) = []; -end -exponential_ktrans_error_std = reshape(exponential_ktrans_error_std,sz); -d_exponential_ktrans_error_std = reshape(d_exponential_ktrans_error_std,sz); -d_exponential_vp_error_std = reshape(d_exponential_vp_error_std,sz); -d_exponential_ve_error_std = reshape(d_exponential_ve_error_std,sz); -d_exponential_fp_error_std = reshape(d_exponential_fp_error_std,sz); - -exponential_ktrans_average = reshape(exponential_ktrans_average,sz); -d_exponential_ktrans_average = reshape(d_exponential_ktrans_average,sz); -d_exponential_vp_average = reshape(d_exponential_vp_average,sz); -d_exponential_ve_average = reshape(d_exponential_ve_average,sz); -d_exponential_fp_average = reshape(d_exponential_fp_average,sz); - -exponential_ktrans_median = reshape(exponential_ktrans_median,sz); -d_exponential_ktrans_median = reshape(d_exponential_ktrans_median,sz); -d_exponential_vp_median = reshape(d_exponential_vp_median,sz); -d_exponential_ve_median = reshape(d_exponential_ve_median,sz); -d_exponential_fp_median = reshape(d_exponential_fp_median,sz); -% Reduce by one since the repeats dimension was removed -toffset_location = toffset_location-1; - -% Subtract out the true values to get the bias error -d_exponential_ktrans_error = bsxfun(@minus,d_exponential_ktrans_average,ktrans_list'); -d_exponential_ktrans_error_percent = bsxfun(@rdivide,d_exponential_ktrans_error,ktrans_list'); - -if average_across_offset - exponential_ktrans_error_std = nanmean(exponential_ktrans_error_std, toffset_location); - d_exponential_ktrans_error_std = nanmean(d_exponential_ktrans_error_std, toffset_location); - d_exponential_vp_error_std = nanmean(d_exponential_vp_error_std, toffset_location); - d_exponential_ve_error_std = nanmean(d_exponential_ve_error_std, toffset_location); - d_exponential_fp_error_std = nanmean(d_exponential_fp_error_std, toffset_location); - - exponential_ktrans_average = nanmean(exponential_ktrans_average, toffset_location); - d_exponential_ktrans_average = nanmean(d_exponential_ktrans_average, toffset_location); - d_exponential_vp_average = nanmean(d_exponential_vp_average, toffset_location); - d_exponential_ve_average = nanmean(d_exponential_ve_average, toffset_location); - d_exponential_fp_average = nanmean(d_exponential_fp_average, toffset_location); - - exponential_ktrans_median = nanmean(exponential_ktrans_median, toffset_location); - d_exponential_ktrans_median = nanmean(d_exponential_ktrans_median, toffset_location); - d_exponential_vp_median = nanmean(d_exponential_vp_median, toffset_location); - d_exponential_ve_median = nanmean(d_exponential_ve_median, toffset_location); - d_exponential_fp_median = nanmean(d_exponential_fp_median, toffset_location); - - d_exponential_ktrans_error = nanmean(d_exponential_ktrans_error, toffset_location); - d_exponential_ktrans_error_percent = nanmean(d_exponential_ktrans_error_percent, toffset_location); -end \ No newline at end of file diff --git a/chopping_block/get_voxel_drift.py b/chopping_block/get_voxel_drift.py deleted file mode 100755 index f85788b..0000000 --- a/chopping_block/get_voxel_drift.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 - -import numpy as np -import nibabel as nib -import os -import argparse -from argparse import Namespace - -def run(): - #filename = '/media/network_mriphysics/GRASP/test/400105.nii.gz' - #filename = '/media/network_mriphysics/GRASP/test/S3reg_strip_auto_threshold.nii' - #output_dir = '/media/network_mriphysics/GRASP/test/' - - img = nib.load(args.filename) - - print('Data read with dimensions ',img.shape) - - data = img.get_fdata() - - #Prep variables for linear fitting - x = np.arange(0,img.shape[3]) - A = np.vstack([x, np.ones(len(x))]).T - - y = np.reshape(data,(img.shape[0]*img.shape[1]*img.shape[2],img.shape[3])).T - - #Do Fit - m, c = np.linalg.lstsq(A, y, rcond=None)[0] - drift = np.divide(m,c) - drift = np.multiply(drift,100*img.shape[3]) - - drift_array = np.reshape(drift,(img.shape[0],img.shape[1],img.shape[2])) - - - #Write drift file - img = nib.Nifti1Image(drift_array,np.eye(4)) - #img.header.set_zooms((size_x,size_y,size_z)) - #img.header.set_xyzt_units('mm','sec') - img.to_filename(args.savename) - print('Wrote file to: '+args.savename) - - -def inputs(filename,savename): - global args - args=Namespace(filename=filename, savename=savename) - run() - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Linear fit (over time) of every voxel, outputs percent change') - parser.add_argument('-f','--filename', help='dynamic series filename') - parser.add_argument('-s','--savename', help='output filename') - args = parser.parse_args() - run() - - diff --git a/chopping_block/motion_correct.py b/chopping_block/motion_correct.py deleted file mode 100755 index c1157ec..0000000 --- a/chopping_block/motion_correct.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python3 - -import os -import argparse -from argparse import Namespace -import sys -import get_voxel_drift -import subprocess -try: - import PySimpleGUI as sg - gui_installed = True -except ImportError: - gui_installed = False - - - -def main(): - output_path = os.path.dirname(os.path.abspath(args.filename)) - os.chdir(output_path) - - subname = os.path.splitext(args.filename)[0] #remove .gz - subname = os.path.splitext(subname)[0] #remove .nii - output_name = subname+'_reg.nii' - - #output_name = 'S3reg.nii.gz' - bet_name = 'S1brain.nii' - mask_name = 'S2mask.nii' - #delete old results if exist - if args.overwrite: - print('Delete previous results') - try: - os.remove(output_name) - os.remove(bet_name) - os.remove(mask_name) - except: - print('') - - - #Step 1 -- Brain extraction - print('Perform brain extration for weighting') - #pull out single 3d dataset from 4d series - os.system('3dAFNItoNIFTI -prefix single_set.nii.gz '+args.filename+'[0]') - #get and save orientation - original_orientation = subprocess.check_output("3dinfo -orient single_set.nii.gz", shell=True, universal_newlines=True).rstrip() - print('Orientation is: ',original_orientation) - #put in standard LPI orientation (required for brain extraction) - os.system('3dresample -orient lpi -prefix single_set_std.nii.gz -input single_set.nii.gz') - #perform bet - os.system('hd-bet -i single_set_std.nii.gz') - #return brain mask to original orientation - os.system('3dresample -orient '+original_orientation+' -prefix '+mask_name+' -input single_set_std_bet_mask.nii.gz') - os.system('3dresample -orient '+original_orientation+' -prefix '+bet_name+' -input single_set_std_bet.nii.gz') - #delete intermediate files - os.remove('single_set.nii.gz') - os.remove('single_set_std.nii.gz') - os.remove('single_set_std_bet.nii.gz') - os.remove('single_set_std_bet_mask.nii.gz') - - - #Step 2 --- Call AFNI registration - # two pass = course fitting first, then fine fitting - # twodup = output dataset will have its xyz-axes origins reset to those of the base dataset - # base 0 = align everything to the first image - # nocip = don't rescale image intensities - # zpad = pad edges of images then remove, may help with interpolation artifacts or clipping - # 1dfile = saves corrections to file used to make plot - # heptic = use heptic interpolation, better than Fourier, fourier casus gibbs ringing drift artifacts - command_txt = '3dvolreg -weight '+mask_name+'[0] -noclip -zpad 5 -1Dfile dmotion.1d -verbose -rot_thresh 0.002 -x_thresh 0.05 -maxite 50 -heptic -base 0 -prefix '+output_name+' '+args.filename - print('Coregistration command: ',command_txt) - os.system(command_txt) - - - #Step 3 --- Create voxel drift image - if args.makedrift: - print('Create voxel drift image') - get_voxel_drift.inputs(output_name,'drift_percent_reg.nii.gz') - get_voxel_drift.run() - - #Step 4 --- Plot results of motion correction - if args.showplot: - os.system('1dplot -volreg -dx 1 -xlabel Acquisition dmotion.1d') - - print('Wrote motion corrected image to file: '+output_name+'.nii') - print('Finished motion correction') - -def inputs(filename,showplot,overwrite,makedrift): - global args - args=Namespace(filename=filename,showplot=showplot, overwrite=overwrite, makedrift=makedrift) - main() - -args=None -if __name__ == "__main__": - if len(sys.argv)==1 and gui_installed: - layout = [[sg.Text('Runs motion correction on a DCE dynamic series using AFNI 3dvolreg')], - [sg.Text('_' * 10)], - [sg.Text('DCE Series', size=(10, 1)), sg.Input(), sg.FileBrowse()], - [sg.Checkbox('Show 1D Motion Plot',default=True)], - [sg.Checkbox('Overwrite existing files',default=True)], - [sg.Text('Other Options:')], - [sg.Checkbox('Create Voxel Drift Image',default=True)], - [sg.Submit(), sg.Cancel()]] - - window = sg.Window('motion_correct', layout) - - - event, values = window.Read() - window.Close() - - if event=="Cancel": - raise SystemExit("Cancelling") - - print('Running motion_correct with the following inputs:') - print("DCE Series: ",values[0]) - print("Show 1D Motion Plot: ",values[1]) - print("Overwrite existing files: ",values[2]) - print("Create Voxel Drift Image: ",values[3]) - args=Namespace(filename=values[0],showplot=values[1],overwrite=values[2],makedrift=values[3]) - else: - if len(sys.argv)==1 and not gui_installed: - print('install PySimpleGUI and tkinter to use GUI, otherwise use command line options') - print('run with "-h" to see command line options') - parser = argparse.ArgumentParser(description='Runs motion correction on a DCE dynamic series using AFNI 3dvolreg') - parser.add_argument('-o','--overwrite', help='Overwrite exisiting output files', action="store_true") - parser.add_argument('-f','--filename', help='DCE series filename') - parser.add_argument('-p','--plot', help='Show 1D Motion Plot', action="store_true") - parser.add_argument('-d','--drift', help='Create voxel drift image', action="store_true") - args = parser.parse_args() - main() - diff --git a/chopping_block/python_norm.py b/chopping_block/python_norm.py deleted file mode 100644 index b329889..0000000 --- a/chopping_block/python_norm.py +++ /dev/null @@ -1,125 +0,0 @@ -''' -PYTHON PROGRAM FROR Z-AXIS NORMALIZATION -PLEASE FOLLOW THE STEPS BELOW SO THAT THE CODE WORKS PROPERLY - -IN THE HOME DIRECTORY - -STEP 1: DOWNLOAD AND INSTALL python 3 -STEP 2: DOWNLOAD AND INSTALL pip -STEP 3: RUN THE FOLLOWING COMMANDS ON TERMINAL - - pip install numpy - pip install nibabel - pip install matplotlib - pip install scipy -SETP 4: RUN THE PYTHON CODE (AFTER PLACING IT IN THE CORRECT LOCATION ON THE COMPUTER, GIVEN BELOW IN THE DIAGRAM) WITH THE COMMAND - python3 python_norm.py -ALSO, IT IS ESSENTIAL THAT THE INPUT DATA AND THIS CODE IS PLACED IN THE PROPER DIRECTORY/FOLDER, SO THAT IT CAN SEARCH AND PROCESS FILES OF ALL SUBJECTS. THE OUTPUT FILES ARE GENERATED AT THE SAME LOCATION OF THE INPUT FILES (FOR EACH SUBJECT). -THE DIRECTORY STRUCTURE TO BE MAINTAINED - - directory - | - | - | - sub directory 1 (containing each subjects' folder and this file) - | - | - | - --- subject folder - | | - | | - | |---- folder named "1st_timepoint" - | | | - | | | - | | | - | | ---- file named "2_new.nii.gz" - | | | - | | ---- file named "5_new.nii.gz" - | | | - | | ---- file named "10_new.nii.gz" - | | | - | | ---- file named "12_new.nii.gz" - | | | - | | ---- file named "15_new.nii.gz" - | | - | | - | | - | |---- folder named "2nd_timepoint" - | | - | | - | | - | ---- file named "2_new.nii.gz" - | | - | ---- file named "5_new.nii.gz" - | | - | ---- file named "10_new.nii.gz" - | | - | ---- file named "12_new.nii.gz" - | | - | ---- file named "15_new.nii.gz" - | - | - | - | - --- this file -''' -import os -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import nibabel as nib -from scipy import ndimage -from PIL import Image -from statistics import mean, pstdev -from pathlib import Path -import re - -def normalize(mri_file1, file_dir): # THE FUNCTION PERFORMING THE NORMALIZATION - img1 = nib.load(mri_file1) - num = int(re.search(r'\d+', mri_file1.split('/')[-1]).group()) - img_data1 = img1.get_fdata() - data = [] - for i in range(14): - a = np.where(img_data1[:, :, i] > 0) - data.append(img_data1[:,:,i][a].mean()) - data_mean = mean(data[1:13]) - std_dev = pstdev(data[1:13]) - err = 1*std_dev - slice_index = [] - min_val = data_mean - err - max_val = data_mean + err - for i in range (14): - if not (min_val <= data[i] <= max_val): - slice_index.append(i) - img_data2 = img_data1 - - for i in slice_index: - norm_val1 = img_data1[:, :, i] * (data_mean/data[i]) - img_data2[:, :, i] = norm_val1 - - data1 = [] - for i in range(14): - a = np.where(img_data2[:, :, i] > 0) - data1.append(img_data2[:,:,i][a].mean()) - data_mean1 = mean(data1) - - fig, (ax1, ax2) = plt.subplots(1, 2, sharex = True, sharey=True, figsize=(20,6)) - ax1.plot(data, 'o-', ms=4) - ax1.grid() - ax2.plot(data1, 'o-', ms=4) - ax2.grid() - path2 = file_dir + '/' + str(num) +'_Z.png' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE GRAPHS GENERATED (I HAVE KEPT IT AS 2_Z.png/5_Z.png/10_Z.png/12_Z.png/15_Z.png AS OF NOW). FOR EACH SUBJECT, THIS FILE GENERATED IS STORED AT THE SAME LOCATION AS THE INPUT FILES - plt.savefig(path2) - - final_img = nib.Nifti1Image(img_data2, img1.affine) - path3 = file_dir + '/' + str(num) + '_Z.nii' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE NORMALIZED NIFTI IMAGE GENERATED (I HAVE KEPT IT AS 2_Z.nii/5_Z.nii/10_Z.nii/12_Z.nii/15_Z.nii AS OF NOW). FOR EACH SUBJECT, THIS FILE GENERATED IS STORED AT THE SAME LOCATION AS THE INPUT FILES - nib.save(final_img, path3) - - -dir1 = Path(os.getcwd()) # CODE TO AUTOMATICALLY PERFORM THIS NORMALIZATION ON ALL SUBJECTS' ALL VFAs IN THIS DIRECTORY -files_in_dir1 = dir1.iterdir() -for file in files_in_dir1: - if (os.path.isdir(str(file))): - files_in_dir2 = file.iterdir() - for item in files_in_dir2: - if ((str(item).split('/')[-1] == '1st_timepoint') or (str(item).split('/')[-1] == '2nd_timepoint')): #SEARCHES FOR THE FOLDERS WITH THESE SPECIFIC NAMES (ENCLOSED IN STRINGS) - files_in_dir3 = item.iterdir() - for b1_imgs in files_in_dir3: - if ((str(b1_imgs).split('/')[-1] == '2_new.nii.gz') or (str(b1_imgs).split('/')[-1] == '5_new.nii.gz') or (str(b1_imgs).split('/')[-1] == '10_new.nii.gz') or (str(b1_imgs).split('/')[-1] == '12_new.nii.gz') or (str(b1_imgs).split('/')[-1] == '15_new.nii.gz')): #SEARCHES FOR THE NIFTI FILES WITH THESE SPECIFIC NAMES (ENCLOSED IN STRINGS) - normalize(str(b1_imgs), str(item)) #CALLING THE 'normalize()' FUNCTION TO PERFORM THE NORMALIZATION \ No newline at end of file diff --git a/chopping_block/python_norm1.py b/chopping_block/python_norm1.py deleted file mode 100644 index 92ab2d0..0000000 --- a/chopping_block/python_norm1.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import nibabel as nib -from scipy import ndimage -from PIL import Image -from statistics import mean, pstdev -from pathlib import Path -import re - -def normalize(mri_file1, file_dir): # THE FUNCTION PERFORMING THE NORMALIZATION - dim = {0,1,2} - img1 = nib.load(mri_file1) - num = int(re.search(r'\d+', mri_file1.split('/')[-1]).group()) - img_data1 = img1.get_fdata() - img_shape = img_data1.shape - slice_num = min(img_shape[0], img_shape[1], img_shape[2]) - slice_loc = img_shape.index(slice_num) - img_data1 = np.reshape(img_data1, (img_shape[min(dim-set([slice_loc]))], img_shape[max(dim-set([slice_loc]))], slice_num)) - data = [] - for i in range(slice_num): - a = np.where(img_data1[:, :, i] > 0) - data.append(img_data1[:, :, i][a].mean()) - data_mean = mean(data[1:13]) - std_dev = pstdev(data[1:13]) - err = 1*std_dev - slice_index = [] - min_val = data_mean - err - max_val = data_mean + err - for i in range (slice_num): - if not (min_val <= data[i] <= max_val): - slice_index.append(i) - img_data2 = img_data1 - - for i in slice_index: - norm_val1 = img_data1[:, :, i] * (data_mean/data[i]) - img_data2[:, :, i] = norm_val1 - - data1 = [] - for i in range(slice_num): - a = np.where(img_data2[:, :, i] > 0) - data1.append(img_data2[:, :, i][a].mean()) - data_mean1 = mean(data1) - - fig, (ax1, ax2) = plt.subplots(1, 2, sharex = True, sharey=True, figsize=(20,6)) - ax1.plot(data, 'o-', ms=4) - ax1.grid() - ax2.plot(data1, 'o-', ms=4) - ax2.grid() - path2 = file_dir + '/' + str(num) +'_corr_finalZ.png' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE GRAPHS GENERATED (I HAVE KEPT IT AS 2_Z.png/5_Z.png/10_Z.png/12_Z.png/15_Z.png AS OF NOW). FOR EACH SUBJECT, THIS FILE GENERATED IS STORED AT THE SAME LOCATION AS THE INPUT FILES - plt.savefig(path2) - - img_data2 = np.reshape(img_data2, img_shape) - final_img = nib.Nifti1Image(img_data2, img1.affine) - path3 = file_dir + '/' + str(num) + '_corr_finalZ.nii' #THE STRING IN THE END CONTAINS THE FILE NAME OF THE NORMALIZED NIFTI IMAGE GENERATED (I HAVE KEPT IT AS 2_Z.nii/5_Z.nii/10_Z.nii/12_Z.nii/15_Z.nii AS OF NOW). FOR EACH SUBJECT, THIS FILE GENERATED IS STORED AT THE SAME LOCATION AS THE INPUT FILES - nib.save(final_img, path3) - - -dir1 = Path(os.getcwd()) # CODE TO AUTOMATICALLY PERFORM THIS NORMALIZATION ON ALL SUBJECTS' ALL VFAs IN THIS DIRECTORY -files_in_dir1 = dir1.iterdir() -for file in files_in_dir1: - if (os.path.isdir(str(file))): - files_in_dir2 = file.iterdir() - for item in files_in_dir2: - if ((str(item).split('/')[-1] == '1st_timepoint') or (str(item).split('/')[-1] == '2nd_timepoint')): #SEARCHES FOR THE FOLDERS WITH THESE SPECIFIC NAMES (ENCLOSED IN STRINGS) - files_in_dir3 = item.iterdir() - for b1_imgs in files_in_dir3: - if str(b1_imgs).endswith('_bfc.nii'): - normalize(str(b1_imgs), str(item)) #CALLING THE 'normalize()' FUNCTION TO PERFORM THE NORMALIZATION diff --git a/compare_ktrans.py b/compare_ktrans.py deleted file mode 100644 index 1ac7ea1..0000000 --- a/compare_ktrans.py +++ /dev/null @@ -1,53 +0,0 @@ -import nibabel as nib -import numpy as np -import matplotlib.pyplot as plt - - -# cpu_file = '/media/network_mriphysics/GRASP/data/500180/DCE_GPU/DCEBBB_flip_500180_patlak_fit_Ktrans_CPU.nii' -# gpu_file = '/media/network_mriphysics/GRASP/data/500180/DCE_GPU/DCEBBB_flip_500180_patlak_fit_Ktrans.nii' -cpu_file = '/home/mrispec/Desktop/raw_data/patlak plots/cpu/dce_patlak_fit_Ktrans.nii' -gpu_file = '/home/mrispec/Desktop/raw_data/patlak plots/gpu/dce_patlak_fit_Ktrans.nii' - -print('Processing file: '+cpu_file) -cpu_img = nib.load(cpu_file) -cpu_img_data = cpu_img.get_data() - -print('Processing file: '+gpu_file) -gpu_img = nib.load(gpu_file) -gpu_img_data = gpu_img.get_data() - - -gpu_filter = gpu_img_data[cpu_img_data>0] -cpu_filter = cpu_img_data[cpu_img_data>0] -gpu_filter = gpu_filter[cpu_filter<0.1] -cpu_filter = cpu_filter[cpu_filter<0.1] - -cpu_filter = cpu_filter[gpu_filter<0.1] -gpu_filter = gpu_filter[gpu_filter<0.1] - -gpu_small = gpu_filter[::500] -cpu_small = cpu_filter[::500] - -print("Total voxels: ",gpu_img_data.size) -print("Filtered voxels (median): ",cpu_filter.size) -print("Plot voxels: ",cpu_small.size) - -difference = np.subtract(gpu_filter,cpu_filter) -diff_abs = np.abs(difference) -diff_median = np.median(diff_abs) -diff_mean = np.mean(diff_abs) -print("Median difference: ",diff_median) -print("Mean difference: ",diff_mean) - -plt.rcParams.update({'font.size': 16}) -plt.figure() -ax = plt.axes() -plt.scatter(cpu_small,gpu_small,marker='o',s=10) -#plt.plot(age_list_short,naa_list_short,'o', xx, yy) -plt.title('Patlak Fitting') -plt.xlabel('Ktrans (CPU - Matlab)') -plt.ylabel('Ktrans (GPU - GPUFit)') -ax.set_ylim([0, 0.01]) -ax.set_xlim([0, 0.01]) - -plt.show() \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..e081998 --- /dev/null +++ b/config.json @@ -0,0 +1,312 @@ +{ + "dcm2niixOptions": "-b y -ba n -z y -f '%3s_%f_%p_%t'", + "compKeys": [ + "AcquisitionDateTime", + "SeriesNumber", + "AcquisitionTime", + "SidecarFilename" + ], + "descriptions": [ + { + "datatype": "anat", + "suffix": "T1w", + "criteria": { + "SeriesDescription": "*MPRAGE*SEL." + }, + "sidecarChanges": { + "ProtocolName": "T1w" + } + }, + { + "datatype": "anat", + "suffix": "T1w", + "criteria": { + "SeriesDescription": "*FSPGR*" + }, + "sidecarChanges": { + "ProtocolName": "T1w" + } + }, + { + "datatype": "anat", + "suffix": "T1w", + "criteria": { + "SeriesDescription": "*mprage*" + }, + "sidecarChanges": { + "ProtocolName": "T1w" + } + }, + { + "datatype": "anat", + "suffix": "T1w", + "criteria": { + "SeriesDescription": "Accelerated Sagittal MPRAGE" + }, + "sidecarChanges": { + "ProtocolName": "T1w" + } + }, + { + "datatype": "anat", + "suffix": "FLAIR", + "criteria": { + "SeriesDescription": "*FLAIR*" + } + }, + { + "datatype": "dce", + "suffix": "DCE", + "criteria": { + "SeriesDescription": "*DCE*" + }, + "sidecarChanges": { + } + }, + { + "datatype": "dce", + "suffix": "DCE", + "criteria": { + "SeriesDescription": "*_dyn" + }, + "sidecarChanges": { + } + }, + { + "datatype": "dce", + "suffix": "DCE", + "criteria": { + "SeriesDescription": "*dynamic_waterext*" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-01", + "criteria": { + "SeriesDescription": "*FA02*", + "FlipAngle": "2" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-01", + "criteria": { + "SeriesDescription": "*dyn_2*", + "FlipAngle": "2" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-01", + "criteria": { + "SeriesDescription": "*tra_flip_2deg*", + "FlipAngle": "2" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-02", + "criteria": { + "SeriesDescription": "*FA05*", + "FlipAngle": "5" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-02", + "criteria": { + "SeriesDescription": "*dyn_5*", + "FlipAngle": "5" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-02", + "criteria": { + "SeriesDescription": "*tra_flip_5deg*", + "FlipAngle": "5" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-03", + "criteria": { + "SeriesDescription": "*FA10*", + "FlipAngle": "10" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-03", + "criteria": { + "SeriesDescription": "*dyn_10*", + "FlipAngle": "10" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-03", + "criteria": { + "SeriesDescription": "*tra_flip_10deg*", + "FlipAngle": "10" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-04", + "criteria": { + "SeriesDescription": "*FA12*", + "FlipAngle": "12" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-04", + "criteria": { + "SeriesDescription": "*dyn_12*", + "FlipAngle": "12" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-03", + "criteria": { + "SeriesDescription": "*tra_flip_12deg*", + "FlipAngle": "12" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-05", + "criteria": { + "SeriesDescription": "*FA15*", + "FlipAngle": "15" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-05", + "criteria": { + "SeriesDescription": "*dyn_15*", + "FlipAngle": "15" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-05", + "criteria": { + "SeriesDescription": "*tra_flip_15deg*", + "FlipAngle": "15" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-06", + "criteria": { + "SeriesDescription": "*FA20*", + "FlipAngle": "20" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-07", + "criteria": { + "SeriesDescription": "*FA25*", + "FlipAngle": "25" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-01", + "criteria": { + "SeriesDescription": "*FLIP1" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-02", + "criteria": { + "SeriesDescription": "*FLIP2" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-03", + "criteria": { + "SeriesDescription": "*FLIP3" + }, + "sidecarChanges": { + } + }, + { + "datatype": "anat", + "suffix": "VFA", + "custom_entities": "flip-04", + "criteria": { + "SeriesDescription": "*FLIP4" + }, + "sidecarChanges": { + } + } + ] +} diff --git a/docker/files/freesurfer-exclude.txt b/docker/files/freesurfer-exclude.txt new file mode 100644 index 0000000..d09d39f --- /dev/null +++ b/docker/files/freesurfer-exclude.txt @@ -0,0 +1,788 @@ +freesurfer/average* +freesurfer/bin/A* +freesurfer/bin/c* +freesurfer/bin/f* +freesurfer/bin/g* +freesurfer/bin/mri_a* +freesurfer/bin/mri_b* +freesurfer/bin/mri_ca* +freesurfer/bin/mri_cc +freesurfer/bin/mri_com* +freesurfer/bin/mri_conc* +freesurfer/bin/mri_coreg +freesurfer/bin/mri_d* +freesurfer/bin/mri_e* +freesurfer/bin/mri_f* +freesurfer/bin/mri_g* +freesurfer/bin/mri_m* +freesurfer/bin/mri_n* +freesurfer/bin/mri_o* +freesurfer/bin/mri_p* +freesurfer/bin/mri_r* +freesurfer/bin/mri_s* +freesurfer/bin/mri_t* +freesurfer/bin/mri_vol2s* +freesurfer/bin/mri_vold* +freesurfer/bin/mri_w* +freesurfer/bin/mris* +freesurfer/bin/p* +freesurfer/bin/r* +freesurfer/bin/s* +freesurfer/bin/ta* +freesurfer/bin/v* +freesurfer/bin/3dvolreg.afni +freesurfer/bin/4dfptoanalyze +freesurfer/bin/anatomiCutsUtils +freesurfer/bin/annot2std +freesurfer/bin/aparc2feat +freesurfer/bin/aparcstats2table +freesurfer/bin/aparc_stats_aseg +freesurfer/bin/aparcstatsdiff +freesurfer/bin/apas2aseg +freesurfer/bin/applyMorph +freesurfer/bin/aseg2feat +freesurfer/bin/asegstats2table +freesurfer/bin/asegstatsdiff +freesurfer/bin/bblabel +freesurfer/bin/bbmask +freesurfer/bin/bedpostx_mgh +freesurfer/bin/beta2sxa +freesurfer/bin/bet.fsl +freesurfer/bin/biasfield +freesurfer/bin/bmedits2surf +freesurfer/bin/brec +freesurfer/bin/brec.awk +freesurfer/bin/browse-minc-header.tcl +freesurfer/bin/bugr +freesurfer/bin/build_desikan_killiany_gcs.csh +freesurfer/bin/cblumwmgyri +freesurfer/bin/checkMCR.sh +freesurfer/bin/check_recons.sh +freesurfer/bin/check_siemens_dir +freesurfer/bin/check_subject +freesurfer/bin/clear_fs_env.csh +freesurfer/bin/compute_interrater_variability.csh +freesurfer/bin/compute_label_vals.csh +freesurfer/bin/compute_label_volumes.csh +freesurfer/bin/connectgraph +freesurfer/bin/cor_to_minc +freesurfer/bin/cp-dicom +freesurfer/bin/createMorph +freesurfer/bin/csvprint +freesurfer/bin/dcmdir-info-mgh +freesurfer/bin/dcmdjpeg.fs +freesurfer/bin/dcmdrle.fs +freesurfer/bin/dcmsplit +freesurfer/bin/dcmunpack +freesurfer/bin/deface_subject +freesurfer/bin/defect-seg +freesurfer/bin/dicom-rename +freesurfer/bin/diffusionUtils +freesurfer/bin/dmri_ac.sh +freesurfer/bin/dmri_AnatomiCuts +freesurfer/bin/dmri_bset +freesurfer/bin/dmri_coloredFA +freesurfer/bin/dmri_extractSurfaceMeasurements +freesurfer/bin/dmri_forrest +freesurfer/bin/dmri_group +freesurfer/bin/dmri_groupByEndpoints +freesurfer/bin/dmri_match +freesurfer/bin/dmri_mergepaths +freesurfer/bin/dmri_motion +freesurfer/bin/dmri_neighboringRegions +freesurfer/bin/dmri_paths +freesurfer/bin/dmri_pathstats +freesurfer/bin/dmri_projectEndPoints +freesurfer/bin/dmrirc.example +freesurfer/bin/dmrirc.long.example +freesurfer/bin/dmrirc.long.multiscan.example +freesurfer/bin/dmrirc.multiscan.example +freesurfer/bin/dmri_saveHistograms +freesurfer/bin/dmri_spline +freesurfer/bin/dmri_stats_ac +freesurfer/bin/dmri_tensoreig +freesurfer/bin/dmri_train +freesurfer/bin/dmri_trk2trk +freesurfer/bin/dmri_violinPlots +freesurfer/bin/dmri_vox2vox +freesurfer/bin/dt_recon +freesurfer/bin/epidewarp.fsl +freesurfer/bin/exportGcam +freesurfer/bin/feat2segstats +freesurfer/bin/feat2surf +freesurfer/bin/findsession +freesurfer/bin/fix_subject +freesurfer/bin/fix_subject_corrected +freesurfer/bin/fix_subject_corrected-lh +freesurfer/bin/fix_subject_corrected-rh +freesurfer/bin/fix_subject-lh +freesurfer/bin/fix_subject_on_seychelles +freesurfer/bin/fix_subject-rh +freesurfer/bin/fixup_mni_paths +freesurfer/bin/flip_4dfp +freesurfer/bin/flirt.fsl +freesurfer/bin/flirt.newdefault.20080811.sch +freesurfer/bin/fname2ext +freesurfer/bin/fname2stem +freesurfer/bin/freesurfer +freesurfer/bin/freeview +freesurfer/bin/fscalc +freesurfer/bin/fscalc.fsl +freesurfer/bin/fsdcmdecompress +freesurfer/bin/fsfget +freesurfer/bin/fsfirst.fsl +freesurfer/bin/fs_install_mcr +freesurfer/bin/fsl.5.0.2.xyztrans.sch +freesurfer/bin/fs_lib_check +freesurfer/bin/fsl_label2voxel +freesurfer/bin/fslmaths.fsl +freesurfer/bin/fslorient.fsl +freesurfer/bin/fslregister +freesurfer/bin/fsl_rigid_register +freesurfer/bin/fsl_sub_mgh +freesurfer/bin/fslswapdim.fsl +freesurfer/bin/fspalm +freesurfer/bin/fsPrintHelp +freesurfer/bin/fsr-coreg +freesurfer/bin/fsr-import +freesurfer/bin/fs_run_from_mcr +freesurfer/bin/fs_spmreg.glnxa64 +freesurfer/bin/fs_tutorial_data +freesurfer/bin/fs_update +freesurfer/bin/fsvglrun +freesurfer/bin/fvcompare +freesurfer/bin/gca-apply +freesurfer/bin/gcainit +freesurfer/bin/gcaprepone +freesurfer/bin/gcatrain +freesurfer/bin/gcatrainskull +freesurfer/bin/gdcmconv.fs +freesurfer/bin/gems_compute_binary_atlas_probs +freesurfer/bin/get_label_thickness +freesurfer/bin/groupstats +freesurfer/bin/groupstatsdiff +freesurfer/bin/gtmseg +freesurfer/bin/help_xml_validate +freesurfer/bin/hiam_make_surfaces +freesurfer/bin/hiam_make_template +freesurfer/bin/hiam_register +freesurfer/bin/histo_compute_joint_density +freesurfer/bin/histo_fix_topology +freesurfer/bin/histo_register_block +freesurfer/bin/histo_segment +freesurfer/bin/histo_synthesize +freesurfer/bin/ico_supersample +freesurfer/bin/id.xfm +freesurfer/bin/inflate_subject +freesurfer/bin/inflate_subject3 +freesurfer/bin/inflate_subject-lh +freesurfer/bin/inflate_subject_new +freesurfer/bin/inflate_subject_new-lh +freesurfer/bin/inflate_subject_new-rh +freesurfer/bin/inflate_subject-rh +freesurfer/bin/inflate_subject_sc +freesurfer/bin/irepifitvol +freesurfer/bin/irepifitvol.glnx64 +freesurfer/bin/isanalyze +freesurfer/bin/isnifti +freesurfer/bin/isolate_labels.csh +freesurfer/bin/isolate_labels_keeporigval.csh +freesurfer/bin/is-surface +freesurfer/bin/jkgcatrain +freesurfer/bin/label2flat +freesurfer/bin/label2patch +freesurfer/bin/label_area +freesurfer/bin/label_border +freesurfer/bin/label_child +freesurfer/bin/label_elderly_subject +freesurfer/bin/labels_disjoint +freesurfer/bin/labels_intersect +freesurfer/bin/label_subject +freesurfer/bin/label_subject_flash +freesurfer/bin/label_subject_mixed +freesurfer/bin/labels_union +freesurfer/bin/list_otl_labels +freesurfer/bin/listsubj +freesurfer/bin/long_create_base_sigma +freesurfer/bin/long_create_orig +freesurfer/bin/longmc +freesurfer/bin/long_mris_slopes +freesurfer/bin/long_qdec_table +freesurfer/bin/long_stats_combine +freesurfer/bin/long_stats_slopes +freesurfer/bin/long_stats_tps +freesurfer/bin/long_submit_jobs +freesurfer/bin/long_submit_postproc +freesurfer/bin/lpcregister +freesurfer/bin/lta_diff +freesurfer/bin/make_average_subcort +freesurfer/bin/make_average_subject +freesurfer/bin/make_average_surface +freesurfer/bin/make_average_volume +freesurfer/bin/make_cortex_label +freesurfer/bin/make_exvivo_filled +freesurfer/bin/make_folding_atlas +freesurfer/bin/make_hemi_mask +freesurfer/bin/make-segvol-table +freesurfer/bin/make_symmetric +freesurfer/bin/make_upright +freesurfer/bin/makevol +freesurfer/bin/map_all_labels +freesurfer/bin/map_all_labels-lh +freesurfer/bin/map_central_sulcus +freesurfer/bin/map_to_base +freesurfer/bin/meanval +freesurfer/bin/mergeseg +freesurfer/bin/merge_stats_tables +freesurfer/bin/minc2seqinfo +freesurfer/bin/mkheadsurf +freesurfer/bin/mkima_index.tcl +freesurfer/bin/mkmnc_index.tcl +freesurfer/bin/mksubjdirs +freesurfer/bin/mksurfatlas +freesurfer/bin/mkxsubjreg +freesurfer/bin/mni152reg +freesurfer/bin/morph_only_subject +freesurfer/bin/morph_only_subject-lh +freesurfer/bin/morph_only_subject-rh +freesurfer/bin/morph_rgb-lh +freesurfer/bin/morph_rgb-rh +freesurfer/bin/morph_subject +freesurfer/bin/morph_subject-lh +freesurfer/bin/morph_subject_on_seychelles +freesurfer/bin/morph_subject-rh +freesurfer/bin/morph_tables-lh +freesurfer/bin/morph_tables-rh +freesurfer/bin/mri_align_long.csh +freesurfer/bin/mri_aparc2wmseg +freesurfer/bin/mri_apply_autoencoder +freesurfer/bin/mri_apply_bias +freesurfer/bin/mri_apply_inu_correction +freesurfer/bin/mri_aseg_edit_reclassify +freesurfer/bin/mri_aseg_edit_train +freesurfer/bin/mri_auto_fill +freesurfer/bin/mri_average +freesurfer/bin/mri_bc_sc_bias_correct +freesurfer/bin/mri_brain_volume +freesurfer/bin/mri_build_priors +freesurfer/bin/mri_cal_renormalize_gca +freesurfer/bin/mri_ca_tissue_parms +freesurfer/bin/mri_ca_train +freesurfer/bin/mri_cht2p +freesurfer/bin/mri_classify +freesurfer/bin/mri_cnr +freesurfer/bin/mri_compute_bias +freesurfer/bin/mri_compute_change_map +freesurfer/bin/mri_compute_distances +freesurfer/bin/mri_compute_layer_fractions +freesurfer/bin/mri_compute_structure_transforms +freesurfer/bin/mri_compute_volume_fractions +freesurfer/bin/mri_compute_volume_intensities +freesurfer/bin/mri_concatenate_gcam +freesurfer/bin/mri_convert_mdh +freesurfer/bin/mri_copy_params +freesurfer/bin/mri_copy_values +freesurfer/bin/mri_cor2label +freesurfer/bin/mri_correct_segmentations +freesurfer/bin/mri_create_t2combined +freesurfer/bin/mri_create_tests +freesurfer/bin/mri_cvs_check +freesurfer/bin/mri_cvs_data_copy +freesurfer/bin/mri_cvs_register +freesurfer/bin/mri_cvs_requiredfiles.txt +freesurfer/bin/mri_dct_align +freesurfer/bin/mri_dct_align_binary +freesurfer/bin/mri_distance_transform +freesurfer/bin/mri_dist_surf_label +freesurfer/bin/mri_divide_segmentation +freesurfer/bin/mri_edit_segmentation +freesurfer/bin/mri_edit_segmentation_with_surfaces +freesurfer/bin/mri_elastic_energy +freesurfer/bin/mri_estimate_tissue_parms +freesurfer/bin/mri_evaluate_morph +freesurfer/bin/mri_extract +freesurfer/bin/mri_extract_conditions +freesurfer/bin/mri_extract_fcd_features +freesurfer/bin/mri_extract_label +freesurfer/bin/mri_extract_largest_CC +freesurfer/bin/mri_fcili +freesurfer/bin/mri_fdr +freesurfer/bin/mri_fieldsign +freesurfer/bin/mri_fit_bias +freesurfer/bin/mri_fslmat_to_lta +freesurfer/bin/mri-func2sph +freesurfer/bin/mri-funcvits +freesurfer/bin/mri_fuse_intensity_images +freesurfer/bin/mri_gca_ambiguous +freesurfer/bin/mri_gcab_train +freesurfer/bin/mri_gdfglm +freesurfer/bin/mri_glmfit +freesurfer/bin/mri_glmfit-sim +freesurfer/bin/mri_gradient_info +freesurfer/bin/mri_gtmpvc +freesurfer/bin/mri_gtmseg +freesurfer/bin/mri_hausdorff_dist +freesurfer/bin/mri_head +freesurfer/bin/mri_hires_register +freesurfer/bin/mri_histo_eq +freesurfer/bin/mri_histo_normalize +freesurfer/bin/mri_ibmc +freesurfer/bin/mri_interpolate +freesurfer/bin/mri_jacobian +freesurfer/bin/mri_joint_density +freesurfer/bin/mri_label_accuracy +freesurfer/bin/mri_label_histo +freesurfer/bin/mri_label_vals +freesurfer/bin/mri_label_volume +freesurfer/bin/mri_linear_align +freesurfer/bin/mri_linear_align_binary +freesurfer/bin/mri_linear_register +freesurfer/bin/mri_long_normalize +freesurfer/bin/mri_make_bem_surfaces +freesurfer/bin/mri_make_density_map +freesurfer/bin/mri_make_labels +freesurfer/bin/mri_make_register +freesurfer/bin/mri_make_template +freesurfer/bin/mri_map_cpdat +freesurfer/bin/mri_maps2csd +freesurfer/bin/mri_mark_temporal_lobe +freesurfer/bin/mri_mc +freesurfer/bin/mri_mcsim +freesurfer/bin/mri_mergelabels +freesurfer/bin/mri_mi +freesurfer/bin/mri_modify +freesurfer/bin/mri_morphology +freesurfer/bin/mri_mosaic +freesurfer/bin/mri_motion_correct +freesurfer/bin/mri_motion_correct2 +freesurfer/bin/mri_ms_EM +freesurfer/bin/mri_ms_EM_with_atlas +freesurfer/bin/mri_ms_fitparms +freesurfer/bin/mri_ms_LDA +freesurfer/bin/mri_multiscale_segment +freesurfer/bin/mri_multispectral_segment +freesurfer/bin/mri_nl_align +freesurfer/bin/mri_nl_align_binary +freesurfer/bin/mri_nlfilter +freesurfer/bin/mri_paint +freesurfer/bin/mri_parselabel +freesurfer/bin/mri_parse_sdcmdir +freesurfer/bin/mri_partial_ribbon +freesurfer/bin/mri_path2label +freesurfer/bin/mri_polv +freesurfer/bin/mri_probedicom +freesurfer/bin/mri_probe_ima +freesurfer/bin/mri_reduce +freesurfer/bin/mri_refine_seg +freesurfer/bin/mri_register +freesurfer/bin/mri_reorient_LR.csh +freesurfer/bin/mri_rf_label +freesurfer/bin/mri_rf_long_label +freesurfer/bin/mri_rf_long_train +freesurfer/bin/mri_rf_train +freesurfer/bin/mri_ribbon +freesurfer/bin/mri_rigid_register +freesurfer/bin/mri_sbbr +freesurfer/bin/mri_segcentroids +freesurfer/bin/mri_seghead +freesurfer/bin/mri_segment_hypothalamic_subunits +freesurfer/bin/mri_segment_tumor +freesurfer/bin/mri_segment_wm_damage +freesurfer/bin/mri_seg_overlap +freesurfer/bin/mri_simulate_atrophy +freesurfer/bin/mris2rgb +freesurfer/bin/mris_AA_shrinkwrap +freesurfer/bin/mris_add_template +freesurfer/bin/mris_annot_diff +freesurfer/bin/mris_annot_to_segmentation +freesurfer/bin/mris_aseg_distance +freesurfer/bin/mris_average_curvature +freesurfer/bin/mris_average_parcellation +freesurfer/bin/mris_BA_segment +freesurfer/bin/mris_ca_deform +freesurfer/bin/mris_ca_train +freesurfer/bin/mris_classify_thickness +freesurfer/bin/mris_compute_acorr +freesurfer/bin/mris_compute_layer_intensities +freesurfer/bin/mris_compute_lgi +freesurfer/bin/mris_compute_optimal_kernel +freesurfer/bin/mris_compute_overlap +freesurfer/bin/mris_compute_parc_overlap +freesurfer/bin/mris_compute_volume_fractions +freesurfer/bin/mris_congeal +freesurfer/bin/mris_copy_header +freesurfer/bin/mris_curvature2image +freesurfer/bin/mris_deform +freesurfer/bin/mris_density +freesurfer/bin/mris_distance_map +freesurfer/bin/mris_distance_to_label +freesurfer/bin/mris_distance_transform +freesurfer/bin/mris_entropy +freesurfer/bin/mris_errors +freesurfer/bin/mris_extract_patches +freesurfer/bin/mris_extract_values +freesurfer/bin/mris_exvivo_surfaces +freesurfer/bin/mris_fbirn_annot +freesurfer/bin/mris_fill +freesurfer/bin/mris_find_flat_regions +freesurfer/bin/mris_flatten +freesurfer/bin/mris_fwhm +freesurfer/bin/mris_gradient +freesurfer/bin/mris_hausdorff_dist +freesurfer/bin/mris_image2vtk +freesurfer/bin/mris_info +freesurfer/bin/mris_init_global_tractography +freesurfer/bin/mris_intensity_profile +freesurfer/bin/mris_interpolate_warp +freesurfer/bin/mris_label_area +freesurfer/bin/mris_label_calc +freesurfer/bin/mris_label_mode +freesurfer/bin/mris_longitudinal_surfaces +freesurfer/bin/mris_make_average_surface +freesurfer/bin/mris_make_face_parcellation +freesurfer/bin/mris_make_map_surfaces +freesurfer/bin/mris_make_surfaces +freesurfer/bin/mris_make_template +freesurfer/bin/mris_map_cuts +freesurfer/bin/mris_mef_surfaces +freesurfer/bin/mris_merge_parcellations +freesurfer/bin/mris_mesh_subdivide +freesurfer/bin/mris_morph_stats +freesurfer/bin/mris_ms_refine +freesurfer/bin/mris_ms_surface_CNR +freesurfer/bin/mris_multimodal +freesurfer/bin/mris_multimodal_surface_placement +freesurfer/bin/mris_multiscale_stats +freesurfer/bin/mris_niters2fwhm +freesurfer/bin/mris_nudge +freesurfer/bin/mris_parcellate_connectivity +freesurfer/bin/mri-sph2surf +freesurfer/bin/mris_pmake +freesurfer/bin/mris_preproc +freesurfer/bin/mris_profileClustering +freesurfer/bin/mrisp_write +freesurfer/bin/mris_refine_surfaces +freesurfer/bin/mris_register_label_map +freesurfer/bin/mris_register_to_label +freesurfer/bin/mris_register_to_volume +freesurfer/bin/mris_remove_negative_vertices +freesurfer/bin/mris_remove_variance +freesurfer/bin/mris_resample +freesurfer/bin/mris_rescale +freesurfer/bin/mris_reverse +freesurfer/bin/mris_rf_label +freesurfer/bin/mris_rf_train +freesurfer/bin/mris_rotate +freesurfer/bin/mris_sample_label +freesurfer/bin/mris_sample_parc +freesurfer/bin/mris_seg2annot +freesurfer/bin/mris_segment +freesurfer/bin/mris_segmentation_stats +freesurfer/bin/mris_segment_vals +freesurfer/bin/mris_shrinkwrap +freesurfer/bin/mris_simulate_atrophy +freesurfer/bin/mris_smooth_intracortical +freesurfer/bin/mris_surf2vtk +freesurfer/bin/mris_surface_change +freesurfer/bin/mris_surface_to_vol_distances +freesurfer/bin/mris_svm_classify +freesurfer/bin/mris_svm_train +freesurfer/bin/mris_talairach +freesurfer/bin/mris_thickness_comparison +freesurfer/bin/mris_transform +freesurfer/bin/mris_translate_annotation +freesurfer/bin/mris_transmantle_dysplasia_paths +freesurfer/bin/mri_strip_nonwhite +freesurfer/bin/mri_strip_subject_info +freesurfer/bin/mris_twoclass +freesurfer/bin/mri_surfacemask +freesurfer/bin/mris_volmask_novtk +freesurfer/bin/mris_volmask_vtk +freesurfer/bin/mris_volsmooth +freesurfer/bin/mris_volume +freesurfer/bin/mris_warp +freesurfer/bin/mris_wm_volume +freesurfer/bin/mris_w_to_curv +freesurfer/bin/mri_synthesize +freesurfer/bin/mri_synthstrip +freesurfer/bin/mri_threshold +freesurfer/bin/mri_topologycorrection +freesurfer/bin/mri_train +freesurfer/bin/mri_train_autoencoder +freesurfer/bin/mri_transform +freesurfer/bin/mri_transform_to_COR +freesurfer/bin/mri_twoclass +freesurfer/bin/mri_update_gca +freesurfer/bin/mri_validate_skull_stripped +freesurfer/bin/mri_vessel_segment +freesurfer/bin/mri_vol2label +freesurfer/bin/mri_vol2roi +freesurfer/bin/mri_volcluster +freesurfer/bin/mri_volsynth +freesurfer/bin/mri_warp_convert +freesurfer/bin/mri_wbc +freesurfer/bin/mri_wmfilter +freesurfer/bin/mri_xcorr +freesurfer/bin/mri_xvolavg +freesurfer/bin/mri_z2p +freesurfer/bin/ms_refine_subject +freesurfer/bin/nmovie_qt +freesurfer/bin/oct_register_mosaic +freesurfer/bin/oct_rf_train +freesurfer/bin/oct_train +freesurfer/bin/optseq2 +freesurfer/bin/orientLAS +freesurfer/bin/parc_atlas_jackknife_test +freesurfer/bin/plot_structure_stats.tcl +freesurfer/bin/polyorder +freesurfer/bin/predict_v1.sh +freesurfer/bin/print_unique_labels.csh +freesurfer/bin/progressbar.tcl +freesurfer/bin/qatools.py +freesurfer/bin/qdec +freesurfer/bin/qdec_glmfit +freesurfer/bin/qt.conf +freesurfer/bin/quantifyBrainstemStructures.sh +freesurfer/bin/quantifyHAsubregions.sh +freesurfer/bin/quantifyThalamicNuclei.sh +freesurfer/bin/rbbr +freesurfer/bin/rbftest +freesurfer/bin/rcbf-prep +freesurfer/bin/rebuild_gca_atlas.csh +freesurfer/bin/recon-all-exvivo +freesurfer/bin/recon-all.makefile +freesurfer/bin/regdat2xfm +freesurfer/bin/reg-feat2anat +freesurfer/bin/register_child +freesurfer/bin/register.csh +freesurfer/bin/register_elderly_subject +freesurfer/bin/register_subject +freesurfer/bin/register_subject_flash +freesurfer/bin/register_subject_mixed +freesurfer/bin/reg-mni305.2mm +freesurfer/bin/reinflate_subject +freesurfer/bin/reinflate_subject-lh +freesurfer/bin/reinflate_subject-rh +freesurfer/bin/remove_talairach +freesurfer/bin/renormalize_subject +freesurfer/bin/renormalize_subject_keep_editting +freesurfer/bin/renormalize_T1_subject +freesurfer/bin/repair_siemens_file +freesurfer/bin/reregister_subject_mixed +freesurfer/bin/rtview +freesurfer/bin/run_mris_preproc +freesurfer/bin/run-qdec-glm +freesurfer/bin/run_samseg_long +freesurfer/bin/run_SegmentSubfieldsT1Longitudinal.sh +freesurfer/bin/run_SegmentSubject.sh +freesurfer/bin/run_segmentSubjectT1_autoEstimateAlveusML.sh +freesurfer/bin/run_segmentSubjectT1T2_autoEstimateAlveusML.sh +freesurfer/bin/run_segmentSubjectT2_autoEstimateAlveusML.sh +freesurfer/bin/run_SegmentThalamicNuclei.sh +freesurfer/bin/samseg +freesurfer/bin/samseg2recon +freesurfer/bin/samseg-long +freesurfer/bin/sbtiv +freesurfer/bin/seg2filled +freesurfer/bin/segmentBS.sh +freesurfer/bin/segmentHA_T1_long.sh +freesurfer/bin/segmentHA_T1.sh +freesurfer/bin/segmentHA_T2.sh +freesurfer/bin/segment_monkey +freesurfer/bin/SegmentSubfieldsT1Longitudinal +freesurfer/bin/segment_subject +freesurfer/bin/segmentSubject +freesurfer/bin/segment_subject_notal +freesurfer/bin/segment_subject_notal2 +freesurfer/bin/segment_subject_old_skull_strip +freesurfer/bin/segment_subject_sc +freesurfer/bin/segmentSubjectT1_autoEstimateAlveusML +freesurfer/bin/segmentSubjectT1T2_autoEstimateAlveusML +freesurfer/bin/segmentSubjectT2_autoEstimateAlveusML +freesurfer/bin/segment_subject_talmgh +freesurfer/bin/SegmentThalamicNuclei +freesurfer/bin/segmentThalamicNuclei.sh +freesurfer/bin/segpons +freesurfer/bin/setlabelstat +freesurfer/bin/sfa2fieldsign +freesurfer/bin/show_tal +freesurfer/bin/skip_long_make_checks +freesurfer/bin/slicedelay +freesurfer/bin/slicetimer.fsl +freesurfer/bin/sphere_subject +freesurfer/bin/sphere_subject-lh +freesurfer/bin/sphere_subject-rh +freesurfer/bin/spherical_st +freesurfer/bin/Spline3_test +freesurfer/bin/spmmat2register +freesurfer/bin/spmregister +freesurfer/bin/spm_t_to_b +freesurfer/bin/sratio +freesurfer/bin/stat_normalize +freesurfer/bin/stattablediff +freesurfer/bin/stem2fname +freesurfer/bin/stim_polar +freesurfer/bin/streamlineFilter +freesurfer/bin/surf2vol +freesurfer/bin/surfreg +freesurfer/bin/swi_preprocess +freesurfer/bin/swi_process +freesurfer/bin/t4img_4dfp +freesurfer/bin/t4imgs_4dfp +freesurfer/bin/talairach2 +freesurfer/bin/talairach_mgh +freesurfer/bin/tal_compare +freesurfer/bin/tal_QC_AZS +freesurfer/bin/talsegprob +freesurfer/bin/template +freesurfer/bin/testOrientationPlanesFromParcellation +freesurfer/bin/test_recon-all.csh +freesurfer/bin/test_tutorials.sh +freesurfer/bin/thickdiffmap +freesurfer/bin/tkmedit +freesurfer/bin/tkmeditfv +freesurfer/bin/tkregisterfv +freesurfer/bin/tksurfer +freesurfer/bin/tksurferfv +freesurfer/bin/trac-all +freesurfer/bin/trac-paths +freesurfer/bin/trac-preproc +freesurfer/bin/tractstats2table +freesurfer/bin/train-gcs-atlas +freesurfer/bin/tridec +freesurfer/bin/trk_tools +freesurfer/bin/unpack_ima1.tcl +freesurfer/bin/unpackimadir +freesurfer/bin/unpackimadir2 +freesurfer/bin/unpack_ima.tcl +freesurfer/bin/unpackmincdir +freesurfer/bin/unpack_mnc.tcl +freesurfer/bin/unpacksdcmdir +freesurfer/bin/usbtree +freesurfer/bin/vol2segavg +freesurfer/bin/vol2subfield +freesurfer/bin/vol2symsurf +freesurfer/bin/vsm-smooth +freesurfer/bin/wfilemask +freesurfer/bin/wm-anat-snr +freesurfer/bin/wmedits2surf +freesurfer/bin/wmsaseg +freesurfer/bin/xcerebralseg +freesurfer/bin/xcorr +freesurfer/bin/xfmrot +freesurfer/bin/xhemireg +freesurfer/bin/xhemi-tal +freesurfer/bin/xsanatreg +freesurfer/bin/zero_lt_4dfp +freesurfer/DefectLUT.txt +freesurfer/diffusion +freesurfer/docs/xml +freesurfer/FreeSurferEnv.csh +freesurfer/FreeSurferEnv.sh +freesurfer/fsfast +freesurfer/lib/bem/ic0.tri +freesurfer/lib/bem/ic1.tri +freesurfer/lib/bem/ic2.tri +freesurfer/lib/bem/ic3.tri +freesurfer/lib/bem/ic6.tri +freesurfer/lib/bem/inner_skull.dat +freesurfer/lib/bem/outer_skin.dat +freesurfer/lib/bem/outer_skull.dat +freesurfer/lib/images +freesurfer/lib/qt +freesurfer/lib/resource +freesurfer/lib/tcl +freesurfer/lib/vtk +freesurfer/matlab +freesurfer/mni-1.4 +freesurfer/mni/bin/autocrop +freesurfer/mni/bin/check_scale +freesurfer/mni/bin/correct_field +freesurfer/mni/bin/crispify +freesurfer/mni/bin/dcm2mnc +freesurfer/mni/bin/Display +freesurfer/mni/bin/ecattominc +freesurfer/mni/bin/evaluate_field +freesurfer/mni/bin/extracttag +freesurfer/mni/bin/field2imp +freesurfer/mni/bin/imp2field +freesurfer/mni/bin/invert_raw_image +freesurfer/mni/bin/make_model +freesurfer/mni/bin/make_phantom +freesurfer/mni/bin/make_template +freesurfer/mni/bin/mincaverage +freesurfer/mni/bin/mincbbox +freesurfer/mni/bin/mincblur +freesurfer/mni/bin/minccalc +freesurfer/mni/bin/mincchamfer +freesurfer/mni/bin/mincconcat +freesurfer/mni/bin/minccopy +freesurfer/mni/bin/mincdiff +freesurfer/mni/bin/mincedit +freesurfer/mni/bin/mincexpand +freesurfer/mni/bin/mincextract +freesurfer/mni/bin/mincheader +freesurfer/mni/bin/minchistory +freesurfer/mni/bin/mincinfo +freesurfer/mni/bin/minclookup +freesurfer/mni/bin/mincmakescalar +freesurfer/mni/bin/mincmakevector +freesurfer/mni/bin/mincmath +freesurfer/mni/bin/minc_modify_header +freesurfer/mni/bin/mincpik +freesurfer/mni/bin/mincresample +freesurfer/mni/bin/mincreshape +freesurfer/mni/bin/mincstats +freesurfer/mni/bin/minctoecat +freesurfer/mni/bin/minctoraw +freesurfer/mni/bin/minctracc +freesurfer/mni/bin/mincview +freesurfer/mni/bin/mincwindow +freesurfer/mni/bin/mnc2nii +freesurfer/mni/bin/mritoself +freesurfer/mni/bin/mritotal +freesurfer/mni/bin/mritotal~ +freesurfer/mni/bin/ncdump +freesurfer/mni/bin/ncgen +freesurfer/mni/bin/nii2mnc +freesurfer/mni/bin/nu_estimate +freesurfer/mni/bin/nu_estimate_np_and_em~ +freesurfer/mni/bin/nu_evaluate +freesurfer/mni/bin/param2xfm +freesurfer/mni/bin/rand_param +freesurfer/mni/bin/rawtominc +freesurfer/mni/bin/register +freesurfer/mni/bin/resample_labels +freesurfer/mni/bin/sharpen_hist +freesurfer/mni/bin/sharpen_volume +freesurfer/mni/bin/spline_smooth +freesurfer/mni/bin/transformtags +freesurfer/mni/bin/upet2mnc +freesurfer/mni/bin/volume_cog +freesurfer/mni/bin/volume_hist +freesurfer/mni/bin/volume_stats +freesurfer/mni/bin/voxeltoworld +freesurfer/mni/bin/worldtovoxel +freesurfer/mni/bin/xcorr_vol +freesurfer/mni/bin/xfm2param +freesurfer/mni/bin/xfmconcat +freesurfer/mni/bin/xfminvert +freesurfer/mni/bin/xfmtool +freesurfer/mni/bin/zscore_vol +freesurfer/mni/data +freesurfer/mni/etc +freesurfer/mni/include +freesurfer/mni/mni.srcbuild.June2015.tgz +freesurfer/mni/share/man +freesurfer/mni/share/mni_autoreg +freesurfer/mni/share/N3 +freesurfer/models +freesurfer/python* +freesurfer/SegmentNoLUT.txt +freesurfer/sessions +freesurfer/SetUpFreeSurfer.csh +freesurfer/SetUpFreeSurfer.sh +freesurfer/Simple_surface_labels2009.txt +freesurfer/sources.sh +freesurfer/subjects* +freesurfer/trctrain diff --git a/docker/files/script_preferences.txt b/docker/files/script_preferences.txt new file mode 100644 index 0000000..697c720 --- /dev/null +++ b/docker/files/script_preferences.txt @@ -0,0 +1,191 @@ +%%%%%%%%% GLOBAL SCRIPT PREFERENCES %%%%%%%%% +%% RUNA OPTIONS +% Load image, ROI +% Calculate concentration vs. time curves from images +%%%%%%%%%%%%%%%% + +%% A_make_R1maps_func + % loadIMGVOL + + % 1 = 4D, 2 = 3D, 3 = 2D + filevolume = 1 + + % 1 = pick noise file, 0 = derive noise from corner square + noise_pathpick = 0 + + % width of corner square noise selection + noise_pixsize = 9 + + % probably deprecated (COPIUM) + LUT = 1 + + % input nifti datasets, can specify subfolders and use wildcard * + dynamic_files = /dce/*_desc-bfcz_DCE.nii* + + aif_files = /dce/*desc-AIF_T1map.nii* + roi_files = /anat/*space-DCEref_desc-brain_mask.nii* + t1map_files = /anat/*space-DCEref_T1map.nii* + noise_files = + drift_files = + + % what t-dimension image to start from/end on + % useful for excluding bad data points at beginning or end + % leave blank to do from beginning/to end + start_t = 3 + end_t = + + % Name of output file + rootname = dce + + % X, Y, Z, t 'xyzt' or X, Y, t, Z 'xytz' + fileorder = xyzt + + % boolean value indicating whether to pursue + % quantitative DCE vs. semi-quant values + quant = 1 + + % roi_files is a T1 map=0; roi_files is a mask=1 + roimaskroi = 1 + % aif_files is a T1 map=0; aif_files is a mask=1 + aifmaskroi = 0 + +% Indicates the type of vascular input ROI, +% 'rr' = reference region +% 'aif_roi' = arterial input +% 'aif_roi_static' = arterial input with user defined T1 value +% 'aif_auto' = AIF is to be auto found +% 'aif_auto_static' = auto found with user defined T1 value +aif_rr_type = aif_roi + +% repetition time of dynamic scan (ms), overwritten by json if it exists +tr = 8 + +% flip angle of dynamic scan (degrees), overwritten by json if it exists +fa = 15 + +% hematocrit percent (0 - 1.00) of subject +hematocrit = 0.45 + +% snr required for AIF voxels, snr must exceed this +% value averaged over all time points +snr_filter = 1 + +% -1 = determine via figure, -2 = auto +injection_time = -2 + +% r1 relaxivity (in mM^-1*sec^-1) of contrast agent +% if AcquisitionDateTime field exists in json, CLI auto determines for this study: +% pre-Oct 2017 assumes MultiHance +% post-Oct 2017 assumes dotarem +% Shen 2015: https://journals.lww.com/investigativeradiology/fulltext/2015/05000/t1_relaxivities_of_gadolinium_based_magnetic.4.aspx +% dotarem 3T, human blood = 3.43 ± 0.29, 3.19 ± 0.4 [3.4 ± 0.4] +% magnevist 3T, human blood = 3.76 ± 0.17, 3.34 ± 0.19 [3.8 ± 0.2] +% MH 3T - https://journals.lww.com/investigativeradiology/fulltext/2015/05000/t1_relaxivities_of_gadolinium_based_magnetic.4.aspx +% https://www.bracco.com/sites/default/files/2022-10/kr-en-2016-04-30-brochure-the-strenght-of-relaxivity-multihance.pdf +% https://journals.lww.com/investigativeradiology/fulltext/2006/03000/relaxivity_of_gadopentetate_dimeglumine.3.aspx +% https://appliedradiology.com/articles/mri-contrast-selection-greater-stability-or-higher-relaxivity +% MultiHance 3T, human blood = 5.4 ± 0.3 (2015; appliedradiology.com), 5.9 ± 0.4, <6ish (bracco), 6.3 ± 0.4 (2006, <1 mM), 6.3 (mriquestions.com) +% Szomolanyi 2019: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6738537/ +% dotarem 3T, human blood - 2.72 ± 0.17 +% magnevist 3T, human blood - 3.3 ± 0.2 +relaxivity = 2.8 +% set below to 1 if you wish to use the value above with the CLI command. +force_use_default_relaxivity = 0 + +% boolean value, perform drift correction globally, +% not on a slice by slice basis +drift_global = 1 + +% the T1 value of blood (in s) only used if +% aif_rr_type is set to 'aif_auto_static' or 'aif_roi_static' +blood_t1 = 2.000 + +% the length of the contrast agent injection in +% number of acquisitions, for auto AIF only +injection_duration = 1 + +%%%%%%%%%%%%%%%% +%% RUNB OPTIONS +% Define timing parameters for analysis, derive fitted AIF or reference +% region from 1+ datasets + +% start time to restrict analysis to (in minutes, 0 if all) +start_time = 0 + +% end time to restrict analysis to (in minutes, 0 if all) +end_time = 0 + +% disable if you really know what you're doing +auto_find_injection = 1 + % start of contrast injection (in minutes) + start_injection = 0.0867 + % end of contrast injection (in minutes) + end_injection = 0.2167 + +% time resolution of dynamic scan (in seconds) +time_resolution = 15.36 + +% raw = 2, fitted = 1, import = 3, raw for reference region +aif_type = 1 + import_aif_path = + +% bool to use manual time vector +timevectyn = 0 + % import manual time vector from .mat file (string) + timevectpath = + +%%%%%%%%%%%%%%%% +%% RUND OPTIONS +% Choose kinetic models for data fitting, derive parameters for single +% dataset. Remember to check `dce_preferences.txt` for fitting parameters. + +% DCE model bools (1 = use), T1 maps required for all except auc +% Tofts +tofts = 0 + +% Tofts extended (w/ Vp) +ex_tofts = 0 + +% "shutter speed" +fxr = 0 + +% area under the curve (does not require T1 map) +auc = 0 + +% series of nested model +nested = 0 + +% 2 param model w/ no backflux +patlak = 1 + +% 3 param model, assumes cp>>ct +tissue_uptake = 0 + +% Two Compartment eXchange Model +two_cxm = 0 + +% Fast eXchange Limit tofts with reference region +FXL_rr = 0 + + +%dce_model.fractal = + +% type of time smoothing +% options: none, moving (avg), rlowess (robust local regression) +time_smoothing = none +% size of time smoothing window (in time points) +time_smoothing_window = 5 +% sigma of the Gaussian low pass smooth function +xy_smooth_size = 0 +% # of CPU cores for parallel processing (0 for max, -1 for max-1) +number_cpus = 0 + +% paths to ROIs that specify homogenous regions to calculate a single +% DCE fit for. Values >0 considered in the ROI, values <=0 considered +% outside the ROI +% multiple files syntax = file1.nii file2.nii file3.nii +roi_list = +% bool to perform DCE fit on individual voxels +fit_voxels = 1 +% output filetype (1 = NIFTI, 2 = 3D DICOM, 3 = 2D DICOM) +outputft = 1 diff --git a/iNESMA_GPU.py b/iNESMA_GPU.py new file mode 100644 index 0000000..a813807 --- /dev/null +++ b/iNESMA_GPU.py @@ -0,0 +1,182 @@ +import nibabel as nib +import numpy as np +from numba import cuda, float32, int32 +import math +import sys + +def load_image(img_path): + img = nib.load(img_path) + data = img.get_fdata() + return data, img + +@cuda.jit +def inesma_kernel(data, smoothed_data, x_dim, y_dim, z_dim, t_dim, + local_neighborhood_x, local_neighborhood_y, local_neighborhood_z, + similarity_threshold, h): + x, y, z = cuda.grid(3) + if x < x_dim and y < y_dim and z < z_dim: + current_curve = cuda.local.array(shape=(100,), dtype=float32) # Adjust size as needed + for t in range(t_dim): + current_curve[t] = data[x, y, z, t] + array_size=local_neighborhood_x*local_neighborhood_y*local_neighborhood_z + similarities = cuda.local.array(shape=(1024,), dtype=float32) + indices = cuda.local.array(shape=(1024, 3), dtype=int32) + count = 0 + + for i in range(max(0, x-local_neighborhood_x), min(x_dim, x+local_neighborhood_x+1)): + for j in range(max(0, y-local_neighborhood_y), min(y_dim, y+local_neighborhood_y+1)): + for k in range(max(0, z-local_neighborhood_z), min(z_dim, z+local_neighborhood_z+1)): + if i == x and j == y and k == z: + continue + + neighbor_curve = cuda.local.array(shape=(100,), dtype=float32) # Adjust size as needed + for t in range(t_dim): + neighbor_curve[t] = data[i, j, k, t] + + dist = 0.0 + location = 0.0 + for t in range(t_dim): + dist += (current_curve[t] - neighbor_curve[t]) ** 2 + location += current_curve[t] ** 2 + dist = math.sqrt(dist) + location = math.sqrt(location) + + #similarity = math.exp(-dist / h) + similarity = dist/location + + similarities[count] = similarity + indices[count, 0] = i + indices[count, 1] = j + indices[count, 2] = k + count += 1 + + # # Find the top 5% most similar voxels + # threshold_index = max(1, int(similarity_threshold * count)) + + # # Manual selection sort to find top 5% similarities + # # Uhhh, there are library functions for this.... + # for i in range(threshold_index): + # max_idx = i + # for j in range(i+1, count): + # if similarities[j] > similarities[max_idx]: + # max_idx = j + # if max_idx != i: + # # Swap values in similarities + # temp_similarity = similarities[i] + # similarities[i] = similarities[max_idx] + # similarities[max_idx] = temp_similarity + + # # Swap values in indices + # temp_indices = (indices[i, 0], indices[i, 1], indices[i, 2]) + # indices[i, 0] = indices[max_idx, 0] + # indices[i, 1] = indices[max_idx, 1] + # indices[i, 2] = indices[max_idx, 2] + # indices[max_idx, 0] = temp_indices[0] + # indices[max_idx, 1] = temp_indices[1] + # indices[max_idx, 2] = temp_indices[2] + + weighted_sum = cuda.local.array(shape=(100,), dtype=float32) # Adjust size as needed + for t in range(t_dim): + weighted_sum[t] = 0.0 + normalization_factor = 0.0 + + for idx in range(count): + if similarities[idx] < similarity_threshold: + # if constant_weighting: + if h == 0: + similarity = 1 + else: + similarity = math.exp(-similarities[idx] / h) + i = indices[idx, 0] + j = indices[idx, 1] + k = indices[idx, 2] + + neighbor_curve = cuda.local.array(shape=(100,), dtype=float32) # Adjust size as needed + for t in range(t_dim): + neighbor_curve[t] = data[i, j, k, t] + + for t in range(t_dim): + weighted_sum[t] += similarity * neighbor_curve[t] + normalization_factor += similarity + + if normalization_factor != 0: + for t in range(t_dim): + smoothed_data[x, y, z, t] = weighted_sum[t] / normalization_factor + else: + for t in range(t_dim): + smoothed_data[x, y, z, t] = current_curve[t] + +def inesma_smoothing(data, num_iterations=2, local_neighborhood_x=5,local_neighborhood_y=5,local_neighborhood_z=2, + similarity_threshold=0.3, h=0.01): + # num_iterations defines the number of iterations for smoothing + + # local_neighborhood defines radius in voxels of neighborhood for smoothing + # TODO: this should be defined in mm, not in voxels + + # similarity_threshold defines the threshold to accept a voxel for smoothing + + # h defines the exponential decay of the weighting, h=0 means constant weighting + if (local_neighborhood_x*2+1)*(local_neighborhood_y*2+1)*(local_neighborhood_z*2+1) > 1024: + raise ValueError("The neighborhood size exceeds the fixed array size in the kernel. Adjust the array size in the kernel.") + + x_dim, y_dim, z_dim, t_dim = data.shape + smoothed_data = np.copy(data) + + data_device = cuda.to_device(data) + smoothed_data_device = cuda.to_device(smoothed_data) + + threads_per_block = (8, 8, 8) + blocks_per_grid_x = (x_dim + threads_per_block[0] - 1) // threads_per_block[0] + blocks_per_grid_y = (y_dim + threads_per_block[1] - 1) // threads_per_block[1] + blocks_per_grid_z = (z_dim + threads_per_block[2] - 1) // threads_per_block[2] + + for _ in range(num_iterations): + inesma_kernel[(blocks_per_grid_x, blocks_per_grid_y, blocks_per_grid_z), threads_per_block]( + data_device, smoothed_data_device, x_dim, y_dim, z_dim, t_dim, + local_neighborhood_x, local_neighborhood_y, local_neighborhood_z, + similarity_threshold, h + ) + data_device = cuda.to_device(smoothed_data_device.copy_to_host()) + + smoothed_data = smoothed_data_device.copy_to_host() + return smoothed_data + +# Load and smooth the image +prefix = sys.argv[1] +img_path = sys.argv[2] +data, img = load_image(img_path) + +roi_path = sys.argv[3] +roi_data, roi_img = load_image(roi_path) + +# expand roi_data to 4D and match the shape of data +# roi_data = np.expand_dims(roi_data, axis=3) +# roi_data = np.repeat(roi_data, data.shape[3], axis=3) + +# save data where roi_data > 0 +data_restore = np.copy(data) +data_restore[roi_data == 0] = 0 + +# exclude roi_data from data +data[roi_data > 0] = 0 + +# Ensure t_dim does not exceed the array size in the kernel +t_dim = data.shape[3] +if t_dim > 100: + raise ValueError("The time dimension (t_dim) exceeds the fixed array size in the kernel. Adjust the array size in the kernel.") + +# normalize the data robustly (2nd and 98th percentile) +p2 = np.percentile(data, 2) +p98 = np.percentile(data, 98) +data = np.clip(data, p2, p98) +data = (data - p2) / (p98 - p2) + +# SHMOOOTHING +smoothed_data = inesma_smoothing(data) + +# Restore roi in smoothed data +smoothed_data[roi_data > 0] = data_restore[roi_data > 0] + +# Save the smoothed image +smoothed_img = nib.Nifti1Image(smoothed_data, img.affine, img.header) +nib.save(smoothed_img, f'dce/{prefix}_desc-bfczSmoothed_DCE.nii.gz') diff --git a/auto_analysis.py b/ktrans_analysis.py similarity index 84% rename from auto_analysis.py rename to ktrans_analysis.py index 47112a5..e388181 100644 --- a/auto_analysis.py +++ b/ktrans_analysis.py @@ -1,9 +1,10 @@ +from utils.constants import KTRANS_MIN_THRESHOLD import sys from pathlib import Path from statistics import mean, median, pstdev, stdev import numpy as np import matplotlib -from numpy.lib.function_base import average +# from numpy.lib.function_base import average matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.ticker as mtick @@ -12,14 +13,19 @@ # add as arg? add mask arg? POLYFIT = True -KTRANS_MIN_THRESHOLD = 0.00001 -def analyze(file_dir): +def analyze(tp_dir): # load files from script pipeline - files = ['/T1_wm.nii.gz', '/T1_gm.nii.gz', '/T1_csf.nii.gz', '/Ktrans_wm.nii.gz', - '/Ktrans_gm.nii.gz', '/Ktrans_csf.nii.gz', '/15_wm_mask_dyn.nii.gz', '/15_gm_mask_dyn.nii.gz'] - for i, file in enumerate(files): - files[i] = file_dir + file + files = ['anat/' + prefix + '_space-DCEref_label-WM_T1map.nii.gz', + 'anat/' + prefix + '_space-DCEref_label-GM_T1map.nii.gz', + 'anat/' + prefix + '_space-DCEref_label-CSF_T1map.nii.gz', + 'dce/' + prefix + '_seg-WM_Ktrans.nii.gz', + 'dce/' + prefix + '_seg-GM_Ktrans.nii.gz', + 'dce/' + prefix + '_seg-CSF_Ktrans.nii.gz', + 'anat/' + prefix + '_space-DCEref_label-WM_mask.nii.gz', + 'anat/' + prefix + '_space-DCEref_label-GM_mask.nii.gz'] + # for i, file in enumerate(files): + # files[i] = file_dir + file T1_wm = nib.load(files[0]) T1_gm = nib.load(files[1]) @@ -104,13 +110,13 @@ def analyze(file_dir): a = np.where(Ktrans_wm_data[:, :, i] > KTRANS_MIN_THRESHOLD) if a[0].size > 0: - Ktrans_wm_mean.append(Ktrans_wm_data[:, :, i][a].mean()) + Ktrans_wm_mean.append(np.nanmean(Ktrans_wm_data[:, :, i][a])) else: Ktrans_wm_mean.append(0) a = np.where(Ktrans_gm_data[:, :, i] > KTRANS_MIN_THRESHOLD) if a[0].size > 0: - Ktrans_gm_mean.append(Ktrans_gm_data[:, :, i][a].mean()) + Ktrans_gm_mean.append(np.nanmean(Ktrans_gm_data[:, :, i][a])) else: Ktrans_gm_mean.append(0) @@ -122,13 +128,13 @@ def analyze(file_dir): a = np.where(Ktrans_wm_data[:, :, i] > KTRANS_MIN_THRESHOLD) if a[0].size > 0: - Ktrans_wm_median.append(median(Ktrans_wm_data[:, :, i][a])) + Ktrans_wm_median.append(np.nanmedian(Ktrans_wm_data[:, :, i][a])) else: Ktrans_wm_median.append(0) a = np.where(Ktrans_gm_data[:, :, i] > KTRANS_MIN_THRESHOLD) if a[0].size > 0: - Ktrans_gm_median.append(median(Ktrans_gm_data[:, :, i][a])) + Ktrans_gm_median.append(np.nanmedian(Ktrans_gm_data[:, :, i][a])) else: Ktrans_gm_median.append(0) @@ -175,10 +181,10 @@ def analyze(file_dir): Ktrans_wm_1D = Ktrans_wm_data.flatten() Ktrans_gm_1D = Ktrans_gm_data.flatten() # Ktrans_csf_data = Ktrans_csf_data.flatten() - Ktrans_wm_median_truncated = median(Ktrans_wm_1D[Ktrans_wm_1D > KTRANS_MIN_THRESHOLD]) - Ktrans_gm_median_truncated = median(Ktrans_gm_1D[Ktrans_gm_1D > KTRANS_MIN_THRESHOLD]) - Ktrans_wm_stdev_truncated = stdev(Ktrans_wm_1D[Ktrans_wm_1D > KTRANS_MIN_THRESHOLD]) - Ktrans_gm_stdev_truncated = stdev(Ktrans_gm_1D[Ktrans_gm_1D > KTRANS_MIN_THRESHOLD]) + Ktrans_wm_median_truncated = np.nanmedian(Ktrans_wm_1D[Ktrans_wm_1D > KTRANS_MIN_THRESHOLD]) + Ktrans_gm_median_truncated = np.nanmedian(Ktrans_gm_1D[Ktrans_gm_1D > KTRANS_MIN_THRESHOLD]) + Ktrans_wm_stdev_truncated = np.nanstd(Ktrans_wm_1D[Ktrans_wm_1D > KTRANS_MIN_THRESHOLD]) + Ktrans_gm_stdev_truncated = np.nanstd(Ktrans_gm_1D[Ktrans_gm_1D > KTRANS_MIN_THRESHOLD]) # for i in range(slice_num) # T1_wm_zeros = size(T1_wm_data[T1_wm_data[:,:,i] == 0]) @@ -241,9 +247,9 @@ def analyze(file_dir): _, max_ylim = ax1.get_ylim() ax1.axvline(Ktrans_wm_median_truncated, color='pink', linestyle='dashed') ax1.axvline(Ktrans_gm_median_truncated, color='gray', linestyle='dashed') - ax1.text(Ktrans_wm_median_truncated*.1, max_ylim*0.2, 'Median: {:.5f}'.format(Ktrans_wm_median_truncated), color='whitesmoke') + ax1.text(Ktrans_wm_median_truncated*.1, max_ylim*0.2, 'Median: {:.5f}'.format(Ktrans_wm_median_truncated), color='lightgray') ax1.text(Ktrans_gm_median_truncated*1.1, max_ylim*0.9, 'Median: {:.5f}'.format(Ktrans_gm_median_truncated), color='gray') - ax1.text(Ktrans_wm_median_truncated*.1, max_ylim*0.1, 'stdev: {:.5f}'.format(Ktrans_wm_stdev_truncated), color='whitesmoke') + ax1.text(Ktrans_wm_median_truncated*.1, max_ylim*0.1, 'stdev: {:.5f}'.format(Ktrans_wm_stdev_truncated), color='lightgray') ax1.text(Ktrans_gm_median_truncated*1.1, max_ylim*0.8, 'stdev: {:.5f}'.format(Ktrans_gm_stdev_truncated), color='gray') # ax1.axvline(mean(Ktrans_csf_data[Ktrans_csf_data > 0]), color = 'cyan', linestyle = 'dashed') ax1.legend() @@ -251,7 +257,7 @@ def analyze(file_dir): ## T1 medians plot ax2.set_xlabel('Slice #') ax2.set_ylabel('T1 Medians') - ax2.set_ylim([0, 2200]) + ax2.set_ylim([0, 2700]) ax2.plot(range(slice_num), T1_wm_median, label='wm', color='pink') ax2.plot(range(slice_num), T1_gm_median, label='gm', color='gray') # ax2.plot(range(slice_num), T1_csf_mean, label = 'csf', color = 'cyan') @@ -261,7 +267,7 @@ def analyze(file_dir): ## Ktrans medians plot ax3.set_xlabel('Slice #') ax3.set_ylabel('Ktrans Medians') - ax3.set_ylim([0, 0.017]) + ax3.set_ylim([0, 0.005]) ax3.plot(range(slice_num), Ktrans_wm_median, label='wm', color='pink') ax3.plot(range(slice_num), Ktrans_gm_median, label='gm', color='gray') # ax3.plot(range(slice_num), Ktrans_csf_mean, label = 'csf', color = 'cyan') @@ -269,12 +275,12 @@ def analyze(file_dir): ax3.legend() # Save graphs - path2 = file_dir + '/T1_Ktrans_analysis.png' + path2 = 'figures/' + prefix + '_desc-analysis.png' plt.savefig(path2, bbox_inches='tight') # Zeros # fig2, ((ax4, ax5)) = plt.subplots(2, 1, figsize=(20,6)) - fig2, ax5 = plt.subplots(1, 1, figsize=(20,6)) + fig2, ax5 = plt.subplots(1, 1, figsize=(10,6)) ## T1 zeros plot # ax4.set_xlabel('Slice #') @@ -300,8 +306,8 @@ def analyze(file_dir): for i in range(slice_num): Ktrans_wm_slicevoxels.append(len(Ktrans_wm_data[:, :, i][wm_data[:, :, i] > 0])) Ktrans_gm_slicevoxels.append(len(Ktrans_gm_data[:, :, i][gm_data[:, :, i] > 0])) - Ktrans_wm_zero_avg = average(Ktrans_wm_zeros, weights=Ktrans_wm_slicevoxels) - Ktrans_gm_zero_avg = average(Ktrans_gm_zeros, weights=Ktrans_gm_slicevoxels) + Ktrans_wm_zero_avg = np.average(Ktrans_wm_zeros, weights=Ktrans_wm_slicevoxels) + Ktrans_gm_zero_avg = np.average(Ktrans_gm_zeros, weights=Ktrans_gm_slicevoxels) min_ylim, max_ylim = ax5.get_ylim() min_xlim, max_xlim = ax5.get_xlim() ax5.hlines(Ktrans_wm_zero_avg, min_xlim, max_xlim, color='pink', linestyle='dashed') @@ -312,8 +318,12 @@ def analyze(file_dir): ax5.grid() ax5.legend() - plt.savefig(file_dir + '/T1_Ktrans_zeros.png', bbox_inches='tight') + plt.savefig('figures/' + prefix + '_desc-zeros.png', bbox_inches='tight') + # print(any(np.array(Ktrans_wm_zeros) >= 70) or any(np.array(Ktrans_gm_zeros) >= 70)) + # print((Ktrans_wm_zero_avg >= 55) or (Ktrans_gm_zero_avg >= 55)) -dir = Path(sys.argv[1]) # takes timepoint directory as argument -analyze(str(dir)) +tp_dir = Path(sys.argv[1]) # takes timepoint directory as argument +# output_dir = Path(sys.argv[2]) # takes output directory as argument +prefix = sys.argv[2] # takes prefix as argument +analyze(str(tp_dir)) diff --git a/ktrans_report.py b/ktrans_report.py new file mode 100644 index 0000000..1c140cc --- /dev/null +++ b/ktrans_report.py @@ -0,0 +1,182 @@ +import json +import matplotlib.pyplot as plt +import matplotlib.image as mpimg +import matplotlib.gridspec as gridspec +from matplotlib.pyplot import subplots_adjust +import numpy as np +import nibabel as nib +from pathlib import Path +import sys +import cairosvg +import imageio.v2 as imageio + + +dir = Path(sys.argv[1]) +# output_dir = Path(sys.argv[2]) +prefix = sys.argv[2] +# try: +# cmap = str(Path(sys.argv[2])) +# except: +# cmap = 'gnuplot' + + +# AIF_overlay = mpimg.imread('figures/AIF_overlay.svg') +curves = [] +svg_path = f'figures/{prefix}_desc-AIF_overlay.svg' +png_bytes = cairosvg.svg2png(file_obj=open(svg_path, "rb")) +png_np_array = imageio.imread(png_bytes, format='png') +curves.append(png_np_array) +svg_path = f'figures/{prefix}_desc-AIF_curve.svg' +png_bytes = cairosvg.svg2png(file_obj=open(svg_path, "rb")) +png_np_array = imageio.imread(png_bytes, format='png') +curves.append(png_np_array) +curves.append(mpimg.imread('figures/dceAIF_fitting.png')) +curves.append(mpimg.imread('figures/dce_timecurves.png')) +ktrans = nib.load('dce/' + prefix + '_Ktrans.nii') +analysis = mpimg.imread('figures/' + prefix + '_desc-analysis.png') +zeros = mpimg.imread('figures/' + prefix + '_desc-zeros.png') +aif_curve = mpimg.imread('figures/dceAIF_fitting.png') +timecurves = mpimg.imread('figures/dce_timecurves.png') +plots = [] +plots.append(mpimg.imread('figures/' + prefix + '_desc-zeros.png')) +if Path('figures/displacements.png').exists(): + plots.append(mpimg.imread('figures/displacements.png')) +try: + json_file = open(f'{dir}/dce/{prefix}_DCE.json') + json_dict = json.load(json_file) + site = json_dict['InstitutionName'] + date = json_dict['AcquisitionDateTime'].split('T')[0] + json_file.close() +except: + site = "no json" + date = "no json" + +dim = {0,1,2} +ktrans_data = ktrans.get_fdata() +ktrans_shape = ktrans_data.shape +slice_num = min(ktrans_shape[0], ktrans_shape[1], ktrans_shape[2]) +slice_loc = ktrans_shape.index(slice_num) +ktrans_data = np.reshape(ktrans_data, (ktrans_shape[min(dim-set([slice_loc]))], ktrans_shape[max(dim-set([slice_loc]))], slice_num)) +slices = [] +for i in range(slice_num): + slices.append(ktrans_data[:,:,i].T) + +fig, axs = plt.subplots(4, 1, figsize=(8.5,11), dpi=300) +subject = str(dir).split('/')[-2] +axs[0].set_title(dir, y=1.02, fontsize='small') +plt.suptitle(str(subject), fontsize='large', y=1.07) # Increase the y value to add extra space at the top +# put text below the title +axs[0].text(0, 1.2, f'Site: {site}', fontsize='small') +axs[0].text(0, 1.1, f'Scan Date: {date}', fontsize='small') +axs[0].axis('off') +axs[1].axis('off') +axs[2].axis('off') +axs[3].axis('off') +axs[2].imshow(analysis) +x = axs[3].imshow(zeros, cmap='gnuplot', vmin=0, vmax=.009) + +gspec = axs[0].get_subplotspec().get_gridspec() +gridspec = axs[1].get_subplotspec().get_gridspec() +gridspec2 = axs[3].get_subplotspec().get_gridspec() +subfig1 = fig.add_subfigure(gspec[3,:]) +subfig2 = fig.add_subfigure(gridspec[1,:]) +subfig3 = fig.add_subfigure(gridspec2[0,:]) +plot_rows = subfig1.subplots(1,2) +if slice_num > 8: + row = subfig2.subplots(2,int(slice_num/2)) +else: + row = subfig2.subplots(1,slice_num) +curve_rows = subfig3.subplots(1,4) + +# cmap = 'gnuplot' +i = 0 +for ax in curve_rows: + ax.axis('off') + if i > 1: + ax.imshow(curves[i], aspect='auto') + else: + ax.imshow(curves[i]) + i+=1 + +i = 0 +for ax in row.flat: + ax.axis('off') + ax.set_xlim(30, 290) + ax.set_ylim(20, 310) + # ax.pcolormesh(slices[i], cmap=cmap, vmin=0, vmax=.009) + x=ax.imshow(slices[i], cmap='gnuplot', vmin=0, vmax=0.009) + i+=1 + +i = 0 +for ax in plot_rows: + ax.axis('off') + ax.imshow(plots[i]) + i += 1 + +# fig.tight_layout(pad=-.7) +subplots_adjust(top=0.99, bottom=0.0, left=-0.0, right=1.0, hspace=0, wspace=-.0) +# cax = fig.add_axes([0.0, 0.23, 1, .02]) +# colorbar = fig.colorbar(x, orientation='horizontal', label='Ktrans (/min)', pad=.02, aspect = 60) +cax = fig.add_axes([1,0.5,.01,.246]) +colorbar = fig.colorbar(x, cax=cax, orientation='vertical', label='Ktrans (/min)', pad=.02) +colorbar.set_label('Ktrans (10^-3/min)', labelpad=-15, fontsize = 'xx-small', color = 'white') +colorbar.ax.yaxis.set_ticks(np.arange(0, 10, 1)) +colorbar.ax.set_yticklabels(np.arange(0, 10, 1), fontsize='xx-small') + +plt.savefig('reports/' + prefix + '_desc-report.png', bbox_inches='tight') + + +## REGISTRATION QC +diff = nib.load('anat/' + prefix + '_space-DCEref_label-WMQC.nii.gz') +diff_data = diff.get_fdata() +# diff_shape = diff_data.shape +# diff_data = np.reshape(diff_data, (ktrans_shape[min(dim-set([slice_loc]))], ktrans_shape[max(dim-set([slice_loc]))], slice_num)) + +fig2, ax2 = plt.subplots(figsize=(20, 6)) +ax2.axis('off') +gridspec_reg = ax2.get_subplotspec().get_gridspec() +subfig_reg = fig2.add_subfigure(gridspec_reg[0,:]) +if slice_num > 8: + reg_rows = subfig_reg.subplots(2,int(slice_num/2)) +else: + reg_rows = subfig_reg.subplots(1,slice_num) +i=0 +for ax in reg_rows.flat: + ax.axis('off') + # ax.set_xlim(30, 290) + # ax.set_ylim(20, 310) + # ax.pcolormesh(slices[i], cmap=cmap, vmin=0, vmax=.009) + x=ax.imshow(diff_data[:,:,i].T, cmap='gray', origin='lower', vmin=0, vmax=500) + i+=1 +fig2.tight_layout(pad=-2) +# ax.imshow(diff_data[:,:,7], cmap=cmap) +# ax.axis('off') + +plt.savefig('figures/' + prefix + '_desc-WMQC_DCE.png', bbox_inches='tight') + +diff = nib.load('anat/' + prefix + '_space-DCEref_label-GMQC.nii.gz') +diff_data = diff.get_fdata() +# diff_shape = diff_data.shape +# diff_data = np.reshape(diff_data, (ktrans_shape[min(dim-set([slice_loc]))], ktrans_shape[max(dim-set([slice_loc]))], slice_num)) + +fig3, ax3 = plt.subplots(figsize=(20, 6)) +ax3.axis('off') +gridspec_reg = ax3.get_subplotspec().get_gridspec() +subfig_reg = fig3.add_subfigure(gridspec_reg[0,:]) +if slice_num > 8: + reg_rows = subfig_reg.subplots(2,int(slice_num/2)) +else: + reg_rows = subfig_reg.subplots(1,slice_num) +i=0 +for ax in reg_rows.flat: + ax.axis('off') + # ax.set_xlim(30, 290) + # ax.set_ylim(20, 310) + # ax.pcolormesh(slices[i], cmap=cmap, vmin=0, vmax=.009) + x=ax.imshow(diff_data[:,:,i].T, cmap='gray', origin='lower', vmin=0, vmax=500) + i+=1 +fig3.tight_layout(pad=-2) +# ax.imshow(diff_data[:,:,7], cmap=cmap) +# ax.axis('off') + +plt.savefig('figures/' + prefix + '_desc-GMQC_DCE.png', bbox_inches='tight') diff --git a/max_disp.py b/max_disp.py index 72ded22..a3308ea 100644 --- a/max_disp.py +++ b/max_disp.py @@ -1,10 +1,126 @@ import sys +import matplotlib.pyplot as plt +import nibabel as nib import numpy as np +from math import sqrt from numpy.core.multiarray import unravel_index +# from numpy.lib.function_base import corrcoef +from sklearn.linear_model import LinearRegression +from statistics import mean + dir = sys.argv[1] -data = np.loadtxt(dir + "/DCE_mc.nii.par", dtype = float) -data[:,0:3] = data[:,0:3]*50 -max_i = data.argmax() -i = unravel_index(max_i, data.shape) -print("Max displacement of " + str(data[i]) + " mm at time slice " + str(i[0] + 1) + "/64, parameter " + str(i[1])) +prefix = sys.argv[2] +mc_params = np.loadtxt(dir + "/" + prefix + "_desc-hmc_DCE.nii.par", dtype=float) +mc_params[:,0:3] = mc_params[:,0:3]*70 +max_i = np.abs(mc_params).argmax() +max_disp_i = unravel_index(max_i, mc_params.shape) +max_disp = str(mc_params[max_disp_i]) +param_num = max_disp_i[1] +# get max displacement of 3d transformation +vector_max_disp = np.zeros(64) +# for i in range(64): +# vector_max_disp[i] = np.linalg.norm(mc_params[i], axis=0) +# vector_max_disp = np.abs(vector_max_disp).max() +for i in range(len(mc_params[:,0])): + vector_max_disp = sqrt((mc_params[i,3]+mc_params[i,0])**2 + (mc_params[i,4]+mc_params[i,1])**2 + (mc_params[i,5]+mc_params[i,2])**2) +vector_max_disp = np.abs(vector_max_disp).max() +vector_max_disp_i = np.abs(mc_params).argmax(axis=0)[param_num]+1 + +if param_num < 4: + param_type = "rot_" +else: + param_type = "trans_" + +if param_num % 3 == 0: + param_type = param_type + 'x' +elif param_num % 3 == 1: + param_type = param_type + 'y' +elif param_num % 3 == 2: + param_type = param_type + 'z' + +print("Max displacement: " + str(vector_max_disp) + "mm at frame " + str(vector_max_disp_i) + "/64") + +fig, ax = plt.subplots(figsize=(10, 6)) +colors = ['k', 'b', 'g', 'm', 'y', 'c'] +labels = ['rot_x', 'rot_y', 'rot_z', 'trans_x', 'trans_y', 'trans_z'] +for i in range(len(mc_params[0,:])): + # label = 'param ' + str(i) + ax.plot(range(len(mc_params[:,i])), mc_params[:,i], label=labels[i], color=colors[i]) + +plt.legend() +plt.grid() +plt.ylabel("Displacement (mm)") +plt.xlabel("") +plt.ylim([-2.5, 2.5]) +plt.text(len(mc_params[:,0])/3, 2, "Max vector displacement: " + str(round(vector_max_disp,4)) + "mm") +plt.text(len(mc_params[:,0])/3, 1.8, "Frame: " + str(vector_max_disp_i) + "/64") +# plt.text(len(mc_params[:,0])/2, 1.8, "param: " + param_type) +path = dir + '/../figures/displacements.svg' +plt.savefig(path, bbox_inches='tight') + +# save as png too +plt.savefig(dir + '/../figures/displacements.png', bbox_inches='tight') + +## PART 2 - Correlation +# dce = nib.load(dir + '/DCE.nii') +# dce_mc = nib.load(dir + '/DCE_mc.nii.gz') +# ktrans = nib.load(dir + '/dce_patlak_fit_Ktrans.nii') +# dce_data = dce.get_fdata() +# dce_mc_data = dce_mc.get_fdata() +# ktrans_data = ktrans.get_fdata() + +# path2 = dir + '/bozo.png' + +# mc_params (disps): 64x6 +# dce_mc: 320x320x14x64 +# dce_mc_data = np.reshape(dce_mc_data, (-1,64)) +# bozo = corrcoef(dce_mc_data[0,0,0,:], mc_params[:,0])[0,1] + +# Correlation Coefficient Image +# corr_dce = np.zeros((320,320,14,6)) +# corr_dce_mc = np.zeros((320,320,14,6)) +# for p in range(6): +# for i in range(dce_mc_data.shape[0]): +# for j in range(dce_mc_data.shape[1]): +# for k in range(dce_mc_data.shape[2]): +# corr_dce[i, j, k, p] = corrcoef(dce_data[i,j,k,:], mc_params[:, p])[0,1] +# corr_dce_mc[i, j, k, p] = corrcoef(dce_mc_data[i ,j, k, :], mc_params[:, p])[0,1] +# final_dce = nib.Nifti1Image(corr_dce, dce.affine) +# final_dce_mc = nib.Nifti1Image(corr_dce_mc, dce_mc.affine) +# path3 = dir + '/dce_corrcoef.nii' +# path4 = dir + '/dce_mc_corrcoef.nii' +# nib.save(final_dce, path3) +# nib.save(final_dce_mc, path4) + +# testing one voxel +# model = LinearRegression().fit(mc_params, dce_mc_data[87, 251, 7, :]) +# r_sq = model.score(mc_params, dce_mc_data[87, 251, 7, :]) +# print(r_sq) +# print(model.coef_) +# pred = model.predict(mc_params) +# residuals = (dce_mc_data[87, 251, 7, :] - pred) +# print(residuals) + +# Residuals image +# dce_mc_residuals = np.zeros((320,320,14,64)) +# for x in range(dce_mc_data.shape[0]): +# for y in range(dce_mc_data.shape[1]): +# for z in range(dce_mc_data.shape[2]): +# model = LinearRegression(positive=True).fit(mc_params, dce_mc_data[x, y, z, :]) +# pred = model.predict(mc_params) +# residuals = dce_mc_data[x, y, z, :] - pred +# dce_mc_residuals[x, y, z, :] = residuals + 250 + + +# final_dce_mc_residuals = nib.Nifti1Image(dce_mc_residuals, dce_mc.affine) +# path5 = dir + '/dce_mc_residuals_linear_150.nii' +# nib.save(final_dce_mc_residuals, path5) +# +# fig2, ax2 = plt.subplots(figsize=(10,6)) +# ax2.plot(range(64), mc_params[:, 0] / mean(mc_params[:, 0]), label='param 0') +# ax2.plot(range(64), dce_mc_data[87, 251, 7, :] / mean(dce_mc_data[87, 251, 7, :]), label = 'MC SI') +# ax2.plot(range(64), residuals, label='residual') + +# plt.legend() +# plt.savefig(path2, bbox_inches='tight') diff --git a/overview.png b/overview.png new file mode 100644 index 0000000..145fe0b Binary files /dev/null and b/overview.png differ diff --git a/population_report.py b/population_report.py new file mode 100644 index 0000000..d6cb881 --- /dev/null +++ b/population_report.py @@ -0,0 +1,2738 @@ +import jinja2 +import json +import os +import matplotlib.pyplot as plt +import nibabel as nib +import numpy as np +import pandas as pd +import datetime +import subprocess +import threading +import time +from sys import argv +from concurrent.futures import ThreadPoolExecutor +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas +from concurrent.futures import as_completed +from utils.constants import KTRANS_MIN_THRESHOLD + +dir = argv[1] +try: + output_dir = argv[2] +except: + output_dir = "" + +dceprep_dir = argv[1] + "/dceprep" + +try: + ROCKETSHIP_dir = argv[3] +except IndexError: + ROCKETSHIP_dir = argv[2] + output_dir = "" + +if output_dir != "": + output_dir = "-" + output_dir + dceprep_dir = dceprep_dir + output_dir + +# Load MRI population data dict with keys as subject IDs and values as gm and wm data +population_data = {} +population_data_exclude = {} +population_data_failed = {} +population_data_missing = {} +# list directories in dir +if not os.path.isdir(dceprep_dir): + print(f"{dceprep_dir} does not exist, trying current working directory") + dir_list = os.listdir(os.getcwd()) +else: + dir_list = os.listdir(dceprep_dir) +# filter out non-directories +subjects = [subject for subject in dir_list if os.path.isdir(os.path.join(dceprep_dir, subject)) and not subject.startswith("figures") and not subject.startswith("logs")] +subjects.sort() +# use text file list for subjects, format is subject date timepoint +# get list of subjects +# subjects = [f"sub-{line.split(' ')[0]}" for line in open(os.path.join(dir, "../code/CBF_list.txt"), "r").readlines()] +# get list of timepoints +# timepoints = [line.split(" ")[2][:-1] for line in open(os.path.join(dir, "../code/CBF_list.txt"), "r").readlines()] +# print(timepoints) +# go into each subject directory and count number of successful_timepoints +# for subject_id in subjects: +# # list _timepoint directories in subject directory +# for timepoint in os.listdir(os.path.join(dir, subject_id)): +# if timepoint.endswith("_timepoint"): +# successful_timepoints.append(subject_id + '/' + timepoint) +manual_aif_status = "AUTO" +# read log file for command used to run dceprep +# log is preprocessing_log_{date}.txt, use latest log +logs = os.listdir(os.path.join(dir, "logs")) +logs = [log for log in logs if log.startswith("preprocessing_log")] +logs.sort() +log = logs[-1] +log = os.path.join(dir, "logs", log) +command = "" +with open(log, "r") as f: + for line in f: + if "Command: " in line: + command = line + break +use_manual_aif = False +if "-A M" in command or "-A T" in command: + use_manual_aif = True + +# Outlier and tracking lists +Ktrans_wm_outliers, Ktrans_gm_outliers = [], [] +whole_hippo_outliers, whole_phg_outliers, whole_putamen_outliers = [], [], [] +whole_pallidum_outliers, whole_thalamus_outliers, whole_caudate_outliers = [], [], [] +whole_amygdala_outliers, whole_entorhinal_cortex_outliers = [], [] +whole_fusiform_gyrus_cortex_outliers, whole_fusiform_gyrus_WM_outliers = [], [] +whole_insula_WM_outliers, whole_superior_temporal_cortex_outliers = [], [] +whole_inferior_temporal_cortex_outliers, whole_posterior_cingulate_cortex_outliers = [], [] +whole_medial_temporal_cortex_outliers = [] + +wm_outliers_exclude, gm_outliers_exclude = [], [] +whole_hippo_outliers_exclude, whole_phg_outliers_exclude, whole_putamen_outliers_exclude = [], [], [] +whole_pallidum_outliers_exclude, whole_thalamus_outliers_exclude, whole_caudate_outliers_exclude = [], [], [] +whole_amygdala_outliers_exclude, whole_entorhinal_cortex_outliers_exclude = [], [] +whole_fusiform_gyrus_cortex_outliers_exclude, whole_fusiform_gyrus_WM_outliers_exclude = [], [] +whole_insula_WM_outliers_exclude, whole_superior_temporal_cortex_outliers_exclude = [], [] +whole_inferior_temporal_cortex_outliers_exclude, whole_posterior_cingulate_cortex_outliers_exclude = [], [] +whole_medial_temporal_cortex_outliers_exclude = [] + +total_timepoints, successful_timepoints = [], [] +popAIF_curves, aif_curves = [], [] +def get_case_stats(subject_id, timepoint): + wmparc_failed = False + stats_failed = False + missing = False + AIFitness = aif_fitted_r2 = max_disp = T1_wm_median = T1_wm_std = T1_gm_median = T1_gm_std = Ktrans_wm_mean = Ktrans_wm_median = Ktrans_wm_std = Ktrans_gm_mean = Ktrans_gm_median = Ktrans_gm_std = 0 + hippo_vol = phg_vol = putamen_vol = pallidum_vol = thalamus_vol = caudate_vol = amygdala_vol = -1 + entorhinal_cortex_vol = fusiform_gyrus_cortex_vol = fusiform_gyrus_wm_vol = insula_wm_vol = -1 + superior_temporal_cortex_vol = inferior_temporal_cortex_vol = posterior_cingulate_cortex_vol = medial_temporal_cortex_vol = -1 + Ktrans_Hippo_median = Ktrans_PhG_median = Ktrans_Putamen_median = Ktrans_Pallidum_median = -1 + Ktrans_Thalamus_median = Ktrans_Caudate_median = Ktrans_Amygdala_median = Ktrans_Entorhinal_cortex_median = -1 + Ktrans_Fusiform_gyrus_cortex_median = Ktrans_Fusiform_gyrus_WM_median = Ktrans_Insula_WM_median = -1 + Ktrans_Superior_temporal_cortex_median = Ktrans_Inferior_temporal_cortex_median = Ktrans_Posterior_cingulate_cortex_median = Ktrans_Medial_temporal_cortex_median = -1 + Vp_Hippo_median = Vp_PhG_median = Vp_Putamen_median = Vp_Pallidum_median = Vp_Thalamus_median = -1 + Vp_Caudate_median = Vp_Amygdala_median = Vp_Entorhinal_cortex_median = Vp_Fusiform_gyrus_cortex_median = -1 + Vp_Fusiform_gyrus_WM_median = Vp_Insula_WM_median = Vp_Superior_temporal_cortex_median = Vp_Inferior_temporal_cortex_median = -1 + Vp_Posterior_cingulate_cortex_median = Vp_Medial_temporal_cortex_median = SNR = -1 + bankssts_thickness_avg = bankssts_thickness_std = caudalanteriorcingulate_thickness_avg = caudalanteriorcingulate_thickness_std = -1 + caudalmiddlefrontal_thickness_avg = caudalmiddlefrontal_thickness_std = cuneus_thickness_avg = cuneus_thickness_std = -1 + entorhinal_thickness_avg = entorhinal_thickness_std = fusiform_thickness_avg = fusiform_thickness_std = -1 + inferiorparietal_thickness_avg = inferiorparietal_thickness_std = inferiortemporal_thickness_avg = inferiortemporal_thickness_std = -1 + insula_thickness_avg = insula_thickness_std = isthmuscingulate_thickness_avg = isthmuscingulate_thickness_std = -1 + lateraloccipital_thickness_avg = lateraloccipital_thickness_std = lateralorbitofrontal_thickness_avg = lateralorbitofrontal_thickness_std = -1 + lingual_thickness_avg = lingual_thickness_std = medialorbitofrontal_thickness_avg = medialorbitofrontal_thickness_std = -1 + middletemporal_thickness_avg = middletemporal_thickness_std = parahippocampal_thickness_avg = parahippocampal_thickness_std = -1 + paracentral_thickness_avg = paracentral_thickness_std = parsopercularis_thickness_avg = parsopercularis_thickness_std = -1 + parsorbitalis_thickness_avg = parsorbitalis_thickness_std = parstriangularis_thickness_avg = parstriangularis_thickness_std = -1 + pericalcarine_thickness_avg = pericalcarine_thickness_std = postcentral_thickness_avg = postcentral_thickness_std = -1 + posteriorcingulate_thickness_avg = posteriorcingulate_thickness_std = precentral_thickness_avg = precentral_thickness_std = -1 + precuneus_thickness_avg = precuneus_thickness_std = rostralanteriorcingulate_thickness_avg = rostralanteriorcingulate_thickness_std = -1 + rostralmiddlefrontal_thickness_avg = rostralmiddlefrontal_thickness_std = superiorfrontal_thickness_avg = superiorfrontal_thickness_std = -1 + superiorparietal_thickness_avg = superiorparietal_thickness_std = superiortemporal_thickness_avg = superiortemporal_thickness_std = -1 + frontalpole_thickness_avg = frontalpole_thickness_std = supramarginal_thickness_avg = supramarginal_thickness_std = -1 + temporalpole_thickness_avg = temporalpole_thickness_std = transversetemporal_thickness_avg = transversetemporal_thickness_std = -1 + + if timepoint.startswith("ses-"): + total_timepoints.append(subject_id + '/' + timepoint) + # Check for missing data in rawdata folder + prefix = f"{subject_id}_{timepoint}" + rawdata_dir = os.path.join(dir, "../rawdata", subject_id, timepoint) + missing_files = [] + # Check for anat/prefix_T1w.nii.gz + t1w_path = os.path.join(rawdata_dir, f"anat/{prefix}_T1w.nii.gz") + if not os.path.exists(t1w_path): + missing_files.append("anat/" + os.path.basename(t1w_path)) + # Check for at least one anat/prefix_*_VFA.nii.gz + anat_dir = os.path.join(rawdata_dir, "anat") + vfa_files = [] + if os.path.isdir(anat_dir): + vfa_files = [f for f in os.listdir(anat_dir) if f.startswith(prefix) and "_VFA" in f and f.endswith(".nii.gz")] + if len(vfa_files) == 0: + missing_files.append("anat/*_VFA.nii.gz") + # Check for dce/prefix_DCE.nii.gz + dce_path = os.path.join(rawdata_dir, f"dce/{prefix}_DCE.nii.gz") + if not os.path.exists(dce_path): + missing_files.append("dce/" + os.path.basename(dce_path)) + if missing_files: + print(f"Missing rawdata for {subject_id} {timepoint}: {', '.join(missing_files)}") + population_data_missing[subject_id + "_" + timepoint] = {"Missing_files": missing_files} + missing = True + return + else: + missing = False + # read AIF curve by applying aif.nii to dce.nii + try: + dce = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_desc-bfcz_DCE.nii.gz") + aif = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_desc-AIF_T1map.nii.gz") + if os.path.exists(dce) and os.path.exists(aif): + # load files + dce_img = nib.load(dce) + aif_img = nib.load(aif) + + # get data from file + aif = aif_img.get_fdata() + dce = dce_img.get_fdata() + + # binarize aif + aif = aif > 400 + + # get curve from masked dce + aif = aif.reshape(aif.shape[0], aif.shape[1], aif.shape[2], 1) + roi_ = dce * aif + num = np.sum(roi_, axis = (0, 1, 2), keepdims=False) + den = np.sum(aif, axis = (0, 1, 2), keepdims=False) + + # normalize to baseline + intensities = num/(den+1e-8) + intensities = np.asarray(intensities) + intensities = intensities/intensities[0] + if intensities[0] != 1: + print("error") + # if intensities[1] < 3 and intensities[2] < 3: + # print(file + " has a weak AIF curve with " + str(intensities[1]) + " and " + str(intensities[2])) + # if intensities[2] > intensities[1] or intensities[3] > intensities[2]+.5: + # print(file + " has a delayed injection with " + str(intensities[1]) + " and " + str(intensities[2]) + " and " + str(intensities[3])) + # if any(intensities[10:30] < 2): + # print(subject_id, timepoint, "has an intensity < 2") + # line up curve peaks + max_index = np.argmax(intensities) + # intensities = np.roll(intensities, -max_index+2) + if intensities.shape[0] < 40: + mean_last_7 = np.mean(intensities[-7:]) + intensities = np.pad(intensities, (0, 40-intensities.shape[0]), 'constant', constant_values=(mean_last_7)) + aif_curves.append(intensities[0:40]) + intensities = np.roll(intensities, -max_index) + if intensities.shape[0] == 64: + # make last five values 0 then roll back + intensities[-5:] = 1 + intensities = np.roll(intensities, 5) + if not np.isnan(intensities).any(): + popAIF_curves.append(intensities) + else: + print("DCE or AIF file does not exist for", subject_id, timepoint) + return + except Exception as e: + print("Error reading DCE or AIF for", subject_id, timepoint) + print(e) + return + + # if use manual AIF and file exists, mark as manual + manual_aif_path = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_desc-AIF_mask.nii.gz") + if use_manual_aif and os.path.isfile(manual_aif_path): + manual_aif_status = "MANUAL" + elif not use_manual_aif and os.path.isfile(manual_aif_path): + manual_aif_status = "OMITTED" + else: + manual_aif_status = "AUTO" + # get fields we want from json + json_file = os.path.join(dir, "../rawdata", subject_id, timepoint, f"dce/{subject_id}_{timepoint}_DCE.json") + try: + with open(json_file, 'r') as f: + data = json.load(f) + manufacturer = data.get("Manufacturer", "json field error") + field_strength = data.get("MagneticFieldStrength", "json field error") + machine = data.get("ManufacturersModelName", "json field error") + institution = data.get("InstitutionName", "json field error") + date = data.get("AcquisitionDateTime", "json field error").split("T")[0] + if date != "json field error": + date = datetime.datetime.strptime(date, "%Y-%m-%d").strftime("%m/%d/%Y") + date = datetime.datetime.strptime(date, "%m/%d/%Y") + sex = data.get("PatientSex", "json field error") + age = data.get("PatientAge", "json field error") + if "ReceiveCoilName" in data: + coil = data.get("ReceiveCoilName", "json field error") + else: + coil = data.get("CoilString", "json field error") + scan_options = data.get("ScanOptions", "json field error") + TE = data.get("EchoTime", "json field error") + # flip_angle = data.get("FlipAngle", "json field error") + # if "RepetitionTimeExcitation" in data: + # # TR = data.get("RepetitionTimeExcitation", "json field error") + # time_resolution = data.get("RepetitionTime", "json field error") + # else: + # # TR = data.get("RepetitionTime", "json field error") + # time_resolution = "not in header" + except Exception as e: + print("Error reading " + json_file) + print(e) + manufacturer = field_strength = machine = institution = date = sex = age = coil = scan_options = TE = flip_angle = TR = time_resolution = "json read error" + + if not missing: + # read wm and gm data from html file + filename = os.path.join(dceprep_dir, subject_id, timepoint, f"reports/{subject_id}_{timepoint}_desc-casereport.html") + if os.path.exists(filename): + try: + with open(filename, "r") as f: + lines = f.readlines() + for i, line in enumerate(lines): + if "T1 wm median:" in line: + T1_wm_median = float(line.split(":")[-1].strip()[:-5]) + if "T1 gm median:" in line: + T1_gm_median = float(line.split(":")[-1].strip()[:-5]) + if "Blood T1: " in line: + T1_blood = float(line.split(":")[-1].strip()[:-6]) + if "Median wm Ktrans" in line: + Ktrans_wm_median = float(line.split()[-1][:-5]) + if "Median gm Ktrans" in line: + Ktrans_gm_median = float(line.split()[-1][:-5]) + if "AIFitness" in line: + AIFitness = line.split(":")[-1].strip()[:-4] + AIFitness = float(AIFitness) + AIFitness = round(AIFitness, 4) + if Ktrans_wm_median > 5: + if subject_id + "_" + timepoint not in Ktrans_wm_outliers: + Ktrans_wm_outliers.append(subject_id + "_" + timepoint) + if Ktrans_gm_median > 5: + if subject_id + "_" + timepoint not in Ktrans_gm_outliers: + Ktrans_gm_outliers.append(subject_id + "_" + timepoint) + except Exception as e: + print("Error reading " + filename) + print(e) + T1_wm_median = T1_gm_median = T1_blood = Ktrans_wm_median = Ktrans_gm_median = AIFitness = -1 + else: + print(f"{filename} does not exist") + T1_wm_median = T1_gm_median = T1_blood = Ktrans_wm_median = Ktrans_gm_median = AIFitness = -1 + + A_log = os.path.join(dceprep_dir, subject_id, timepoint, "dce/A_dceR1info.log") + try: + with open(A_log, 'r') as f: + for line in f: + if "User selected TR (ms):" in line: + TR = next(f).strip() + TR = float(TR) + if "User selected FA (degrees):" in line: + flip_angle = next(f).strip() + flip_angle = float(flip_angle) + if "time points = " in line: + n_reps = line.split(" ")[-1] + n_reps = int(n_reps) + except Exception as e: + print("Error reading " + A_log) + print(e) + TR = -1 + flip_angle = -1 + + # read lines after "AIF mmol:" + aif_mmol = [] + B_log = os.path.join(dceprep_dir, subject_id, timepoint, "dce/B_dcefitted_R1info.log") + B_imported_log = os.path.join(dceprep_dir, subject_id, timepoint, "dce/B_dceimported_R1info.log") + try: + if os.path.isfile(B_log): + with open(B_log, 'r') as f: + fitted_done = False + for line in f: + if "User selected time resolution (sec)" in line: + # take next line as time resolution + time_resolution = next(f).strip() + time_resolution = float(time_resolution) + if "AIF mmol:" in line: + aif_mmol = f.readlines() + # find index of line after last numbers ("MAT results saved to: \n") + try: + lastline = aif_mmol.index("MAT results saved to: \n") + except ValueError: + lastline = aif_mmol.index("Finished B\n") + aif_mmol = aif_mmol[:lastline-1] + # remove \n and \t + aif_mmol = [i[2:-2] for i in aif_mmol] + # split each item into list + aif_mmol = [i.split() for i in aif_mmol] + # unite all lists into one + aif_mmol = [item for sublist in aif_mmol for item in sublist] + # convert to float + aif_mmol = [float(i) for i in aif_mmol] + # take last 33% of aif + aif_mmol = aif_mmol[int(len(aif_mmol) * 0.66):] + # convert to numpy array + aif_mmol = np.array(aif_mmol) + # take mean + aif_mmol = np.mean(aif_mmol) + if "Adjusted R^2 of AIF fit = " in line and not fitted_done: + aif_fitted_r2 = line.split()[-1] + aif_fitted_r2 = float(aif_fitted_r2) + fitted_done = True + elif os.path.isfile(B_imported_log): + with open(B_imported_log, 'r') as f: + for line in f: + if "User selected time resolution (sec)" in line: + # take next line as time resolution + time_resolution = next(f).strip() + time_resolution = float(time_resolution) + if "AIF mmol:" in line: + aif_mmol = f.readlines() + # find index of line after last numbers ("MAT results saved to: \n") + try: + lastline = aif_mmol.index("MAT results saved to: \n") + except ValueError: + lastline = aif_mmol.index("Finished B\n") + aif_mmol = aif_mmol[:lastline-1] + # remove \n and \t + aif_mmol = [i[2:-2] for i in aif_mmol] + # split each item into list + aif_mmol = [i.split() for i in aif_mmol] + # unite all lists into one + aif_mmol = [item for sublist in aif_mmol for item in sublist] + # convert to float + aif_mmol = [float(i) for i in aif_mmol] + # take last 33% of aif + aif_mmol = aif_mmol[int(len(aif_mmol) * 0.66):] + # convert to numpy array + aif_mmol = np.array(aif_mmol) + # take mean + aif_mmol = np.mean(aif_mmol) + except Exception as e: + print("Error reading " + B_log) + print(e) + aif_mmol = -1 + aif_fitted_r2 = -1 + # get max_disp from {prefix}_desc-hmcmaxdisp.txt + max_disp_path = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_desc-hmc_maxdisp.txt") + if os.path.exists(max_disp_path): + try: + with open(max_disp_path, 'r') as f: + for line in f: + if "Max displacement" in line: + max_disp = line.split(":")[-1].strip() + max_disp = max_disp.split("mm")[0] + max_disp = float(max_disp) + break + except Exception as e: + print("Error reading " + max_disp_path) + print(e) + max_disp = -1 + else: + print(f"{max_disp_path} does not exist") + max_disp = -1 + else: + print(f"Skipping stats for {subject_id} {timepoint} due to missing rawdata") + return + # read ktrans map + try: + ktrans_map = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_Ktrans.nii") + ktrans_map = nib.load(ktrans_map) + ktrans_map = ktrans_map.get_fdata() + except: + print("Error reading " + ktrans_map) + wmparc_failed = True + stats_failed = True + return + + # read Vp map + try: + Vp_map = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_Vp.nii") + Vp_map = nib.load(Vp_map) + Vp_map = Vp_map.get_fdata() + except: + print("Error reading " + Vp_map) + stats_failed = True + return + # Numbers are locations of regions in freesurfer wmparc.mgz + regions = { + "HIPPO": (17, 53), + "PHG": (1016, 2016), + "PUTAMEN": (12, 51), + "PALLIDUM": (13, 52), + "THALAMUS": (10, 49), + "CAUDATE": (11, 50), + "AMYGDALA": (18, 54), + "ENTORHINAL_CORTEX": (1006, 2006), + "FUSIFORM_GYRUS_CORTEX": (1007, 2007), + "FUSIFORM_GYRUS_WM": (3007, 4007), + "INSULA_WM": (3035, 4035), + "SUPERIOR_TEMPORAL_CORTEX": (1030, 2030), + "INFERIOR_TEMPORAL_CORTEX": (1009, 2009), + "POSTERIOR_CINGULATE_CORTEX": (1023, 2023) + } + + # atlas file is where this script is located + # atlas = os.path.join(os.path.dirname(os.path.realpath(__file__)), "BN_Atlas_246_1mm.nii.gz") + # atlas = nib.load(atlas) + # atlas = atlas.get_fdata() + # atlas = ktrans_map_hippo + # atlas = atlas[:,110,:] + error = "" + try: + prefix = f"{subject_id}_{timepoint}" + wmparc_path = os.path.join(dceprep_dir, subject_id, timepoint, f"anat/{prefix}_space-DCEref_desc-wmparc.nii.gz") + if os.path.isfile(wmparc_path): + wmparc = nib.load(wmparc_path) + wmparc = wmparc.get_fdata() + else: + print(f"{wmparc_path} does not exist") + error = "anat/wmparc does not exist" + wmparc_failed = True + stats_failed = True + # read stats from tsv + freesurfer_path = os.path.join(dir, 'freesurfer', subject_id, timepoint, "stats") + if os.path.isfile(os.path.join(freesurfer_path, "wmparc.stats")) and os.path.isfile(os.path.join(freesurfer_path, "aseg.stats")) and os.path.isfile(os.path.join(freesurfer_path, "lh.aparc.stats")) and os.path.isfile(os.path.join(freesurfer_path, "rh.aparc.stats")): + wmparc_stats = os.path.join(freesurfer_path, "wmparc.stats") + aseg_stats = os.path.join(freesurfer_path, "aseg.stats") + lh_aparc_stats = os.path.join(freesurfer_path, "lh.aparc.stats") + rh_aparc_stats = os.path.join(freesurfer_path, "rh.aparc.stats") + # fastsurfer = False + # elif os.path.isfile(os.path.join(freesurfer_path, "wmparc.DKTatlas.mapped.stats")): + # # fastsurfer outputs + # wmparc_stats = os.path.join(freesurfer_path, "wmparc.DKTatlas.mapped.stats") + # aseg_stats = os.path.join(freesurfer_path, "aseg.stats") + # lh_aparc_stats = os.path.join(freesurfer_path, "lh.aparc.DKTatlas.mapped.stats") + # rh_aparc_stats = os.path.join(freesurfer_path, "rh.aparc.DKTatlas.mapped.stats") + # fastsurfer = True + else: + print("wmparc stats do not exist for", prefix) + if error == "": + error = "stats files are missing" + wmparc_failed = True + stats_failed = True + # fastsurfer = False + + except Exception as e: + print("Error reading freesurfer stats for", subject_id, timepoint) + print(e) + error = "stats error" + wmparc_failed = True + stats_failed = True + if not stats_failed: + with open(wmparc_stats, 'r') as f: + lines = f.readlines() + for i, line in enumerate(lines): + if line.startswith("# ColHeaders"): + break + lines = lines[i:] + # remove # from beginning of each line + lines_split = [line.replace('#','').strip().split() for line in lines] + + df_wmparc = pd.DataFrame(lines_split) + # Convert only numeric columns (skip first column and header row) + # First, set column names from the first row (shifted) + df_wmparc.columns = df_wmparc.iloc[0].shift(-1) + df_wmparc = df_wmparc.drop(df_wmparc.index[0]) + # Identify numeric columns (skip 'StructName') + numeric_cols = [col for col in df_wmparc.columns if col != 'StructName'] + df_wmparc[numeric_cols] = df_wmparc[numeric_cols].apply(pd.to_numeric) + df_wmparc = df_wmparc.set_index(df_wmparc.iloc[:, 0]) + # drop first column + df_wmparc = df_wmparc.drop(df_wmparc.columns[0], axis=1) + + # fraudsurfer thalamus name varies + right_thalamus = '' + left_thalamus = '' + with open(aseg_stats, 'r') as f: + # find line starting with # ColHeaders, skip up to that line + lines = f.readlines() + for i, line in enumerate(lines): + if line.startswith("# ColHeaders"): + break + lines = lines[i:] + # remove # from beginning of each line + lines_split = [line.replace('#','').strip().split() for line in lines] + + # find name of thalamus column labels + left_thalamus = [line for line in lines if 'Left-Thalamus' in line] + # split thalamus line and take element with 'Thalamus' in it + left_thalamus = left_thalamus[0].split() + left_thalamus = [col for col in left_thalamus if 'Left-Thalamus' in col][0] + # left_thalamus = left_thalamus.split('-')[-1] + + right_thalamus = [line for line in lines if 'Right-Thalamus' in line] + right_thalamus = right_thalamus[0].split() + right_thalamus = [col for col in right_thalamus if 'Right-Thalamus' in col][0] + # right_thalamus = right_thalamus.split('-')[-1] + + df_aseg = pd.DataFrame(lines_split) + # Set column names from the first row (shifted) + df_aseg.columns = df_aseg.iloc[0].shift(-1) + df_aseg = df_aseg.drop(df_aseg.index[0]) + # Identify numeric columns (skip 'StructName') + numeric_cols = [col for col in df_aseg.columns if col != 'StructName'] + df_aseg[numeric_cols] = df_aseg[numeric_cols].apply(pd.to_numeric) + # df_aseg = df_aseg.set_index(df_aseg.iloc[:, 0]) + # drop first column + df_aseg = df_aseg.drop(df_aseg.columns[0], axis=1) + + with open(lh_aparc_stats, 'r') as f: + lines = f.readlines() + for i, line in enumerate(lines): + if line.startswith("# ColHeaders"): + break + lines = lines[i:] + # remove # from beginning of each line + lines_split = [line.replace('#','').strip().split() for line in lines] + + df_lh_aparc = pd.DataFrame(lines_split) + # Shift column names left by 1 to remove 'ColHeaders' and align 'StructName' + df_lh_aparc.columns = df_lh_aparc.iloc[0].shift(-1) + df_lh_aparc = df_lh_aparc.drop(df_lh_aparc.index[0]) + # Identify numeric columns (skip 'StructName') + numeric_cols = [col for col in df_lh_aparc.columns if col != 'StructName'] + df_lh_aparc[numeric_cols] = df_lh_aparc[numeric_cols].apply(pd.to_numeric) + # df_lh_aparc = df_lh_aparc.set_index(df_lh_aparc.iloc[:, 0]) + + with open(rh_aparc_stats, 'r') as f: + lines = f.readlines() + for i, line in enumerate(lines): + if line.startswith("# ColHeaders"): + break + lines = lines[i:] + # remove # from beginning of each line + lines_split = [line.replace('#','').strip().split() for line in lines] + + df_rh_aparc = pd.DataFrame(lines_split) + # Shift column names left by 1 to remove 'ColHeaders' and align 'StructName' + df_rh_aparc.columns = df_rh_aparc.iloc[0].shift(-1) + df_rh_aparc = df_rh_aparc.drop(df_rh_aparc.index[0]) + # Identify numeric columns (skip 'StructName') + numeric_cols = [col for col in df_rh_aparc.columns if col != 'StructName'] + df_rh_aparc[numeric_cols] = df_rh_aparc[numeric_cols].apply(pd.to_numeric) + # df_rh_aparc = df_rh_aparc.set_index(df_rh_aparc.iloc[:, 0]) + # assign regional volumes to variables + hippo_vol = float(df_aseg.loc[df_aseg['StructName'] == 'Left-Hippocampus', 'Volume_mm3'].values[0]) + float(df_aseg.loc[df_aseg['StructName'] == 'Right-Hippocampus', 'Volume_mm3'].values[0]) + phg_vol = float(df_wmparc.loc[df_wmparc['StructName'] == 'wm-lh-parahippocampal', 'Volume_mm3'].values[0]) + float(df_wmparc.loc[df_wmparc['StructName'] == 'wm-rh-parahippocampal', 'Volume_mm3'].values[0]) + putamen_vol = float(df_aseg.loc[df_aseg['StructName'] == 'Left-Putamen', 'Volume_mm3'].values[0]) + float(df_aseg.loc[df_aseg['StructName'] == 'Right-Putamen', 'Volume_mm3'].values[0]) + pallidum_vol = float(df_aseg.loc[df_aseg['StructName'] == 'Left-Pallidum', 'Volume_mm3'].values[0]) + float(df_aseg.loc[df_aseg['StructName'] == 'Right-Pallidum', 'Volume_mm3'].values[0]) + thalamus_vol = float(df_aseg.loc[df_aseg['StructName'] == left_thalamus, 'Volume_mm3'].values[0]) + float(df_aseg.loc[df_aseg['StructName'] == right_thalamus, 'Volume_mm3'].values[0]) + caudate_vol = float(df_aseg.loc[df_aseg['StructName'] == 'Left-Caudate', 'Volume_mm3'].values[0]) + float(df_aseg.loc[df_aseg['StructName'] == 'Right-Caudate', 'Volume_mm3'].values[0]) + amygdala_vol = float(df_aseg.loc[df_aseg['StructName'] == 'Left-Amygdala', 'Volume_mm3'].values[0]) + float(df_aseg.loc[df_aseg['StructName'] == 'Right-Amygdala', 'Volume_mm3'].values[0]) + entorhinal_cortex_vol = float(df_lh_aparc.loc[df_lh_aparc['StructName'] == 'entorhinal', 'GrayVol'].values[0]) + float(df_rh_aparc.loc[df_rh_aparc['StructName'] == 'entorhinal', 'GrayVol'].values[0]) + fusiform_gyrus_cortex_vol = float(df_lh_aparc.loc[df_lh_aparc['StructName'] == 'fusiform', 'GrayVol'].values[0]) + float(df_rh_aparc.loc[df_rh_aparc['StructName'] == 'fusiform', 'GrayVol'].values[0]) + fusiform_gyrus_wm_vol = float(df_wmparc.loc[df_wmparc['StructName'] == 'wm-lh-fusiform', 'Volume_mm3'].values[0]) + float(df_wmparc.loc[df_wmparc['StructName'] == 'wm-rh-fusiform', 'Volume_mm3'].values[0]) + insula_wm_vol = float(df_wmparc.loc[df_wmparc['StructName'] == 'wm-lh-insula', 'Volume_mm3'].values[0]) + float(df_wmparc.loc[df_wmparc['StructName'] == 'wm-rh-insula', 'Volume_mm3'].values[0]) + superior_temporal_cortex_vol = float(df_lh_aparc.loc[df_lh_aparc['StructName'] == 'superiortemporal', 'GrayVol'].values[0]) + float(df_rh_aparc.loc[df_rh_aparc['StructName'] == 'superiortemporal', 'GrayVol'].values[0]) + inferior_temporal_cortex_vol = float(df_lh_aparc.loc[df_lh_aparc['StructName'] == 'inferiortemporal', 'GrayVol'].values[0]) + float(df_rh_aparc.loc[df_rh_aparc['StructName'] == 'inferiortemporal', 'GrayVol'].values[0]) + posterior_cingulate_cortex_vol = float(df_lh_aparc.loc[df_lh_aparc['StructName'] == 'posteriorcingulate', 'GrayVol'].values[0]) + float(df_rh_aparc.loc[df_rh_aparc['StructName'] == 'posteriorcingulate', 'GrayVol'].values[0]) + medial_temporal_cortex_vol = hippo_vol + phg_vol + entorhinal_cortex_vol + # get all cortical thickness values from left and right aparc + cortical_thickness_lh = df_lh_aparc[['StructName', 'NumVert', 'ThickAvg', 'ThickStd']].values.tolist() + cortical_thickness_rh = df_rh_aparc[['StructName', 'NumVert', 'ThickAvg', 'ThickStd']].values.tolist() + # for each region, get whole-brain mean cortical thickness, weighted by number of vertices in region + cortical_thickness = {} + for lh, rh in zip(cortical_thickness_lh, cortical_thickness_rh): + # print(lh, rh) + if lh[0] == rh[0] and lh[0] not in cortical_thickness: + region = lh[0] + num_vert = int(lh[1]) + int(rh[1]) + thickness = (float(lh[1])*float(lh[2]) + float(rh[1])*float(rh[2])) / num_vert + thickness_std = np.sqrt((float(lh[1]) * float(lh[3])**2 + float(rh[1]) * float(rh[3])**2) / num_vert) + cortical_thickness[region] = { + "thickness": thickness, + "thickness_std": thickness_std, + } + bankssts_thickness_avg = cortical_thickness.get('bankssts', {}).get('thickness', -1) + bankssts_thickness_std = cortical_thickness.get('bankssts', {}).get('thickness_std', -1) + caudalanteriorcingulate_thickness_avg = cortical_thickness.get('caudalanteriorcingulate', {}).get('thickness', -1) + caudalanteriorcingulate_thickness_std = cortical_thickness.get('caudalanteriorcingulate', {}).get('thickness_std', -1) + caudalmiddlefrontal_thickness_avg = cortical_thickness.get('caudalmiddlefrontal', {}).get('thickness', -1) + caudalmiddlefrontal_thickness_std = cortical_thickness.get('caudalmiddlefrontal', {}).get('thickness_std', -1) + cuneus_thickness_avg = cortical_thickness.get('cuneus', {}).get('thickness', -1) + cuneus_thickness_std = cortical_thickness.get('cuneus', {}).get('thickness_std', -1) + entorhinal_thickness_avg = cortical_thickness.get('entorhinal', {}).get('thickness', -1) + entorhinal_thickness_std = cortical_thickness.get('entorhinal', {}).get('thickness_std', -1) + fusiform_thickness_avg = cortical_thickness.get('fusiform', {}).get('thickness', -1) + fusiform_thickness_std = cortical_thickness.get('fusiform', {}).get('thickness_std', -1) + inferiorparietal_thickness_avg = cortical_thickness.get('inferiorparietal', {}).get('thickness', -1) + inferiorparietal_thickness_std = cortical_thickness.get('inferiorparietal', {}).get('thickness_std', -1) + inferiortemporal_thickness_avg = cortical_thickness.get('inferiortemporal', {}).get('thickness', -1) + inferiortemporal_thickness_std = cortical_thickness.get('inferiortemporal', {}).get('thickness_std', -1) + isthmuscingulate_thickness_avg = cortical_thickness.get('isthmuscingulate', {}).get('thickness', -1) + isthmuscingulate_thickness_std = cortical_thickness.get('isthmuscingulate', {}).get('thickness_std', -1) + lateraloccipital_thickness_avg = cortical_thickness.get('lateraloccipital', {}).get('thickness', -1) + lateraloccipital_thickness_std = cortical_thickness.get('lateraloccipital', {}).get('thickness_std', -1) + lateralorbitofrontal_thickness_avg = cortical_thickness.get('lateralorbitofrontal', {}).get('thickness', -1) + lateralorbitofrontal_thickness_std = cortical_thickness.get('lateralorbitofrontal', {}).get('thickness_std', -1) + lingual_thickness_avg = cortical_thickness.get('lingual', {}).get('thickness', -1) + lingual_thickness_std = cortical_thickness.get('lingual', {}).get('thickness_std', -1) + medialorbitofrontal_thickness_avg = cortical_thickness.get('medialorbitofrontal', {}).get('thickness', -1) + medialorbitofrontal_thickness_std = cortical_thickness.get('medialorbitofrontal', {}).get('thickness_std', -1) + middletemporal_thickness_avg = cortical_thickness.get('middletemporal', {}).get('thickness', -1) + middletemporal_thickness_std = cortical_thickness.get('middletemporal', {}).get('thickness_std', -1) + parahippocampal_thickness_avg = cortical_thickness.get('parahippocampal', {}).get('thickness', -1) + parahippocampal_thickness_std = cortical_thickness.get('parahippocampal', {}).get('thickness_std', -1) + paracentral_thickness_avg = cortical_thickness.get('paracentral', {}).get('thickness', -1) + paracentral_thickness_std = cortical_thickness.get('paracentral', {}).get('thickness_std', -1) + parsopercularis_thickness_avg = cortical_thickness.get('parsopercularis', {}).get('thickness', -1) + parsopercularis_thickness_std = cortical_thickness.get('parsopercularis', {}).get('thickness_std', -1) + parsorbitalis_thickness_avg = cortical_thickness.get('parsorbitalis', {}).get('thickness', -1) + parsorbitalis_thickness_std = cortical_thickness.get('parsorbitalis', {}).get('thickness_std', -1) + parstriangularis_thickness_avg = cortical_thickness.get('parstriangularis', {}).get('thickness', -1) + parstriangularis_thickness_std = cortical_thickness.get('parstriangularis', {}).get('thickness_std', -1) + pericalcarine_thickness_avg = cortical_thickness.get('pericalcarine', {}).get('thickness', -1) + pericalcarine_thickness_std = cortical_thickness.get('pericalcarine', {}).get('thickness_std', -1) + postcentral_thickness_avg = cortical_thickness.get('postcentral', {}).get('thickness', -1) + postcentral_thickness_std = cortical_thickness.get('postcentral', {}).get('thickness_std', -1) + posteriorcingulate_thickness_avg = cortical_thickness.get('posteriorcingulate', {}).get('thickness', -1) + posteriorcingulate_thickness_std = cortical_thickness.get('posteriorcingulate', {}).get('thickness_std', -1) + precentral_thickness_avg = cortical_thickness.get('precentral', {}).get('thickness', -1) + precentral_thickness_std = cortical_thickness.get('precentral', {}).get('thickness_std', -1) + precuneus_thickness_avg = cortical_thickness.get('precuneus', {}).get('thickness', -1) + precuneus_thickness_std = cortical_thickness.get('precuneus', {}).get('thickness_std', -1) + rostralanteriorcingulate_thickness_avg = cortical_thickness.get('rostralanteriorcingulate', {}).get('thickness', -1) + rostralanteriorcingulate_thickness_std = cortical_thickness.get('rostralanteriorcingulate', {}).get('thickness_std', -1) + rostralmiddlefrontal_thickness_avg = cortical_thickness.get('rostralmiddlefrontal', {}).get('thickness', -1) + rostralmiddlefrontal_thickness_std = cortical_thickness.get('rostralmiddlefrontal', {}).get('thickness_std', -1) + superiorfrontal_thickness_avg = cortical_thickness.get('superiorfrontal', {}).get('thickness', -1) + superiorfrontal_thickness_std = cortical_thickness.get('superiorfrontal', {}).get('thickness_std', -1) + superiorparietal_thickness_avg = cortical_thickness.get('superiorparietal', {}).get('thickness', -1) + superiorparietal_thickness_std = cortical_thickness.get('superiorparietal', {}).get('thickness_std', -1) + superiortemporal_thickness_avg = cortical_thickness.get('superiortemporal', {}).get('thickness', -1) + superiortemporal_thickness_std = cortical_thickness.get('superiortemporal', {}).get('thickness_std', -1) + inferiortemporal_thickness_avg = cortical_thickness.get('inferiortemporal', {}).get('thickness', -1) + inferiortemporal_thickness_std = cortical_thickness.get('inferiortemporal', {}).get('thickness_std', -1) + supramarginal_thickness_avg = cortical_thickness.get('supramarginal', {}).get('thickness', -1) + supramarginal_thickness_std = cortical_thickness.get('supramarginal', {}).get('thickness_std', -1) + frontalpole_thickness_avg = cortical_thickness.get('frontalpole', {}).get('thickness', -1) + frontalpole_thickness_std = cortical_thickness.get('frontalpole', {}).get('thickness_std', -1) + temporalpole_thickness_avg = cortical_thickness.get('temporalpole', {}).get('thickness', -1) + temporalpole_thickness_std = cortical_thickness.get('temporalpole', {}).get('thickness_std', -1) + transversetemporal_thickness_avg = cortical_thickness.get('transversetemporal', {}).get('thickness', -1) + transversetemporal_thickness_std = cortical_thickness.get('transversetemporal', {}).get('thickness_std', -1) + insula_thickness_avg = cortical_thickness.get('insula', {}).get('thickness', -1) + insula_thickness_std = cortical_thickness.get('insula', {}).get('thickness_std', -1) + + if not wmparc_failed: + HIPPO_INDICES = np.where(((wmparc == regions["HIPPO"][0]) | (wmparc == regions["HIPPO"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + PHG_INDICES = np.where(((wmparc == regions["PHG"][0]) | (wmparc == regions["PHG"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + PUTAMEN_INDICES = np.where(((wmparc == regions["PUTAMEN"][0]) | (wmparc == regions["PUTAMEN"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + PALLIDUM_INDICES = np.where(((wmparc == regions["PALLIDUM"][0]) | (wmparc == regions["PALLIDUM"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + THALAMUS_INDICES = np.where(((wmparc == regions["THALAMUS"][0]) | (wmparc == regions["THALAMUS"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + CAUDATE_INDICES = np.where(((wmparc == regions["CAUDATE"][0]) | (wmparc == regions["CAUDATE"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + AMYGDALA_INDICES = np.where(((wmparc == regions["AMYGDALA"][0]) | (wmparc == regions["AMYGDALA"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + ENTORHINAL_CORTEX_INDICES = np.where(((wmparc == regions["ENTORHINAL_CORTEX"][0]) | (wmparc == regions["ENTORHINAL_CORTEX"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + FUSIFORM_GYRUS_CORTEX_INDICES = np.where(((wmparc == regions["FUSIFORM_GYRUS_CORTEX"][0]) | (wmparc == regions["FUSIFORM_GYRUS_CORTEX"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + FUSIFORM_GYRUS_WM_INDICES = np.where(((wmparc == regions["FUSIFORM_GYRUS_WM"][0]) | (wmparc == regions["FUSIFORM_GYRUS_WM"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + INSULA_WM_INDICES = np.where(((wmparc == regions["INSULA_WM"][0]) | (wmparc == regions["INSULA_WM"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + SUPERIOR_TEMPORAL_CORTEX_INDICES = np.where(((wmparc == regions["SUPERIOR_TEMPORAL_CORTEX"][0]) | (wmparc == regions["SUPERIOR_TEMPORAL_CORTEX"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + INFERIOR_TEMPORAL_CORTEX_INDICES = np.where(((wmparc == regions["INFERIOR_TEMPORAL_CORTEX"][0]) | (wmparc == regions["INFERIOR_TEMPORAL_CORTEX"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + POSTERIOR_CINGULATE_CORTEX_INDICES = np.where(((wmparc == regions["POSTERIOR_CINGULATE_CORTEX"][0]) | (wmparc == regions["POSTERIOR_CINGULATE_CORTEX"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + MEDIAL_TEMPORAL_CORTEX_INDICES = np.where(((wmparc == regions["HIPPO"][0]) | (wmparc == regions["HIPPO"][1]) | (wmparc == regions["PHG"][0]) | (wmparc == regions["PHG"][1]) | (wmparc == regions["ENTORHINAL_CORTEX"][0]) | (wmparc == regions["ENTORHINAL_CORTEX"][1])) & (ktrans_map > KTRANS_MIN_THRESHOLD)) + + Ktrans_Hippo = ktrans_map[HIPPO_INDICES]*1000 + Ktrans_PhG = ktrans_map[PHG_INDICES]*1000 + Ktrans_Putamen = ktrans_map[PUTAMEN_INDICES]*1000 + Ktrans_Pallidum = ktrans_map[PALLIDUM_INDICES]*1000 + Ktrans_Thalamus = ktrans_map[THALAMUS_INDICES]*1000 + Ktrans_Caudate = ktrans_map[CAUDATE_INDICES]*1000 + Ktrans_Amygdala = ktrans_map[AMYGDALA_INDICES]*1000 + Ktrans_Entorhinal_cortex = ktrans_map[ENTORHINAL_CORTEX_INDICES]*1000 + Ktrans_Fusiform_gyrus_cortex = ktrans_map[FUSIFORM_GYRUS_CORTEX_INDICES]*1000 + Ktrans_Fusiform_gyrus_WM = ktrans_map[FUSIFORM_GYRUS_WM_INDICES]*1000 + Ktrans_Insula_WM = ktrans_map[INSULA_WM_INDICES]*1000 + Ktrans_Superior_temporal_cortex = ktrans_map[SUPERIOR_TEMPORAL_CORTEX_INDICES]*1000 + Ktrans_Inferior_temporal_cortex = ktrans_map[INFERIOR_TEMPORAL_CORTEX_INDICES]*1000 + Ktrans_Posterior_cingulate_cortex = ktrans_map[POSTERIOR_CINGULATE_CORTEX_INDICES]*1000 + Ktrans_Medial_temporal_cortex = ktrans_map[MEDIAL_TEMPORAL_CORTEX_INDICES]*1000 + Vp_Hippo = Vp_map[HIPPO_INDICES] + Vp_PhG = Vp_map[PHG_INDICES] + Vp_Putamen = Vp_map[PUTAMEN_INDICES] + Vp_Pallidum = Vp_map[PALLIDUM_INDICES] + Vp_Thalamus = Vp_map[THALAMUS_INDICES] + Vp_Caudate = Vp_map[CAUDATE_INDICES] + Vp_Amygdala = Vp_map[AMYGDALA_INDICES] + Vp_Entorhinal_cortex = Vp_map[ENTORHINAL_CORTEX_INDICES] + Vp_Fusiform_gyrus_cortex = Vp_map[FUSIFORM_GYRUS_CORTEX_INDICES] + Vp_Fusiform_gyrus_WM = Vp_map[FUSIFORM_GYRUS_WM_INDICES] + Vp_Insula_WM = Vp_map[INSULA_WM_INDICES] + Vp_Superior_temporal_cortex = Vp_map[SUPERIOR_TEMPORAL_CORTEX_INDICES] + Vp_Inferior_temporal_cortex = Vp_map[INFERIOR_TEMPORAL_CORTEX_INDICES] + Vp_Posterior_cingulate_cortex = Vp_map[POSTERIOR_CINGULATE_CORTEX_INDICES] + Vp_Medial_temporal_cortex = Vp_map[MEDIAL_TEMPORAL_CORTEX_INDICES] + + Ktrans_Hippo_median = np.nanmedian(Ktrans_Hippo) + Ktrans_PhG_median = np.nanmedian(Ktrans_PhG) + Ktrans_Putamen_median = np.nanmedian(Ktrans_Putamen) + Ktrans_Pallidum_median = np.nanmedian(Ktrans_Pallidum) + Ktrans_Thalamus_median = np.nanmedian(Ktrans_Thalamus) + Ktrans_Caudate_median = np.nanmedian(Ktrans_Caudate) + Ktrans_Amygdala_median = np.nanmedian(Ktrans_Amygdala) + Ktrans_Entorhinal_cortex_median = np.nanmedian(Ktrans_Entorhinal_cortex) + Ktrans_Fusiform_gyrus_cortex_median = np.nanmedian(Ktrans_Fusiform_gyrus_cortex) + Ktrans_Fusiform_gyrus_WM_median = np.nanmedian(Ktrans_Fusiform_gyrus_WM) + Ktrans_Insula_WM_median = np.nanmedian(Ktrans_Insula_WM) + Ktrans_Superior_temporal_cortex_median = np.nanmedian(Ktrans_Superior_temporal_cortex) + Ktrans_Inferior_temporal_cortex_median = np.nanmedian(Ktrans_Inferior_temporal_cortex) + Ktrans_Posterior_cingulate_cortex_median = np.nanmedian(Ktrans_Posterior_cingulate_cortex) + Ktrans_Medial_temporal_cortex_median = np.nanmedian(Ktrans_Medial_temporal_cortex) + Vp_Hippo_median = np.nanmedian(Vp_Hippo) + Vp_PhG_median = np.nanmedian(Vp_PhG) + Vp_Putamen_median = np.nanmedian(Vp_Putamen) + Vp_Pallidum_median = np.nanmedian(Vp_Pallidum) + Vp_Thalamus_median = np.nanmedian(Vp_Thalamus) + Vp_Caudate_median = np.nanmedian(Vp_Caudate) + Vp_Amygdala_median = np.nanmedian(Vp_Amygdala) + Vp_Entorhinal_cortex_median = np.nanmedian(Vp_Entorhinal_cortex) + Vp_Fusiform_gyrus_cortex_median = np.nanmedian(Vp_Fusiform_gyrus_cortex) + Vp_Fusiform_gyrus_WM_median = np.nanmedian(Vp_Fusiform_gyrus_WM) + Vp_Insula_WM_median = np.nanmedian(Vp_Insula_WM) + Vp_Superior_temporal_cortex_median = np.nanmedian(Vp_Superior_temporal_cortex) + Vp_Inferior_temporal_cortex_median = np.nanmedian(Vp_Inferior_temporal_cortex) + Vp_Posterior_cingulate_cortex_median = np.nanmedian(Vp_Posterior_cingulate_cortex) + Vp_Medial_temporal_cortex_median = np.nanmedian(Vp_Medial_temporal_cortex) + + if Ktrans_Hippo_median > 5: + if subject_id + "_" + timepoint not in whole_hippo_outliers: + whole_hippo_outliers.append(subject_id + "_" + timepoint) + if Ktrans_PhG_median > 5: + if subject_id + "_" + timepoint not in whole_phg_outliers: + whole_phg_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Putamen_median > 5: + if subject_id + "_" + timepoint not in whole_putamen_outliers: + whole_putamen_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Pallidum_median > 5: + if subject_id + "_" + timepoint not in whole_pallidum_outliers: + whole_pallidum_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Thalamus_median > 5: + if subject_id + "_" + timepoint not in whole_thalamus_outliers: + whole_thalamus_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Caudate_median > 5: + if subject_id + "_" + timepoint not in whole_caudate_outliers: + whole_caudate_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Amygdala_median > 5: + if subject_id + "_" + timepoint not in whole_amygdala_outliers: + whole_amygdala_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Entorhinal_cortex_median > 5: + if subject_id + "_" + timepoint not in whole_entorhinal_cortex_outliers: + whole_entorhinal_cortex_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Fusiform_gyrus_cortex_median > 5: + if subject_id + "_" + timepoint not in whole_fusiform_gyrus_cortex_outliers: + whole_fusiform_gyrus_cortex_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Fusiform_gyrus_WM_median > 5: + if subject_id + "_" + timepoint not in whole_fusiform_gyrus_WM_outliers: + whole_fusiform_gyrus_WM_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Insula_WM_median > 5: + if subject_id + "_" + timepoint not in whole_insula_WM_outliers: + whole_insula_WM_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Superior_temporal_cortex_median > 5: + if subject_id + "_" + timepoint not in whole_superior_temporal_cortex_outliers: + whole_superior_temporal_cortex_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Inferior_temporal_cortex_median > 5: + if subject_id + "_" + timepoint not in whole_inferior_temporal_cortex_outliers: + whole_inferior_temporal_cortex_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Posterior_cingulate_cortex_median > 5: + if subject_id + "_" + timepoint not in whole_posterior_cingulate_cortex_outliers: + whole_posterior_cingulate_cortex_outliers.append(subject_id + "_" + timepoint) + if Ktrans_Medial_temporal_cortex_median > 5: + if subject_id + "_" + timepoint not in whole_medial_temporal_cortex_outliers: + whole_medial_temporal_cortex_outliers.append(subject_id + "_" + timepoint) + + # Calculate SNR from getting mean SI in DCE thalamus then stdev of the difference between the last 2 DCE measures + DCE_img_path = os.path.join(dceprep_dir, subject_id, timepoint, f"dce/{subject_id}_{timepoint}_desc-bfcz_DCE.nii.gz") + if os.path.exists(DCE_img_path): + try: + # DCE_img = nib.load(DCE_img_path) + # DCE_img = DCE_img.get_fdata() + SI_Thalamus_DCE = dce[THALAMUS_INDICES] + + SI_Thalamus_DCE_mean = np.mean(SI_Thalamus_DCE) + SI_Thalamus_DCE_last = SI_Thalamus_DCE[:,-1] + SI_Thalamus_DCE_penultimate = SI_Thalamus_DCE[:,-2] + SI_Thalamus_DCE_last2_difference = SI_Thalamus_DCE_last - SI_Thalamus_DCE_penultimate + SI_Thalamus_DCE_noise_stdev = np.std(SI_Thalamus_DCE_last2_difference) + # calculate SNR + SNR = SI_Thalamus_DCE_mean / SI_Thalamus_DCE_noise_stdev + except Exception as e: + SNR = -1 + print(f"Could not calculate SNR for {subject_id} {timepoint}: {e}") + else: + SNR = -1 + + entry = subject_id + "_" + timepoint + # Common data for both success and fail + case_data = { + "AIFitness": AIFitness, + "aif_mmol": aif_mmol, + "aif_fitted_r2": aif_fitted_r2, + "T1_wm_median": T1_wm_median, + "T1_gm_median": T1_gm_median, + "T1_blood": T1_blood, + "Ktrans_wm_median": Ktrans_wm_median, + "Ktrans_gm_median": Ktrans_gm_median, + "max_disp": max_disp, + "Manufacturer": manufacturer, + "Field_strength": field_strength, + "Machine": machine, + "Institution": institution, + "Date": date, + "Sex": sex, + "Age": age, + "Coil": coil, + "Scan_options": scan_options, + "TE": TE, + "Time_resolution": time_resolution, + "Flip_angle": flip_angle, + "TR": TR, + "n_reps": n_reps, + "Approximate SNR": SNR, + "Ktrans_Hippo_median": Ktrans_Hippo_median, + "Ktrans_PhG_median": Ktrans_PhG_median, + "Ktrans_Putamen_median": Ktrans_Putamen_median, + "Ktrans_Pallidum_median": Ktrans_Pallidum_median, + "Ktrans_Thalamus_median": Ktrans_Thalamus_median, + "Ktrans_Caudate_median": Ktrans_Caudate_median, + "Ktrans_Amygdala_median": Ktrans_Amygdala_median, + "Ktrans_Entorhinal_cortex_median": Ktrans_Entorhinal_cortex_median, + "Ktrans_Fusiform_gyrus_cortex_median": Ktrans_Fusiform_gyrus_cortex_median, + "Ktrans_Fusiform_gyrus_WM_median": Ktrans_Fusiform_gyrus_WM_median, + "Ktrans_Insula_WM_median": Ktrans_Insula_WM_median, + "Ktrans_Superior_temporal_cortex_median": Ktrans_Superior_temporal_cortex_median, + "Ktrans_Inferior_temporal_cortex_median": Ktrans_Inferior_temporal_cortex_median, + "Ktrans_Posterior_cingulate_cortex_median": Ktrans_Posterior_cingulate_cortex_median, + "Ktrans_Medial_temporal_cortex_median": Ktrans_Medial_temporal_cortex_median, + "Vp_Hippo_median": Vp_Hippo_median, + "Vp_PhG_median": Vp_PhG_median, + "Vp_Putamen_median": Vp_Putamen_median, + "Vp_Pallidum_median": Vp_Pallidum_median, + "Vp_Thalamus_median": Vp_Thalamus_median, + "Vp_Caudate_median": Vp_Caudate_median, + "Vp_Amygdala_median": Vp_Amygdala_median, + "Vp_Entorhinal_cortex_median": Vp_Entorhinal_cortex_median, + "Vp_Fusiform_gyrus_cortex_median": Vp_Fusiform_gyrus_cortex_median, + "Vp_Fusiform_gyrus_WM_median": Vp_Fusiform_gyrus_WM_median, + "Vp_Insula_WM_median": Vp_Insula_WM_median, + "Vp_Superior_temporal_cortex_median": Vp_Superior_temporal_cortex_median, + "Vp_Inferior_temporal_cortex_median": Vp_Inferior_temporal_cortex_median, + "Vp_Posterior_cingulate_cortex_median": Vp_Posterior_cingulate_cortex_median, + "Vp_Medial_temporal_cortex_median": Vp_Medial_temporal_cortex_median, + "hippo_vol": hippo_vol, + "phg_vol": phg_vol, + "putamen_vol": putamen_vol, + "pallidum_vol": pallidum_vol, + "thalamus_vol": thalamus_vol, + "caudate_vol": caudate_vol, + "amygdala_vol": amygdala_vol, + "entorhinal_cortex_vol": entorhinal_cortex_vol, + "fusiform_gyrus_cortex_vol": fusiform_gyrus_cortex_vol, + "fusiform_gyrus_wm_vol": fusiform_gyrus_wm_vol, + "insula_wm_vol": insula_wm_vol, + "superior_temporal_cortex_vol": superior_temporal_cortex_vol, + "inferior_temporal_cortex_vol": inferior_temporal_cortex_vol, + "posterior_cingulate_cortex_vol": posterior_cingulate_cortex_vol, + "medial_temporal_cortex_vol": medial_temporal_cortex_vol, + "manual_aif_status": manual_aif_status, + "bankssts_thickness_avg": bankssts_thickness_avg, + "bankssts_thickness_std": bankssts_thickness_std, + "caudalanteriorcingulate_thickness_avg": caudalanteriorcingulate_thickness_avg, + "caudalanteriorcingulate_thickness_std": caudalanteriorcingulate_thickness_std, + "caudalmiddlefrontal_thickness_avg": caudalmiddlefrontal_thickness_avg, + "caudalmiddlefrontal_thickness_std": caudalmiddlefrontal_thickness_std, + "cuneus_thickness_avg": cuneus_thickness_avg, + "cuneus_thickness_std": cuneus_thickness_std, + "entorhinal_thickness_avg": entorhinal_thickness_avg, + "entorhinal_thickness_std": entorhinal_thickness_std, + "fusiform_thickness_avg": fusiform_thickness_avg, + "fusiform_thickness_std": fusiform_thickness_std, + "inferiorparietal_thickness_avg": inferiorparietal_thickness_avg, + "inferiorparietal_thickness_std": inferiorparietal_thickness_std, + "inferiortemporal_thickness_avg": inferiortemporal_thickness_avg, + "inferiortemporal_thickness_std": inferiortemporal_thickness_std, + "isthmuscingulate_thickness_avg": isthmuscingulate_thickness_avg, + "isthmuscingulate_thickness_std": isthmuscingulate_thickness_std, + "lateraloccipital_thickness_avg": lateraloccipital_thickness_avg, + "lateraloccipital_thickness_std": lateraloccipital_thickness_std, + "lateralorbitofrontal_thickness_avg": lateralorbitofrontal_thickness_avg, + "lateralorbitofrontal_thickness_std": lateralorbitofrontal_thickness_std, + "lingual_thickness_avg": lingual_thickness_avg, + "lingual_thickness_std": lingual_thickness_std, + "medialorbitofrontal_thickness_avg": medialorbitofrontal_thickness_avg, + "medialorbitofrontal_thickness_std": medialorbitofrontal_thickness_std, + "middletemporal_thickness_avg": middletemporal_thickness_avg, + "middletemporal_thickness_std": middletemporal_thickness_std, + "parahippocampal_thickness_avg": parahippocampal_thickness_avg, + "parahippocampal_thickness_std": parahippocampal_thickness_std, + "paracentral_thickness_avg": paracentral_thickness_avg, + "paracentral_thickness_std": paracentral_thickness_std, + "parsopercularis_thickness_avg": parsopercularis_thickness_avg, + "parsopercularis_thickness_std": parsopercularis_thickness_std, + "parsorbitalis_thickness_avg": parsorbitalis_thickness_avg, + "parsorbitalis_thickness_std": parsorbitalis_thickness_std, + "parstriangularis_thickness_avg": parstriangularis_thickness_avg, + "parstriangularis_thickness_std": parstriangularis_thickness_std, + "pericalcarine_thickness_avg": pericalcarine_thickness_avg, + "pericalcarine_thickness_std": pericalcarine_thickness_std, + "postcentral_thickness_avg": postcentral_thickness_avg, + "postcentral_thickness_std": postcentral_thickness_std, + "posteriorcingulate_thickness_avg": posteriorcingulate_thickness_avg, + "posteriorcingulate_thickness_std": posteriorcingulate_thickness_std, + "precentral_thickness_avg": precentral_thickness_avg, + "precentral_thickness_std": precentral_thickness_std, + "precuneus_thickness_avg": precuneus_thickness_avg, + "precuneus_thickness_std": precuneus_thickness_std, + "rostralanteriorcingulate_thickness_avg": rostralanteriorcingulate_thickness_avg, + "rostralanteriorcingulate_thickness_std": rostralanteriorcingulate_thickness_std, + "rostralmiddlefrontal_thickness_avg": rostralmiddlefrontal_thickness_avg, + "rostralmiddlefrontal_thickness_std": rostralmiddlefrontal_thickness_std, + "superiorfrontal_thickness_avg": superiorfrontal_thickness_avg, + "superiorfrontal_thickness_std": superiorfrontal_thickness_std, + "superiorparietal_thickness_avg": superiorparietal_thickness_avg, + "superiorparietal_thickness_std": superiorparietal_thickness_std, + "superiortemporal_thickness_avg": superiortemporal_thickness_avg, + "superiortemporal_thickness_std": superiortemporal_thickness_std, + "supramarginal_thickness_avg": supramarginal_thickness_avg, + "supramarginal_thickness_std": supramarginal_thickness_std, + "frontalpole_thickness_avg": frontalpole_thickness_avg, + "frontalpole_thickness_std": frontalpole_thickness_std, + "temporalpole_thickness_avg": temporalpole_thickness_avg, + "temporalpole_thickness_std": temporalpole_thickness_std, + "transversetemporal_thickness_avg": transversetemporal_thickness_avg, + "transversetemporal_thickness_std": transversetemporal_thickness_std, + "insula_thickness_avg": insula_thickness_avg, + "insula_thickness_std": insula_thickness_std + } + + if wmparc_failed is False: + # case_data["fastsurfer"] = fastsurfer + successful_timepoints.append(entry.replace("_", "/")) + population_data[entry] = case_data + else: + case_data["Reason"] = error + population_data_failed[entry] = case_data + +# time +# start = time.time() +# lock = threading.Lock() +subject_timepoints = {subject_id: sorted(os.listdir(os.path.join(dceprep_dir, subject_id))) for subject_id in subjects} + +with ThreadPoolExecutor() as executor: + futures = [executor.submit(get_case_stats, subject_id, timepoint) for subject_id, timepoints in subject_timepoints.items() for timepoint in timepoints] + for future in futures: + try: + future.result() + except Exception as e: + print(f"Error in future: {future}, {e}") +# end = time.time() +# print(f"Time taken: {end - start} seconds") +# get flagged cases +flagged_cases = [] +flagged_links = [] +MOTION_THRESHOLD = 3.8 +AIFITNESS_THRESHOLD = 59 +for entry in successful_timepoints: + flag_str = "" + subject = entry.split('/')[0] + session = entry.split('/')[1] + save_name = f"{output_dir}/{entry}/reports/{subject}_{session}_desc-casereport.html" + flag = False + entry = subject + "_" + session + if entry in population_data.keys() and population_data[entry]['max_disp'] > MOTION_THRESHOLD: + flag = True + flag_str += "motion" + save_name += "#MCFLIRT" + if entry in population_data.keys() and population_data[entry]['AIFitness'] < AIFITNESS_THRESHOLD: + flag = True + if flag_str != "": + flag_str += ", " + flag_str += "AIFitness" + if not save_name[-1].endswith("MCFLIRT"): + save_name += "#AIF" + if flag_str != "": + flagged_cases.append(f'{entry} ({flag_str})') + flagged_links.append(save_name) + if flag: + # move to population_data_exclude + # put AUTO at beginning of flag_str + flag_str = "AUTO: " + flag_str + population_data_exclude[entry] = population_data.pop(entry) + population_data_exclude[entry]['Reason'] = flag_str + # move outliers to exclude + if entry in whole_hippo_outliers: + whole_hippo_outliers_exclude.append(whole_hippo_outliers.pop(whole_hippo_outliers.index(entry))) + if entry in whole_phg_outliers: + whole_phg_outliers_exclude.append(whole_phg_outliers.pop(whole_phg_outliers.index(entry))) + if entry in whole_putamen_outliers: + whole_putamen_outliers_exclude.append(whole_putamen_outliers.pop(whole_putamen_outliers.index(entry))) + if entry in whole_pallidum_outliers: + whole_pallidum_outliers_exclude.append(whole_pallidum_outliers.pop(whole_pallidum_outliers.index(entry))) + if entry in whole_thalamus_outliers: + whole_thalamus_outliers_exclude.append(whole_thalamus_outliers.pop(whole_thalamus_outliers.index(entry))) + if entry in whole_caudate_outliers: + whole_caudate_outliers_exclude.append(whole_caudate_outliers.pop(whole_caudate_outliers.index(entry))) + if entry in whole_amygdala_outliers: + whole_amygdala_outliers_exclude.append(whole_amygdala_outliers.pop(whole_amygdala_outliers.index(entry))) + if entry in whole_entorhinal_cortex_outliers: + whole_entorhinal_cortex_outliers_exclude.append(whole_entorhinal_cortex_outliers.pop(whole_entorhinal_cortex_outliers.index(entry))) + if entry in whole_fusiform_gyrus_cortex_outliers: + whole_fusiform_gyrus_cortex_outliers_exclude.append(whole_fusiform_gyrus_cortex_outliers.pop(whole_fusiform_gyrus_cortex_outliers.index(entry))) + if entry in whole_fusiform_gyrus_WM_outliers: + whole_fusiform_gyrus_WM_outliers_exclude.append(whole_fusiform_gyrus_WM_outliers.pop(whole_fusiform_gyrus_WM_outliers.index(entry))) + if entry in whole_insula_WM_outliers: + whole_insula_WM_outliers_exclude.append(whole_insula_WM_outliers.pop(whole_insula_WM_outliers.index(entry))) + if entry in whole_superior_temporal_cortex_outliers: + whole_superior_temporal_cortex_outliers_exclude.append(whole_superior_temporal_cortex_outliers.pop(whole_superior_temporal_cortex_outliers.index(entry))) + if entry in whole_inferior_temporal_cortex_outliers: + whole_inferior_temporal_cortex_outliers_exclude.append(whole_inferior_temporal_cortex_outliers.pop(whole_inferior_temporal_cortex_outliers.index(entry))) + if entry in whole_posterior_cingulate_cortex_outliers: + whole_posterior_cingulate_cortex_outliers_exclude.append(whole_posterior_cingulate_cortex_outliers.pop(whole_posterior_cingulate_cortex_outliers.index(entry))) + if entry in whole_medial_temporal_cortex_outliers: + whole_medial_temporal_cortex_outliers_exclude.append(whole_medial_temporal_cortex_outliers.pop(whole_medial_temporal_cortex_outliers.index(entry))) + +# remove cases with Machine == Signa HDxt from population_data_exclude and population_data +# population_data_exclude_signa = {} +# for entry in list(population_data_exclude.keys()): +# if population_data_exclude[entry]["Machine"] == "Signa HDxt": +# population_data_exclude_signa[entry] = population_data_exclude.pop(entry) +# population_data_exclude_signa[entry]['Reason'] = "AUTO: Crazy GE Data" + +# for entry in list(population_data.keys()): +# if population_data[entry]["Machine"] == "Signa HDxt": +# population_data_exclude_signa[entry] = population_data.pop(entry) +# population_data_exclude_signa[entry]['Reason'] = "AUTO: Crazy GE Data" + +try: + AIFitness_values = [float(population_data[entry]["AIFitness"]) for entry in population_data] + AIFitness_mean = np.mean(AIFitness_values) + AIFitness_median = np.median(AIFitness_values) + AIFitness_std = np.std(AIFitness_values) + AIFitness_5th_percentile = np.percentile(AIFitness_values, 5) +except Exception as e: + print("AIFitness issue.", e) + AIFitness_mean = -1 + AIFitness_median = -1 + AIFitness_std = -1 + AIFitness_5th_percentile = -1 + +try: + AIFitness_exclude = [float(population_data_exclude[entry]["AIFitness"]) for entry in population_data_exclude] + AIFitness_exclude_mean = np.mean(AIFitness_exclude) + AIFitness_exclude_median = np.median(AIFitness_exclude) + AIFitness_exclude_std = np.std(AIFitness_exclude) + AIFitness_exclude_5th_percentile = np.percentile(AIFitness_exclude, 5) +except Exception as e: + print("AIFitness issue.", e) + AIFitness_exclude_mean = -1 + AIFitness_exclude_median = -1 + AIFitness_exclude_std = -1 + AIFitness_exclude_5th_percentile = -1 + +try: + aif_mmol_mean = np.mean([population_data[entry]["aif_mmol"] for entry in population_data]) + aif_mmol_median = np.median([population_data[entry]["aif_mmol"] for entry in population_data]) + aif_mmol_std = np.std([population_data[entry]["aif_mmol"] for entry in population_data]) + aif_mmol_5th_percentile = np.percentile([population_data[entry]["aif_mmol"] for entry in population_data], 5) + aif_mmol_95th_percentile = np.percentile([population_data[entry]["aif_mmol"] for entry in population_data], 95) +except Exception as e: + print(e) + aif_mmol_mean = -1 + aif_mmol_median = -1 + aif_mmol_std = -1 + aif_mmol_5th_percentile = -1 + aif_mmol_95th_percentile = -1 + +try: + aif_mmol_exclude = [population_data_exclude[entry]["aif_mmol"] for entry in population_data_exclude] + aif_mmol_mean_exclude = np.mean(aif_mmol_exclude) + aif_mmol_median_exclude = np.median(aif_mmol_exclude) + aif_mmol_std_exclude = np.std(aif_mmol_exclude) + aif_mmol_5th_percentile_exclude = np.percentile(aif_mmol_exclude, 5) + aif_mmol_95th_percentile_exclude = np.percentile(aif_mmol_exclude, 95) +except Exception as e: + print(e) + aif_mmol_mean_exclude = -1 + aif_mmol_median_exclude = -1 + aif_mmol_std_exclude = -1 + aif_mmol_5th_percentile_exclude = -1 + aif_mmol_95th_percentile_exclude = -1 + +try: + T1_wm_mean = np.mean([population_data[entry]["T1_wm_median"] for entry in population_data]) + T1_wm_median = np.median([population_data[entry]["T1_wm_median"] for entry in population_data]) + T1_wm_std = np.std([population_data[entry]["T1_wm_median"] for entry in population_data]) + T1_wm_5th_percentile = np.percentile([population_data[entry]["T1_wm_median"] for entry in population_data], 5) + T1_wm_95th_percentile = np.percentile([population_data[entry]["T1_wm_median"] for entry in population_data], 95) +except Exception as e: + print(e) + T1_wm_mean = -1 + T1_wm_median = -1 + T1_wm_std = -1 + T1_wm_5th_percentile = -1 + T1_wm_95th_percentile = -1 + +try: + T1_wm_mean_exclude = np.mean([population_data_exclude[entry]["T1_wm_median"] for entry in population_data_exclude]) + T1_wm_median_exclude = np.median([population_data_exclude[entry]["T1_wm_median"] for entry in population_data_exclude]) + T1_wm_std_exclude = np.std([population_data_exclude[entry]["T1_wm_median"] for entry in population_data_exclude]) + T1_wm_5th_percentile_exclude = np.percentile([population_data_exclude[entry]["T1_wm_median"] for entry in population_data_exclude], 5) + T1_wm_95th_percentile_exclude = np.percentile([population_data_exclude[entry]["T1_wm_median"] for entry in population_data_exclude], 95) +except Exception as e: + print(e) + T1_wm_mean_exclude = -1 + T1_wm_median_exclude = -1 + T1_wm_std_exclude = -1 + T1_wm_5th_percentile_exclude = -1 + T1_wm_95th_percentile_exclude = -1 + +try: + T1_gm_mean = np.mean([population_data[entry]["T1_gm_median"] for entry in population_data]) + T1_gm_median = np.median([population_data[entry]["T1_gm_median"] for entry in population_data]) + T1_gm_std = np.std([population_data[entry]["T1_gm_median"] for entry in population_data]) + T1_gm_5th_percentile = np.percentile([population_data[entry]["T1_gm_median"] for entry in population_data], 5) + T1_gm_95th_percentile = np.percentile([population_data[entry]["T1_gm_median"] for entry in population_data], 95) +except Exception as e: + print(e) + T1_gm_mean = -1 + T1_gm_median = -1 + T1_gm_std = -1 + T1_gm_5th_percentile = -1 + T1_gm_95th_percentile = -1 + +try: + T1_gm_mean_exclude = np.mean([population_data_exclude[entry]["T1_gm_median"] for entry in population_data_exclude]) + T1_gm_median_exclude = np.median([population_data_exclude[entry]["T1_gm_median"] for entry in population_data_exclude]) + T1_gm_std_exclude = np.std([population_data_exclude[entry]["T1_gm_median"] for entry in population_data_exclude]) + T1_gm_5th_percentile_exclude = np.percentile([population_data_exclude[entry]["T1_gm_median"] for entry in population_data_exclude], 5) + T1_gm_95th_percentile_exclude = np.percentile([population_data_exclude[entry]["T1_gm_median"] for entry in population_data_exclude], 95) +except Exception as e: + print(e) + T1_gm_mean_exclude = -1 + T1_gm_median_exclude = -1 + T1_gm_std_exclude = -1 + T1_gm_5th_percentile_exclude = -1 + T1_gm_95th_percentile_exclude = -1 + +try: + T1_blood_mean = np.mean([population_data[entry]["T1_blood"] for entry in population_data]) + T1_blood_median = np.median([population_data[entry]["T1_blood"] for entry in population_data]) + T1_blood_std = np.std([population_data[entry]["T1_blood"] for entry in population_data]) + T1_blood_5th_percentile = np.percentile([population_data[entry]["T1_blood"] for entry in population_data], 5) + T1_blood_95th_percentile = np.percentile([population_data[entry]["T1_blood"] for entry in population_data], 95) +except Exception as e: + print(e) + T1_blood_mean = -1 + T1_blood_median = -1 + T1_blood_std = -1 + T1_blood_5th_percentile = -1 + T1_blood_95th_percentile = -1 + +try: + T1_blood_mean_exclude = np.mean([population_data_exclude[entry]["T1_blood"] for entry in population_data_exclude]) + T1_blood_median_exclude = np.median([population_data_exclude[entry]["T1_blood"] for entry in population_data_exclude]) + T1_blood_std_exclude = np.std([population_data_exclude[entry]["T1_blood"] for entry in population_data_exclude]) + T1_blood_5th_percentile_exclude = np.percentile([population_data_exclude[entry]["T1_blood"] for entry in population_data_exclude], 5) + T1_blood_95th_percentile_exclude = np.percentile([population_data_exclude[entry]["T1_blood"] for entry in population_data_exclude], 95) +except Exception as e: + print(e) + T1_blood_mean_exclude = -1 + T1_blood_median_exclude = -1 + T1_blood_std_exclude = -1 + T1_blood_5th_percentile_exclude = -1 + T1_blood_95th_percentile_exclude = -1 + +try: + Ktrans_wm_mean = np.nanmean([population_data[entry]["Ktrans_wm_median"] for entry in population_data]) + Ktrans_wm_median = np.nanmedian([population_data[entry]["Ktrans_wm_median"] for entry in population_data]) + Ktrans_wm_std = np.nanstd([population_data[entry]["Ktrans_wm_median"] for entry in population_data]) + Ktrans_gm_mean = np.nanmean([population_data[entry]["Ktrans_gm_median"] for entry in population_data]) + Ktrans_gm_median = np.nanmedian([population_data[entry]["Ktrans_gm_median"] for entry in population_data]) + Ktrans_gm_std = np.nanstd([population_data[entry]["Ktrans_gm_median"] for entry in population_data]) +except Exception as e: + print(e) + Ktrans_wm_mean = -1 + Ktrans_wm_median = -1 + Ktrans_wm_std = -1 + Ktrans_gm_mean = -1 + Ktrans_gm_median = -1 + Ktrans_gm_std = -1 + +try: + wm_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_wm_median"] for entry in population_data_exclude]) + wm_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_wm_median"] for entry in population_data_exclude]) + wm_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_wm_median"] for entry in population_data_exclude]) + gm_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_gm_median"] for entry in population_data_exclude]) + gm_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_gm_median"] for entry in population_data_exclude]) + gm_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_gm_median"] for entry in population_data_exclude]) +except Exception as e: + print(e) + wm_mean_exclude = -1 + wm_median_exclude = -1 + wm_std_exclude = -1 + gm_mean_exclude = -1 + gm_median_exclude = -1 + gm_std_exclude = -1 + +whole_hippo_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Hippo_median"] for entry in population_data]) +whole_hippo_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Hippo_median"] for entry in population_data]) +whole_hippo_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Hippo_median"] for entry in population_data]) + +whole_hippo_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Hippo_median"] for entry in population_data_exclude]) +whole_hippo_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Hippo_median"] for entry in population_data_exclude]) +whole_hippo_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Hippo_median"] for entry in population_data_exclude]) + +whole_phg_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_PhG_median"] for entry in population_data]) +whole_phg_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_PhG_median"] for entry in population_data]) +whole_phg_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_PhG_median"] for entry in population_data]) + +whole_phg_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_PhG_median"] for entry in population_data_exclude]) +whole_phg_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_PhG_median"] for entry in population_data_exclude]) +whole_phg_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_PhG_median"] for entry in population_data_exclude]) + +whole_putamen_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Putamen_median"] for entry in population_data]) +whole_putamen_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Putamen_median"] for entry in population_data]) +whole_putamen_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Putamen_median"] for entry in population_data]) + +whole_putamen_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Putamen_median"] for entry in population_data_exclude]) +whole_putamen_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Putamen_median"] for entry in population_data_exclude]) +whole_putamen_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Putamen_median"] for entry in population_data_exclude]) + +whole_pallidum_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Pallidum_median"] for entry in population_data]) +whole_pallidum_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Pallidum_median"] for entry in population_data]) +whole_pallidum_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Pallidum_median"] for entry in population_data]) + +whole_pallidum_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Pallidum_median"] for entry in population_data_exclude]) +whole_pallidum_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Pallidum_median"] for entry in population_data_exclude]) +whole_pallidum_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Pallidum_median"] for entry in population_data_exclude]) + +whole_thalamus_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Thalamus_median"] for entry in population_data]) +whole_thalamus_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Thalamus_median"] for entry in population_data]) +whole_thalamus_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Thalamus_median"] for entry in population_data]) + +whole_thalamus_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Thalamus_median"] for entry in population_data_exclude]) +whole_thalamus_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Thalamus_median"] for entry in population_data_exclude]) +whole_thalamus_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Thalamus_median"] for entry in population_data_exclude]) + +whole_caudate_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Caudate_median"] for entry in population_data]) +whole_caudate_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Caudate_median"] for entry in population_data]) +whole_caudate_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Caudate_median"] for entry in population_data]) + +whole_caudate_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Caudate_median"] for entry in population_data_exclude]) +whole_caudate_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Caudate_median"] for entry in population_data_exclude]) +whole_caudate_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Caudate_median"] for entry in population_data_exclude]) + +whole_amygdala_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Amygdala_median"] for entry in population_data]) +whole_amygdala_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Amygdala_median"] for entry in population_data]) +whole_amygdala_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Amygdala_median"] for entry in population_data]) + +whole_amygdala_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Amygdala_median"] for entry in population_data_exclude]) +whole_amygdala_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Amygdala_median"] for entry in population_data_exclude]) +whole_amygdala_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Amygdala_median"] for entry in population_data_exclude]) + +whole_entorhinal_cortex_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Entorhinal_cortex_median"] for entry in population_data]) +whole_entorhinal_cortex_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Entorhinal_cortex_median"] for entry in population_data]) +whole_entorhinal_cortex_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Entorhinal_cortex_median"] for entry in population_data]) + +whole_entorhinal_cortex_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Entorhinal_cortex_median"] for entry in population_data_exclude]) +whole_entorhinal_cortex_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Entorhinal_cortex_median"] for entry in population_data_exclude]) +whole_entorhinal_cortex_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Entorhinal_cortex_median"] for entry in population_data_exclude]) + +whole_fusiform_gyrus_cortex_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Fusiform_gyrus_cortex_median"] for entry in population_data]) +whole_fusiform_gyrus_cortex_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Fusiform_gyrus_cortex_median"] for entry in population_data]) +whole_fusiform_gyrus_cortex_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Fusiform_gyrus_cortex_median"] for entry in population_data]) + +whole_fusiform_gyrus_cortex_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Fusiform_gyrus_cortex_median"] for entry in population_data_exclude]) +whole_fusiform_gyrus_cortex_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Fusiform_gyrus_cortex_median"] for entry in population_data_exclude]) +whole_fusiform_gyrus_cortex_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Fusiform_gyrus_cortex_median"] for entry in population_data_exclude]) + +whole_fusiform_gyrus_WM_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Fusiform_gyrus_WM_median"] for entry in population_data]) +whole_fusiform_gyrus_WM_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Fusiform_gyrus_WM_median"] for entry in population_data]) +whole_fusiform_gyrus_WM_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Fusiform_gyrus_WM_median"] for entry in population_data]) + +whole_fusiform_gyrus_WM_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Fusiform_gyrus_WM_median"] for entry in population_data_exclude]) +whole_fusiform_gyrus_WM_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Fusiform_gyrus_WM_median"] for entry in population_data_exclude]) +whole_fusiform_gyrus_WM_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Fusiform_gyrus_WM_median"] for entry in population_data_exclude]) + +whole_insula_WM_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Insula_WM_median"] for entry in population_data]) +whole_insula_WM_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Insula_WM_median"] for entry in population_data]) +whole_insula_WM_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Insula_WM_median"] for entry in population_data]) + +whole_insula_WM_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Insula_WM_median"] for entry in population_data_exclude]) +whole_insula_WM_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Insula_WM_median"] for entry in population_data_exclude]) +whole_insula_WM_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Insula_WM_median"] for entry in population_data_exclude]) + +whole_superior_temporal_cortex_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Superior_temporal_cortex_median"] for entry in population_data]) +whole_superior_temporal_cortex_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Superior_temporal_cortex_median"] for entry in population_data]) +whole_superior_temporal_cortex_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Superior_temporal_cortex_median"] for entry in population_data]) + +whole_superior_temporal_cortex_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Superior_temporal_cortex_median"] for entry in population_data_exclude]) +whole_superior_temporal_cortex_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Superior_temporal_cortex_median"] for entry in population_data_exclude]) +whole_superior_temporal_cortex_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Superior_temporal_cortex_median"] for entry in population_data_exclude]) + +whole_inferior_temporal_cortex_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Inferior_temporal_cortex_median"] for entry in population_data]) +whole_inferior_temporal_cortex_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Inferior_temporal_cortex_median"] for entry in population_data]) +whole_inferior_temporal_cortex_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Inferior_temporal_cortex_median"] for entry in population_data]) + +whole_inferior_temporal_cortex_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Inferior_temporal_cortex_median"] for entry in population_data_exclude]) +whole_inferior_temporal_cortex_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Inferior_temporal_cortex_median"] for entry in population_data_exclude]) +whole_inferior_temporal_cortex_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Inferior_temporal_cortex_median"] for entry in population_data_exclude]) + +whole_posterior_cingulate_cortex_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Posterior_cingulate_cortex_median"] for entry in population_data]) +whole_posterior_cingulate_cortex_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Posterior_cingulate_cortex_median"] for entry in population_data]) +whole_posterior_cingulate_cortex_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Posterior_cingulate_cortex_median"] for entry in population_data]) + +whole_posterior_cingulate_cortex_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Posterior_cingulate_cortex_median"] for entry in population_data_exclude]) +whole_posterior_cingulate_cortex_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Posterior_cingulate_cortex_median"] for entry in population_data_exclude]) +whole_posterior_cingulate_cortex_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Posterior_cingulate_cortex_median"] for entry in population_data_exclude]) + +whole_medial_temporal_cortex_Ktrans_mean = np.nanmean([population_data[entry]["Ktrans_Medial_temporal_cortex_median"] for entry in population_data]) +whole_medial_temporal_cortex_Ktrans_median = np.nanmedian([population_data[entry]["Ktrans_Medial_temporal_cortex_median"] for entry in population_data]) +whole_medial_temporal_cortex_Ktrans_std = np.nanstd([population_data[entry]["Ktrans_Medial_temporal_cortex_median"] for entry in population_data]) + +whole_medial_temporal_cortex_Ktrans_mean_exclude = np.nanmean([population_data_exclude[entry]["Ktrans_Medial_temporal_cortex_median"] for entry in population_data_exclude]) +whole_medial_temporal_cortex_Ktrans_median_exclude = np.nanmedian([population_data_exclude[entry]["Ktrans_Medial_temporal_cortex_median"] for entry in population_data_exclude]) +whole_medial_temporal_cortex_Ktrans_std_exclude = np.nanstd([population_data_exclude[entry]["Ktrans_Medial_temporal_cortex_median"] for entry in population_data_exclude]) + +# if no outliers, set to "None" +if len(Ktrans_wm_outliers) == 0: + Ktrans_wm_outliers = "None" +if len(Ktrans_gm_outliers) == 0: + Ktrans_gm_outliers = "None" + +# make figures directory if it doesn't exist +if not os.path.exists(os.path.join("figures/")): + os.makedirs(os.path.join("figures/")) + +# make T1 blood histogram +T1_blood_histogram = [] +for entry in population_data.keys(): + T1_blood_histogram.append(population_data[entry]["T1_blood"]) + +date_filename = datetime.datetime.now().strftime("%Y-%m-%d") + +# plot histogram +plt.hist(T1_blood_histogram, bins=30) +plt.title("T1 Blood") +plt.xlabel("T1 Blood") +T1_blood_histogram_path = os.path.join("figures/", "T1_blood_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(T1_blood_histogram_path, bbox_inches='tight') +plt.close() + +T1_blood_histogram_exclude = [] +for entry in population_data_exclude.keys(): + T1_blood_histogram_exclude.append(population_data_exclude[entry]["T1_blood"]) + +# plot histogram +plt.hist(T1_blood_histogram_exclude, bins=30) +plt.title("T1 Blood (Exclude)") +plt.xlabel("T1 Blood") +T1_blood_histogram_exclude_path = os.path.join("figures/", "T1_blood_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(T1_blood_histogram_exclude_path, bbox_inches='tight') +plt.close() + +# make AIFitness histogram +plt.hist(AIFitness_values, bins=30) +plt.title("AIFitness Median") +plt.xlabel("AIFitness") +aifitness_histogram_path = os.path.join("figures/", "aifitness_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(aifitness_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(AIFitness_exclude, bins=30) +plt.title("AIFitness Median (Exclude)") +plt.xlabel("AIFitness") +aifitness_histogram_exclude_path = os.path.join("figures/", "aifitness_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(aifitness_histogram_exclude_path, bbox_inches='tight') +plt.close() + +# make aif_mmol histogram +aif_mmol_histogram = [] +for entry in population_data.keys(): + aif_mmol_histogram.append(population_data[entry]["aif_mmol"]) + +# plot histogram +plt.hist(aif_mmol_histogram, bins=30) +plt.title("AIF mmol (mean of last 1/3)") +plt.xlabel("AIF mmol") +aif_mmol_histogram_path = os.path.join("figures/", "aif_mmol_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(aif_mmol_histogram_path, bbox_inches='tight') +plt.close() + +aif_mmol_histogram_exclude = [] +for entry in population_data_exclude.keys(): + aif_mmol_histogram_exclude.append(population_data_exclude[entry]["aif_mmol"]) + +# plot histogram +plt.hist(aif_mmol_histogram_exclude, bins=30) +plt.title("AIF mmol (mean of last 1/3) (Exclude)") +plt.xlabel("AIF mmol") +aif_mmol_histogram_exclude_path = os.path.join("figures/", "aif_mmol_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(aif_mmol_histogram_exclude_path, bbox_inches='tight') +plt.close() + +# make ktrans histograms from each timepoint mean +wm_histogram = [] +for entry in population_data.keys(): + wm_histogram.append(population_data[entry]["Ktrans_wm_median"]) + +# plot histogram +plt.hist(wm_histogram, bins=50, range=(0, 5)) +plt.title("White Matter Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +ktrans_wm_histogram_path = os.path.join("figures/", "wm_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(ktrans_wm_histogram_path, bbox_inches='tight') +plt.close() + +ktrans_wm_histogram_exclude = [] +for entry in population_data_exclude.keys(): + ktrans_wm_histogram_exclude.append(population_data_exclude[entry]["Ktrans_wm_median"]) + +# plot histogram +plt.hist(ktrans_wm_histogram_exclude, bins=50, range=(0, 5)) +plt.title("White Matter Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +ktrans_wm_histogram_exclude_path = os.path.join("figures/", "wm_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(ktrans_wm_histogram_exclude_path, bbox_inches='tight') +plt.close() + +# now get gm mean histogram +gm_histogram = [] +for entry in population_data.keys(): + gm_histogram.append(population_data[entry]["Ktrans_gm_median"]) + +# plot histogram +plt.hist(gm_histogram, bins=50, range=(0, 5)) +plt.title("Gray Matter Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +ktrans_gm_histogram_path = os.path.join("figures/", "gm_histogram" + output_dir + "_" + date_filename + ".png") +# save range of histogram for later use +gm_histogram_range = plt.xlim() +plt.savefig(ktrans_gm_histogram_path, bbox_inches='tight') +plt.close() + +ktrans_gm_histogram_exclude = [] +for entry in population_data_exclude.keys(): + ktrans_gm_histogram_exclude.append(population_data_exclude[entry]["Ktrans_gm_median"]) + +# plot histogram +plt.hist(ktrans_gm_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Gray Matter Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +ktrans_gm_histogram_exclude_path = os.path.join("figures/", "gm_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.xlim(gm_histogram_range) +plt.savefig(ktrans_gm_histogram_exclude_path, bbox_inches='tight') +plt.close() + +try: + pop_avg_AIF = np.asarray(popAIF_curves) + pop_avg_AIF = np.mean(pop_avg_AIF, axis=0) + # save average curve to export into MATLAB + np.savetxt("average_aif_curve.csv", pop_avg_AIF, delimiter=",") + for aif in popAIF_curves: + plt.plot(aif, linewidth=0.5, color='grey', alpha=0.5) + plt.plot(pop_avg_AIF, linewidth=1, color='black') + # plot stdev per timepoint + plt.fill_between(np.arange(0, len(pop_avg_AIF)), pop_avg_AIF - np.std(popAIF_curves, axis=0), pop_avg_AIF + np.std(popAIF_curves, axis=0), alpha=0.3) + plt.xlabel('Time (s)') + plt.ylabel('Normalized Intensity') + plt.title('AIF Curves (Modified for MATLAB Import)') + aif_pop_avg_path = os.path.join("figures/", "aif_pop_avg_AIF" + output_dir + "_" + date_filename + ".png") + plt.savefig(aif_pop_avg_path, bbox_inches='tight', dpi=300) # Increase dpi for higher resolution + plt.close() +except Exception as e: + print("Error plotting population AIF curve.", e) + aif_pop_avg_path = "Error plotting population average AIF curve." + +try: + # plot true AIFs + aif_curves = np.asarray(aif_curves) + aif_curves_avg = np.nanmean(aif_curves, axis=0) + for aif in aif_curves: + plt.plot(aif, linewidth=0.5, color='grey', alpha=0.5) + plt.plot(aif_curves_avg, linewidth=1, color='black') + # plot 95% confidence interval + plt.fill_between(np.arange(0, len(aif_curves_avg)), aif_curves_avg - np.std(aif_curves, axis=0), aif_curves_avg + np.std(aif_curves, axis=0), alpha=0.3) + plt.xlabel('Time (s)') + plt.ylabel('Normalized Intensity') + plt.title('AIF Curves') + aif_curves_path = os.path.join("figures/", "aif_pop_AIF" + output_dir + "_" + date_filename + ".png") + plt.savefig(aif_curves_path, bbox_inches='tight', dpi=300) # Increase dpi for higher resolution + plt.close() +except Exception as e: + print("Error plotting population AIF curve.", e) + aif_curves_path = "Error plotting population AIF curves." + +Ktrans_histograms = { + "whole_hippo": [], + "whole_phg": [], + "whole_putamen": [], + "whole_pallidum": [], + "whole_thalamus": [], + "whole_caudate": [], + "whole_amygdala": [], + "whole_entorhinal_cortex": [], + "whole_fusiform_gyrus_cortex": [], + "whole_fusiform_gyrus_WM": [], + "whole_insula_WM": [], + "whole_superior_temporal_cortex": [], + "whole_inferior_temporal_cortex": [], + "whole_posterior_cingulate_cortex": [], + "whole_medial_temporal_cortex": [] +} + +Vp_histograms = { + "whole_hippo": [], + "whole_phg": [], + "whole_putamen": [], + "whole_pallidum": [], + "whole_thalamus": [], + "whole_caudate": [], + "whole_amygdala": [], + "whole_entorhinal_cortex": [], + "whole_fusiform_gyrus_cortex": [], + "whole_fusiform_gyrus_WM": [], + "whole_insula_WM": [], + "whole_superior_temporal_cortex": [], + "whole_inferior_temporal_cortex": [], + "whole_posterior_cingulate_cortex": [], + "whole_medial_temporal_cortex": [] +} +for entry in population_data.keys(): + Ktrans_histograms["whole_hippo"].append(population_data[entry]["Ktrans_Hippo_median"]) + Ktrans_histograms["whole_phg"].append(population_data[entry]["Ktrans_PhG_median"]) + Ktrans_histograms["whole_putamen"].append(population_data[entry]["Ktrans_Putamen_median"]) + Ktrans_histograms["whole_pallidum"].append(population_data[entry]["Ktrans_Pallidum_median"]) + Ktrans_histograms["whole_thalamus"].append(population_data[entry]["Ktrans_Thalamus_median"]) + Ktrans_histograms["whole_caudate"].append(population_data[entry]["Ktrans_Caudate_median"]) + Ktrans_histograms["whole_amygdala"].append(population_data[entry]["Ktrans_Amygdala_median"]) + Ktrans_histograms["whole_entorhinal_cortex"].append(population_data[entry]["Ktrans_Entorhinal_cortex_median"]) + Ktrans_histograms["whole_fusiform_gyrus_cortex"].append(population_data[entry]["Ktrans_Fusiform_gyrus_cortex_median"]) + Ktrans_histograms["whole_fusiform_gyrus_WM"].append(population_data[entry]["Ktrans_Fusiform_gyrus_WM_median"]) + Ktrans_histograms["whole_insula_WM"].append(population_data[entry]["Ktrans_Insula_WM_median"]) + Ktrans_histograms["whole_superior_temporal_cortex"].append(population_data[entry]["Ktrans_Superior_temporal_cortex_median"]) + Ktrans_histograms["whole_inferior_temporal_cortex"].append(population_data[entry]["Ktrans_Inferior_temporal_cortex_median"]) + Ktrans_histograms["whole_posterior_cingulate_cortex"].append(population_data[entry]["Ktrans_Posterior_cingulate_cortex_median"]) + Ktrans_histograms["whole_medial_temporal_cortex"].append(population_data[entry]["Ktrans_Medial_temporal_cortex_median"]) + Vp_histograms["whole_hippo"].append(population_data[entry]["Vp_Hippo_median"]) + Vp_histograms["whole_phg"].append(population_data[entry]["Vp_PhG_median"]) + Vp_histograms["whole_putamen"].append(population_data[entry]["Vp_Putamen_median"]) + Vp_histograms["whole_pallidum"].append(population_data[entry]["Vp_Pallidum_median"]) + Vp_histograms["whole_thalamus"].append(population_data[entry]["Vp_Thalamus_median"]) + Vp_histograms["whole_caudate"].append(population_data[entry]["Vp_Caudate_median"]) + Vp_histograms["whole_amygdala"].append(population_data[entry]["Vp_Amygdala_median"]) + Vp_histograms["whole_entorhinal_cortex"].append(population_data[entry]["Vp_Entorhinal_cortex_median"]) + Vp_histograms["whole_fusiform_gyrus_cortex"].append(population_data[entry]["Vp_Fusiform_gyrus_cortex_median"]) + Vp_histograms["whole_fusiform_gyrus_WM"].append(population_data[entry]["Vp_Fusiform_gyrus_WM_median"]) + Vp_histograms["whole_insula_WM"].append(population_data[entry]["Vp_Insula_WM_median"]) + Vp_histograms["whole_superior_temporal_cortex"].append(population_data[entry]["Vp_Superior_temporal_cortex_median"]) + Vp_histograms["whole_inferior_temporal_cortex"].append(population_data[entry]["Vp_Inferior_temporal_cortex_median"]) + Vp_histograms["whole_posterior_cingulate_cortex"].append(population_data[entry]["Vp_Posterior_cingulate_cortex_median"]) + Vp_histograms["whole_medial_temporal_cortex"].append(population_data[entry]["Vp_Medial_temporal_cortex_median"]) + +whole_hippo_histogram_exclude = [] +whole_phg_histogram_exclude = [] +whole_putamen_histogram_exclude = [] +whole_pallidum_histogram_exclude = [] +whole_thalamus_histogram_exclude = [] +whole_caudate_histogram_exclude = [] +whole_amygdala_histogram_exclude = [] +whole_entorhinal_cortex_histogram_exclude = [] +whole_fusiform_gyrus_cortex_histogram_exclude = [] +whole_fusiform_gyrus_WM_histogram_exclude = [] +whole_insula_WM_histogram_exclude = [] +whole_superior_temporal_cortex_histogram_exclude = [] +whole_inferior_temporal_cortex_histogram_exclude = [] +whole_posterior_cingulate_cortex_histogram_exclude = [] +whole_medial_temporal_cortex_histogram_exclude = [] +for entry in population_data_exclude.keys(): + whole_hippo_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Hippo_median"]) + whole_phg_histogram_exclude.append(population_data_exclude[entry]["Ktrans_PhG_median"]) + whole_putamen_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Putamen_median"]) + whole_pallidum_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Pallidum_median"]) + whole_thalamus_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Thalamus_median"]) + whole_caudate_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Caudate_median"]) + whole_amygdala_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Amygdala_median"]) + whole_entorhinal_cortex_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Entorhinal_cortex_median"]) + whole_fusiform_gyrus_cortex_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Fusiform_gyrus_cortex_median"]) + whole_fusiform_gyrus_WM_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Fusiform_gyrus_WM_median"]) + whole_insula_WM_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Insula_WM_median"]) + whole_superior_temporal_cortex_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Superior_temporal_cortex_median"]) + whole_inferior_temporal_cortex_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Inferior_temporal_cortex_median"]) + whole_posterior_cingulate_cortex_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Posterior_cingulate_cortex_median"]) + whole_medial_temporal_cortex_histogram_exclude.append(population_data_exclude[entry]["Ktrans_Medial_temporal_cortex_median"]) + +plt.hist(Ktrans_histograms["whole_hippo"], bins=50, range=(0, 5)) +plt.title("Whole Hippocampus Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_hippo_histogram_path = os.path.join("figures/", "Ktrans_whole_hippo_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_hippo_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_phg"], bins=50, range=(0, 5)) +plt.title("Whole Parahippocampal Gyrus Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_phg_histogram_path = os.path.join("figures/", "Ktrans_whole_phg_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_phg_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_putamen"], bins=50, range=(0, 5)) +plt.title("Whole Putamen Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_putamen_histogram_path = os.path.join("figures/", "Ktrans_whole_putamen_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_putamen_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_pallidum"], bins=50, range=(0, 5)) +plt.title("Whole Pallidum Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_pallidum_histogram_path = os.path.join("figures/", "Ktrans_whole_pallidum_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_pallidum_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_thalamus"], bins=50, range=(0, 5)) +plt.title("Whole Thalamus Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_thalamus_histogram_path = os.path.join("figures/", "Ktrans_whole_thalamus_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_thalamus_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_caudate"], bins=50, range=(0, 5)) +plt.title("Whole Caudate Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_caudate_histogram_path = os.path.join("figures/", "Ktrans_whole_caudate_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_caudate_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_amygdala"], bins=50, range=(0, 5)) +plt.title("Whole Amygdala Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_amygdala_histogram_path = os.path.join("figures/", "Ktrans_whole_amygdala_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_amygdala_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_entorhinal_cortex"], bins=50, range=(0, 5)) +plt.title("Whole Entorhinal Cortex Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_entorhinal_cortex_histogram_path = os.path.join("figures/", "Ktrans_whole_entorhinal_cortex_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_entorhinal_cortex_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_fusiform_gyrus_cortex"], bins=50, range=(0, 5)) +plt.title("Whole Fusiform Gyrus Cortex Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_fusiform_gyrus_cortex_histogram_path = os.path.join("figures/", "Ktrans_whole_fusiform_gyrus_cortex_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_fusiform_gyrus_cortex_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_fusiform_gyrus_WM"], bins=50, range=(0, 5)) +plt.title("Whole Fusiform Gyrus WM Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_fusiform_gyrus_WM_histogram_path = os.path.join("figures/", "Ktrans_whole_fusiform_gyrus_WM_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_fusiform_gyrus_WM_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_insula_WM"], bins=50, range=(0, 5)) +plt.title("Whole Insula WM Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_insula_WM_histogram_path = os.path.join("figures/", "Ktrans_whole_insula_WM_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_insula_WM_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_superior_temporal_cortex"], bins=50, range=(0, 5)) +plt.title("Whole Superior Temporal Cortex Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_superior_temporal_cortex_histogram_path = os.path.join("figures/", "Ktrans_whole_superior_temporal_cortex_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_superior_temporal_cortex_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_inferior_temporal_cortex"], bins=50, range=(0, 5)) +plt.title("Whole Inferior Temporal Cortex Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_inferior_temporal_cortex_histogram_path = os.path.join("figures/", "Ktrans_whole_inferior_temporal_cortex_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_inferior_temporal_cortex_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_posterior_cingulate_cortex"], bins=50, range=(0, 5)) +plt.title("Whole Posterior Cingulate Cortex Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_posterior_cingulate_cortex_histogram_path = os.path.join("figures/", "Ktrans_whole_posterior_cingulate_cortex_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_posterior_cingulate_cortex_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(Ktrans_histograms["whole_medial_temporal_cortex"], bins=50, range=(0, 5)) +plt.title("Whole Medial Temporal Cortex Median Ktrans") +plt.xlabel("Ktrans (10^-3/min)") +Ktrans_whole_medial_temporal_cortex_histogram_path = os.path.join("figures/", "Ktrans_whole_medial_temporal_cortex_histogram" + output_dir + "_" + date_filename + ".png") +plt.savefig(Ktrans_whole_medial_temporal_cortex_histogram_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_hippo_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Hippocampus Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_hippo_histogram_exclude_path = os.path.join("figures/", "whole_hippo_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_hippo_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_phg_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Parahippocampal Gyrus Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_phg_histogram_exclude_path = os.path.join("figures/", "whole_phg_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_phg_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_putamen_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Putamen Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_putamen_histogram_exclude_path = os.path.join("figures/", "whole_putamen_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_putamen_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_pallidum_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Pallidum Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_pallidum_histogram_exclude_path = os.path.join("figures/", "whole_pallidum_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_pallidum_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_thalamus_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Thalamus Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_thalamus_histogram_exclude_path = os.path.join("figures/", "whole_thalamus_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_thalamus_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_caudate_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Caudate Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_caudate_histogram_exclude_path = os.path.join("figures/", "whole_caudate_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_caudate_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_amygdala_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Amygdala Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_amygdala_histogram_exclude_path = os.path.join("figures/", "whole_amygdala_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_amygdala_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_entorhinal_cortex_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Entorhinal Cortex Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_entorhinal_cortex_histogram_exclude_path = os.path.join("figures/", "whole_entorhinal_cortex_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_entorhinal_cortex_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_fusiform_gyrus_cortex_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Fusiform Gyrus Cortex Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_fusiform_gyrus_cortex_histogram_exclude_path = os.path.join("figures/", "whole_fusiform_gyrus_cortex_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_fusiform_gyrus_cortex_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_fusiform_gyrus_WM_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Fusiform Gyrus WM Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_fusiform_gyrus_WM_histogram_exclude_path = os.path.join("figures/", "whole_fusiform_gyrus_WM_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_fusiform_gyrus_WM_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_insula_WM_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Insula WM Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_insula_WM_histogram_exclude_path = os.path.join("figures/", "whole_insula_WM_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_insula_WM_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_superior_temporal_cortex_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Superior Temporal Cortex Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_superior_temporal_cortex_histogram_exclude_path = os.path.join("figures/", "whole_superior_temporal_cortex_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_superior_temporal_cortex_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_inferior_temporal_cortex_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Inferior Temporal Cortex Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_inferior_temporal_cortex_histogram_exclude_path = os.path.join("figures/", "whole_inferior_temporal_cortex_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_inferior_temporal_cortex_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_posterior_cingulate_cortex_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Posterior Cingulate Cortex Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_posterior_cingulate_cortex_histogram_exclude_path = os.path.join("figures/", "whole_posterior_cingulate_cortex_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_posterior_cingulate_cortex_histogram_exclude_path, bbox_inches='tight') +plt.close() + +plt.hist(whole_medial_temporal_cortex_histogram_exclude, bins=50, range=(0, 5)) +plt.title("Whole Medial Temporal Cortex Median Ktrans (Exclude)") +plt.xlabel("Ktrans (10^-3/min)") +whole_medial_temporal_cortex_histogram_exclude_path = os.path.join("figures/", "whole_medial_temporal_cortex_histogram_exclude" + output_dir + "_" + date_filename + ".png") +plt.savefig(whole_medial_temporal_cortex_histogram_exclude_path, bbox_inches='tight') +plt.close() + +# round to 4 decimal places +Ktrans_wm_mean = round(Ktrans_wm_mean, 4) +Ktrans_wm_median = round(Ktrans_wm_median, 4) +Ktrans_wm_std = round(Ktrans_wm_std, 4) +Ktrans_gm_mean = round(Ktrans_gm_mean, 4) +Ktrans_gm_median = round(Ktrans_gm_median, 4) +Ktrans_gm_std = round(Ktrans_gm_std, 4) + +wm_mean_exclude = round(wm_mean_exclude, 4) +wm_median_exclude = round(wm_median_exclude, 4) +wm_std_exclude = round(wm_std_exclude, 4) +gm_mean_exclude = round(gm_mean_exclude, 4) +gm_median_exclude = round(gm_median_exclude, 4) +gm_std_exclude = round(gm_std_exclude, 4) + +# use jinja2 to generate html +env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(os.path.realpath(__file__)))) +template = env.get_template('population_template.html') + +# get date +date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") +# get commit hash for this repo +try: + commit_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=os.path.dirname(os.path.realpath(__file__))).decode('ascii').strip() +except Exception as e: + print("Git didn't work correctly. Trying a different way of getting latest dev branch commit hash...") + command = ['cat', '.git/refs/heads/dev'] + commit_hash = subprocess.check_output(command, cwd=os.path.dirname(os.path.realpath(__file__))).decode('ascii').strip() + +# get commit hash for ROCKETSHIP repo +try: + ROCKETSHIP_commit_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROCKETSHIP_dir).decode('ascii').strip() +except Exception as e: + print("Could not get ROCKETSHIP commit hash:", e) + ROCKETSHIP_commit_hash = "N/A" + +# make dict of manufacturer, field strength, machine, and institution +manufacturers = {} +field_strengths = {} +machines = {} +institutions = {} +try: + for entry in population_data.keys(): + manufacturer = population_data[entry]["Manufacturer"] + field_strength = population_data[entry]["Field_strength"] + machine = population_data[entry]["Machine"] + institution = population_data[entry]["Institution"] + if manufacturer in manufacturers.keys(): + manufacturers[manufacturer] += 1 + else: + manufacturers[manufacturer] = 1 + if field_strength in field_strengths.keys(): + field_strengths[field_strength] += 1 + else: + field_strengths[field_strength] = 1 + if machine in machines.keys(): + machines[machine] += 1 + else: + machines[machine] = 1 + if institution in institutions.keys(): + institutions[institution] += 1 + else: + institutions[institution] = 1 +except Exception as e: + print("Error in getting manufacturer, field strength, machine, and institution data.") + print(e) + +manufacturers_exclude = {} +field_strengths_exclude = {} +machines_exclude = {} +institutions_exclude = {} +try: + for entry in population_data_exclude.keys(): + manufacturer = population_data_exclude[entry]["Manufacturer"] + field_strength = population_data_exclude[entry]["Field_strength"] + machine = population_data_exclude[entry]["Machine"] + institution = population_data_exclude[entry]["Institution"] + if manufacturer in manufacturers_exclude.keys(): + manufacturers_exclude[manufacturer] += 1 + else: + manufacturers_exclude[manufacturer] = 1 + if field_strength in field_strengths_exclude.keys(): + field_strengths_exclude[field_strength] += 1 + else: + field_strengths_exclude[field_strength] = 1 + if machine in machines_exclude.keys(): + machines_exclude[machine] += 1 + else: + machines_exclude[machine] = 1 + if institution in institutions_exclude.keys(): + institutions_exclude[institution] += 1 + else: + institutions_exclude[institution] = 1 +except Exception as e: + print("Error in getting manufacturer, field strength, machine, and institution data.") + print(e) + +successful_timepoints = list(set(successful_timepoints)) +successful_timepoints.sort() +# num_timepoints = len(successful_timepoints) +# remove output_dir from successful_timepoints +cases = [timepoint for timepoint in successful_timepoints] +successful_links = [] +for timepoint in successful_timepoints: + subject = timepoint.split('/')[0] + session = timepoint.split('/')[1] + successful_links.append(f"dceprep{output_dir}/{subject}/{session}/reports/{subject}_{session}_desc-casereport.html") +# get failed cases from total_timepoints not in successful_timepoints +failed_cases = [timepoint for timepoint in total_timepoints if timepoint not in successful_timepoints] +# get links for each failed case's directory +failed_links = [os.path.join(dceprep_dir, case) for case in failed_cases] + +# read ROCKETSHIP preference file +with open(ROCKETSHIP_dir + "/script_preferences.txt", "r") as f: + lines = f.readlines() + for line in lines: + if "tr =" in line: + pref_tr = line.split("= ")[1].strip() + elif "fa =" in line: + pref_fa = line.split("= ")[1].strip() + elif "hematocrit =" in line: + pref_hematocrit = line.split("= ")[1].strip() + elif "snr_filter =" in line: + pref_SNR = line.split("= ")[1].strip() + elif "relaxivity =" in line and "pref_relaxivity" not in locals(): + pref_relaxivity = line.split("= ")[1].strip() + elif "blood_t1 =" in line: + pref_t1blood = line.split("= ")[1].strip() + elif "start_t =" in line: + pref_start_t = line.split("= ")[1].strip() + elif "end_t =" in line: + pref_end_t = line.split("= ")[1].strip() + elif "time_resolution =" in line: + pref_timeres = line.split("= ")[1].strip() + elif "tofts = 1" in line: + DCE_model = "Tofts" + elif "ex_tofts = 1" in line: + DCE_model = "Tofts Extended" + elif "patlak = 1" in line: + DCE_model = "Patlak" + elif "tissue_uptake = 1" in line: + DCE_model = "Tissue Uptake" + elif "two_cxm = 1" in line: + DCE_model = "Two Compartment Exchange" + +data = { + 'Subjects' : subjects, + # 'base_url': dceprep_dir, + 'Links': successful_links, + 'Failed_links': failed_links, + 'Cases': cases, + 'Failed_cases': failed_cases, + 'Flagged_cases': flagged_cases, + 'Combo': zip(successful_links, cases), + 'Failed_combo': zip(failed_links, failed_cases), + 'Motion_threshold': MOTION_THRESHOLD, + 'AIFitness_threshold': AIFITNESS_THRESHOLD, + 'Flagged_combo': zip(flagged_links, flagged_cases), + 'Subject_count': len(subjects), + 'Successes': str(len(population_data)) + '/' + str(len(total_timepoints)) + ' (' + str(round((len(population_data) / len(total_timepoints)) * 100, 2)) + '%)', + 'Excludes': str(len(population_data_exclude)) + '/' + str(len(total_timepoints)) + ' (' + str(round((len(population_data_exclude) / len(total_timepoints)) * 100, 2)) + '%)', + 'Date': date, + 'Commit': commit_hash, + 'ROCKETSHIP_commit': ROCKETSHIP_commit_hash, + 'Manufacturers': manufacturers, + 'Field_strengths': field_strengths, + 'Machines': machines, + 'Institutions': institutions, + 'pref_tr': pref_tr, + 'pref_fa': pref_fa, + 'pref_hematocrit': pref_hematocrit, + 'pref_SNR': pref_SNR, + 'pref_relaxivity': pref_relaxivity, + 'pref_t1blood': pref_t1blood, + 'pref_start_t': pref_start_t, + 'pref_end_t': pref_end_t, + 'pref_timeres': pref_timeres, + 'pref_model': DCE_model, + 'T1_wm_mean': round(T1_wm_mean, 4), + 'T1_wm_median': round(T1_wm_median, 4), + 'T1_wm_std': round(T1_wm_std, 4), + 'T1_wm_5th_percentile': round(T1_wm_5th_percentile, 4), + 'T1_wm_95th_percentile': round(T1_wm_95th_percentile, 4), + 'T1_gm_mean': round(T1_gm_mean, 4), + 'T1_gm_median': round(T1_gm_median, 4), + 'T1_gm_std': round(T1_gm_std, 4), + 'T1_gm_5th_percentile': round(T1_gm_5th_percentile, 4), + 'T1_gm_95th_percentile': round(T1_gm_95th_percentile, 4), + 'T1_blood_mean': round(T1_blood_mean, 4), + 'T1_blood_median': round(T1_blood_median, 4), + 'T1_blood_std': round(T1_blood_std, 4), + 'T1_blood_5th_percentile': round(T1_blood_5th_percentile, 4), + 'T1_blood_95th_percentile': round(T1_blood_95th_percentile, 4), + 'AIFitness_mean': round(AIFitness_mean, 4), + 'AIFitness_median': round(AIFitness_median, 4), + 'AIFitness_std': round(AIFitness_std, 4), + 'AIFitness_5th_percentile': round(AIFitness_5th_percentile, 4), + 'aif_mmol_mean': round(aif_mmol_mean, 4), + 'aif_mmol_median': round(aif_mmol_median, 4), + 'aif_mmol_std': round(aif_mmol_std, 4), + 'aif_mmol_5th_percentile': round(aif_mmol_5th_percentile, 4), + 'aif_mmol_95th_percentile': round(aif_mmol_95th_percentile, 4), + 'AIFitness_histogram' : "../../" + aifitness_histogram_path, + 'aif_mmol_histogram': "../../" + aif_mmol_histogram_path, + 'aif_pop_avg_AIF': "../../" + aif_pop_avg_path, + 'aif_curves': "../../" + aif_curves_path, + 'Ktrans_wm_mean': Ktrans_wm_mean, + 'Ktrans_wm_median': Ktrans_wm_median, + 'Ktrans_wm_std': Ktrans_wm_std, + 'Ktrans_gm_mean': Ktrans_gm_mean, + 'Ktrans_gm_median': Ktrans_gm_median, + 'Ktrans_gm_std': Ktrans_gm_std, + 'whole_hippo_Ktrans_mean': round(whole_hippo_Ktrans_mean, 4), + 'whole_hippo_Ktrans_median': round(whole_hippo_Ktrans_median, 4), + 'whole_hippo_Ktrans_std': round(whole_hippo_Ktrans_std, 4), + 'whole_phg_Ktrans_mean': round(whole_phg_Ktrans_mean, 4), + 'whole_phg_Ktrans_median': round(whole_phg_Ktrans_median, 4), + 'whole_phg_Ktrans_std': round(whole_phg_Ktrans_std, 4), + 'whole_putamen_Ktrans_mean': round(whole_putamen_Ktrans_mean, 4), + 'whole_putamen_Ktrans_median': round(whole_putamen_Ktrans_median, 4), + 'whole_putamen_Ktrans_std': round(whole_putamen_Ktrans_std, 4), + 'whole_pallidum_Ktrans_mean': round(whole_pallidum_Ktrans_mean, 4), + 'whole_pallidum_Ktrans_median': round(whole_pallidum_Ktrans_median, 4), + 'whole_pallidum_Ktrans_std': round(whole_pallidum_Ktrans_std, 4), + 'whole_thalamus_Ktrans_mean': round(whole_thalamus_Ktrans_mean, 4), + 'whole_thalamus_Ktrans_median': round(whole_thalamus_Ktrans_median, 4), + 'whole_thalamus_Ktrans_std': round(whole_thalamus_Ktrans_std, 4), + 'whole_caudate_Ktrans_mean': round(whole_caudate_Ktrans_mean, 4), + 'whole_caudate_Ktrans_median': round(whole_caudate_Ktrans_median, 4), + 'whole_caudate_Ktrans_std': round(whole_caudate_Ktrans_std, 4), + 'whole_amygdala_Ktrans_mean': round(whole_amygdala_Ktrans_mean, 4), + 'whole_amygdala_Ktrans_median': round(whole_amygdala_Ktrans_median, 4), + 'whole_amygdala_Ktrans_std': round(whole_amygdala_Ktrans_std, 4), + 'whole_entorhinal_cortex_Ktrans_mean': round(whole_entorhinal_cortex_Ktrans_mean, 4), + 'whole_entorhinal_cortex_Ktrans_median': round(whole_entorhinal_cortex_Ktrans_median, 4), + 'whole_entorhinal_cortex_Ktrans_std': round(whole_entorhinal_cortex_Ktrans_std, 4), + 'whole_fusiform_gyrus_cortex_Ktrans_mean': round(whole_fusiform_gyrus_cortex_Ktrans_mean, 4), + 'whole_fusiform_gyrus_cortex_Ktrans_median': round(whole_fusiform_gyrus_cortex_Ktrans_median, 4), + 'whole_fusiform_gyrus_cortex_Ktrans_std': round(whole_fusiform_gyrus_cortex_Ktrans_std, 4), + 'whole_fusiform_gyrus_WM_Ktrans_mean': round(whole_fusiform_gyrus_WM_Ktrans_mean, 4), + 'whole_fusiform_gyrus_WM_Ktrans_median': round(whole_fusiform_gyrus_WM_Ktrans_median, 4), + 'whole_fusiform_gyrus_WM_Ktrans_std': round(whole_fusiform_gyrus_WM_Ktrans_std, 4), + 'whole_insula_WM_Ktrans_mean': round(whole_insula_WM_Ktrans_mean, 4), + 'whole_insula_WM_Ktrans_median': round(whole_insula_WM_Ktrans_median, 4), + 'whole_insula_WM_Ktrans_std': round(whole_insula_WM_Ktrans_std, 4), + 'whole_superior_temporal_cortex_Ktrans_mean': round(whole_superior_temporal_cortex_Ktrans_mean, 4), + 'whole_superior_temporal_cortex_Ktrans_median': round(whole_superior_temporal_cortex_Ktrans_median, 4), + 'whole_superior_temporal_cortex_Ktrans_std': round(whole_superior_temporal_cortex_Ktrans_std, 4), + 'whole_inferior_temporal_cortex_Ktrans_mean': round(whole_inferior_temporal_cortex_Ktrans_mean, 4), + 'whole_inferior_temporal_cortex_Ktrans_median': round(whole_inferior_temporal_cortex_Ktrans_median, 4), + 'whole_inferior_temporal_cortex_Ktrans_std': round(whole_inferior_temporal_cortex_Ktrans_std, 4), + 'whole_posterior_cingulate_cortex_Ktrans_mean': round(whole_posterior_cingulate_cortex_Ktrans_mean, 4), + 'whole_posterior_cingulate_cortex_Ktrans_median': round(whole_posterior_cingulate_cortex_Ktrans_median, 4), + 'whole_posterior_cingulate_cortex_Ktrans_std': round(whole_posterior_cingulate_cortex_Ktrans_std, 4), + 'whole_medial_temporal_cortex_Ktrans_mean': round(whole_medial_temporal_cortex_Ktrans_mean, 4), + 'whole_medial_temporal_cortex_Ktrans_median': round(whole_medial_temporal_cortex_Ktrans_median, 4), + 'whole_medial_temporal_cortex_Ktrans_std': round(whole_medial_temporal_cortex_Ktrans_std, 4), + 'ktrans_wm_outliers': Ktrans_wm_outliers, + 'ktrans_gm_outliers': Ktrans_gm_outliers, + 'T1_blood_histogram': "../../" + T1_blood_histogram_path, + 'wm_histogram': "../../" + ktrans_wm_histogram_path, + 'gm_histogram': "../../" + ktrans_gm_histogram_path, + 'whole_hippo_outliers': whole_hippo_outliers, + 'whole_phg_outliers': whole_phg_outliers, + 'whole_putamen_outliers': whole_putamen_outliers, + 'whole_pallidum_outliers': whole_pallidum_outliers, + 'whole_thalamus_outliers': whole_thalamus_outliers, + 'whole_caudate_outliers': whole_caudate_outliers, + 'whole_amygdala_outliers': whole_amygdala_outliers, + 'whole_entorhinal_cortex_outliers': whole_entorhinal_cortex_outliers, + 'whole_fusiform_gyrus_cortex_outliers': whole_fusiform_gyrus_cortex_outliers, + 'whole_fusiform_gyrus_WM_outliers': whole_fusiform_gyrus_WM_outliers, + 'whole_insula_WM_outliers': whole_insula_WM_outliers, + 'whole_superior_temporal_cortex_outliers': whole_superior_temporal_cortex_outliers, + 'whole_inferior_temporal_cortex_outliers': whole_inferior_temporal_cortex_outliers, + 'whole_posterior_cingulate_cortex_outliers': whole_posterior_cingulate_cortex_outliers, + 'whole_medial_temporal_cortex_outliers': whole_medial_temporal_cortex_outliers, + 'Ktrans_whole_hippo_histogram': "../../" + Ktrans_whole_hippo_histogram_path, + 'Ktrans_whole_phg_histogram': "../../" + Ktrans_whole_phg_histogram_path, + 'Ktrans_whole_putamen_histogram': "../../" + Ktrans_whole_putamen_histogram_path, + 'Ktrans_whole_pallidum_histogram': "../../" + Ktrans_whole_pallidum_histogram_path, + 'Ktrans_whole_thalamus_histogram': "../../" + Ktrans_whole_thalamus_histogram_path, + 'Ktrans_whole_caudate_histogram': "../../" + Ktrans_whole_caudate_histogram_path, + 'Ktrans_whole_amygdala_histogram': "../../" + Ktrans_whole_amygdala_histogram_path, + 'Ktrans_whole_entorhinal_cortex_histogram': "../../" + Ktrans_whole_entorhinal_cortex_histogram_path, + 'Ktrans_whole_fusiform_gyrus_cortex_histogram': "../../" + Ktrans_whole_fusiform_gyrus_cortex_histogram_path, + 'Ktrans_whole_fusiform_gyrus_WM_histogram': "../../" + Ktrans_whole_fusiform_gyrus_WM_histogram_path, + 'Ktrans_whole_insula_WM_histogram': "../../" + Ktrans_whole_insula_WM_histogram_path, + 'Ktrans_whole_superior_temporal_cortex_histogram': "../../" + Ktrans_whole_superior_temporal_cortex_histogram_path, + 'Ktrans_whole_inferior_temporal_cortex_histogram': "../../" + Ktrans_whole_inferior_temporal_cortex_histogram_path, + 'Ktrans_whole_posterior_cingulate_cortex_histogram': "../../" + Ktrans_whole_posterior_cingulate_cortex_histogram_path, + 'Ktrans_whole_medial_temporal_cortex_histogram': "../../" + Ktrans_whole_medial_temporal_cortex_histogram_path +} + +output = template.render(data) + +data = { + 'Subjects' : subjects, + # 'base_url': dceprep_dir, + 'Links': successful_links, + 'Failed_links': failed_links, + 'Cases': cases, + 'Failed_cases': failed_cases, + 'Flagged_cases': flagged_cases, + 'Combo': zip(successful_links, cases), + 'Failed_combo': zip(failed_links, failed_cases), + 'Motion_threshold': MOTION_THRESHOLD, + 'AIFitness_threshold': AIFITNESS_THRESHOLD, + 'Flagged_combo': zip(flagged_links, flagged_cases), + 'Subject_count': len(subjects), + 'Successes': str(len(population_data)) + '/' + str(len(total_timepoints)) + ' (' + str(round((len(population_data) / len(total_timepoints)) * 100, 2)) + '%)', + 'Excludes': str(len(population_data_exclude)) + '/' + str(len(total_timepoints)) + ' (' + str(round((len(population_data_exclude) / len(total_timepoints)) * 100, 2)) + '%)', + 'Date': date, + 'Commit': commit_hash, + 'ROCKETSHIP_commit': ROCKETSHIP_commit_hash, + 'Manufacturers': manufacturers_exclude, + 'Field_strengths': field_strengths_exclude, + 'Machines': machines, + 'Institutions': institutions_exclude, + 'pref_tr': pref_tr, + 'pref_fa': pref_fa, + 'pref_hematocrit': pref_hematocrit, + 'pref_SNR': pref_SNR, + 'pref_relaxivity': pref_relaxivity, + 'pref_t1blood': pref_t1blood, + 'pref_start_t': pref_start_t, + 'pref_end_t': pref_end_t, + 'pref_timeres': pref_timeres, + 'pref_model': DCE_model, + 'T1_wm_mean': round(T1_wm_mean_exclude, 4), + 'T1_wm_median': round(T1_wm_median_exclude, 4), + 'T1_wm_std': round(T1_wm_std_exclude, 4), + 'T1_wm_5th_percentile': round(T1_wm_5th_percentile_exclude, 4), + 'T1_wm_95th_percentile': round(T1_wm_95th_percentile_exclude, 4), + 'T1_gm_mean': round(T1_gm_mean_exclude, 4), + 'T1_gm_median': round(T1_gm_median_exclude, 4), + 'T1_gm_std': round(T1_gm_std_exclude, 4), + 'T1_gm_5th_percentile': round(T1_gm_5th_percentile_exclude, 4), + 'T1_gm_95th_percentile': round(T1_gm_95th_percentile_exclude, 4), + 'T1_blood_mean': round(T1_blood_mean_exclude, 4), + 'T1_blood_median': round(T1_blood_median_exclude, 4), + 'T1_blood_std': round(T1_blood_std_exclude, 4), + 'T1_blood_5th_percentile': round(T1_blood_5th_percentile_exclude, 4), + 'T1_blood_95th_percentile': round(T1_blood_95th_percentile_exclude, 4), + 'AIFitness_mean': round(AIFitness_exclude_mean, 4), + 'AIFitness_median': round(AIFitness_exclude_median, 4), + 'AIFitness_std': round(AIFitness_exclude_std, 4), + 'AIFitness_5th_percentile': round(AIFitness_exclude_5th_percentile, 4), + 'aif_mmol_mean': round(aif_mmol_mean_exclude, 4), + 'aif_mmol_median': round(aif_mmol_median_exclude, 4), + 'aif_mmol_std': round(aif_mmol_std_exclude, 4), + 'aif_mmol_5th_percentile': round(aif_mmol_5th_percentile_exclude, 4), + 'aif_mmol_95th_percentile': round(aif_mmol_95th_percentile_exclude, 4), + 'AIFitness_histogram' : "../../" + aifitness_histogram_exclude_path, + 'aif_mmol_histogram': "../../" + aif_mmol_histogram_exclude_path, + 'aif_pop_avg_AIF': "../../" + aif_pop_avg_path, + 'aif_curves': "../../" + aif_curves_path, + 'Ktrans_wm_mean': wm_mean_exclude, + 'Ktrans_wm_median': wm_median_exclude, + 'Ktrans_wm_std': wm_std_exclude, + 'Ktrans_gm_mean': gm_mean_exclude, + 'Ktrans_gm_median': gm_median_exclude, + 'Ktrans_gm_std': gm_std_exclude, + 'whole_hippo_Ktrans_mean': round(whole_hippo_Ktrans_mean_exclude, 4), + 'whole_hippo_Ktrans_median': round(whole_hippo_Ktrans_median_exclude, 4), + 'whole_hippo_Ktrans_std': round(whole_hippo_Ktrans_std_exclude, 4), + 'whole_phg_Ktrans_mean': round(whole_phg_Ktrans_mean_exclude, 4), + 'whole_phg_Ktrans_median': round(whole_phg_Ktrans_median_exclude, 4), + 'whole_phg_Ktrans_std': round(whole_phg_Ktrans_std_exclude, 4), + 'whole_putamen_Ktrans_mean': round(whole_putamen_Ktrans_mean_exclude, 4), + 'whole_putamen_Ktrans_median': round(whole_putamen_Ktrans_median_exclude, 4), + 'whole_putamen_Ktrans_std': round(whole_putamen_Ktrans_std_exclude, 4), + 'whole_pallidum_Ktrans_mean': round(whole_pallidum_Ktrans_mean_exclude, 4), + 'whole_pallidum_Ktrans_median': round(whole_pallidum_Ktrans_median_exclude, 4), + 'whole_pallidum_Ktrans_std': round(whole_pallidum_Ktrans_std_exclude, 4), + 'whole_thalamus_Ktrans_mean': round(whole_thalamus_Ktrans_mean_exclude, 4), + 'whole_thalamus_Ktrans_median': round(whole_thalamus_Ktrans_median_exclude, 4), + 'whole_thalamus_Ktrans_std': round(whole_thalamus_Ktrans_std_exclude, 4), + 'whole_caudate_Ktrans_mean': round(whole_caudate_Ktrans_mean_exclude, 4), + 'whole_caudate_Ktrans_median': round(whole_caudate_Ktrans_median_exclude, 4), + 'whole_caudate_Ktrans_std': round(whole_caudate_Ktrans_std_exclude, 4), + 'whole_amygdala_Ktrans_mean': round(whole_amygdala_Ktrans_mean_exclude, 4), + 'whole_amygdala_Ktrans_median': round(whole_amygdala_Ktrans_median_exclude, 4), + 'whole_amygdala_Ktrans_std': round(whole_amygdala_Ktrans_std_exclude, 4), + 'whole_entorhinal_cortex_Ktrans_mean': round(whole_entorhinal_cortex_Ktrans_mean_exclude, 4), + 'whole_entorhinal_cortex_Ktrans_median': round(whole_entorhinal_cortex_Ktrans_median_exclude, 4), + 'whole_entorhinal_cortex_Ktrans_std': round(whole_entorhinal_cortex_Ktrans_std_exclude, 4), + 'whole_fusiform_gyrus_cortex_Ktrans_mean': round(whole_fusiform_gyrus_cortex_Ktrans_mean_exclude, 4), + 'whole_fusiform_gyrus_cortex_Ktrans_median': round(whole_fusiform_gyrus_cortex_Ktrans_median_exclude, 4), + 'whole_fusiform_gyrus_cortex_Ktrans_std': round(whole_fusiform_gyrus_cortex_Ktrans_std_exclude, 4), + 'whole_fusiform_gyrus_WM_Ktrans_mean': round(whole_fusiform_gyrus_WM_Ktrans_mean_exclude, 4), + 'whole_fusiform_gyrus_WM_Ktrans_median': round(whole_fusiform_gyrus_WM_Ktrans_median_exclude, 4), + 'whole_fusiform_gyrus_WM_Ktrans_std': round(whole_fusiform_gyrus_WM_Ktrans_std_exclude, 4), + 'whole_insula_WM_Ktrans_mean': round(whole_insula_WM_Ktrans_mean_exclude, 4), + 'whole_insula_WM_Ktrans_median': round(whole_insula_WM_Ktrans_median_exclude, 4), + 'whole_insula_WM_Ktrans_std': round(whole_insula_WM_Ktrans_std_exclude, 4), + 'whole_superior_temporal_cortex_Ktrans_mean': round(whole_superior_temporal_cortex_Ktrans_mean_exclude, 4), + 'whole_superior_temporal_cortex_Ktrans_median': round(whole_superior_temporal_cortex_Ktrans_median_exclude, 4), + 'whole_superior_temporal_cortex_Ktrans_std': round(whole_superior_temporal_cortex_Ktrans_std_exclude, 4), + 'whole_inferior_temporal_cortex_Ktrans_mean': round(whole_inferior_temporal_cortex_Ktrans_mean_exclude, 4), + 'whole_inferior_temporal_cortex_Ktrans_median': round(whole_inferior_temporal_cortex_Ktrans_median_exclude, 4), + 'whole_inferior_temporal_cortex_Ktrans_std': round(whole_inferior_temporal_cortex_Ktrans_std_exclude, 4), + 'whole_posterior_cingulate_cortex_Ktrans_mean': round(whole_posterior_cingulate_cortex_Ktrans_mean_exclude, 4), + 'whole_posterior_cingulate_cortex_Ktrans_median': round(whole_posterior_cingulate_cortex_Ktrans_median_exclude, 4), + 'whole_posterior_cingulate_cortex_Ktrans_std': round(whole_posterior_cingulate_cortex_Ktrans_std_exclude, 4), + 'whole_medial_temporal_cortex_Ktrans_mean': round(whole_medial_temporal_cortex_Ktrans_mean_exclude, 4), + 'whole_medial_temporal_cortex_Ktrans_median': round(whole_medial_temporal_cortex_Ktrans_median_exclude, 4), + 'whole_medial_temporal_cortex_Ktrans_std': round(whole_medial_temporal_cortex_Ktrans_std_exclude, 4), + 'ktrans_wm_outliers': Ktrans_wm_outliers, + 'ktrans_gm_outliers': Ktrans_gm_outliers, + 'T1_blood_histogram': "../../" + T1_blood_histogram_exclude_path, + 'wm_histogram': "../../" + ktrans_wm_histogram_exclude_path, + 'gm_histogram': "../../" + ktrans_gm_histogram_exclude_path, + 'whole_hippo_outliers': whole_hippo_outliers, + 'whole_phg_outliers': whole_phg_outliers, + 'whole_putamen_outliers': whole_putamen_outliers, + 'whole_pallidum_outliers': whole_pallidum_outliers, + 'whole_thalamus_outliers': whole_thalamus_outliers, + 'whole_caudate_outliers': whole_caudate_outliers, + 'whole_amygdala_outliers': whole_amygdala_outliers, + 'whole_entorhinal_cortex_outliers': whole_entorhinal_cortex_outliers, + 'whole_fusiform_gyrus_cortex_outliers': whole_fusiform_gyrus_cortex_outliers, + 'whole_fusiform_gyrus_WM_outliers': whole_fusiform_gyrus_WM_outliers, + 'whole_insula_WM_outliers': whole_insula_WM_outliers, + 'whole_superior_temporal_cortex_outliers': whole_superior_temporal_cortex_outliers, + 'whole_inferior_temporal_cortex_outliers': whole_inferior_temporal_cortex_outliers, + 'whole_posterior_cingulate_cortex_outliers': whole_posterior_cingulate_cortex_outliers, + 'whole_medial_temporal_cortex_outliers': whole_medial_temporal_cortex_outliers, + 'Ktrans_whole_hippo_histogram': "../../" + whole_hippo_histogram_exclude_path, + 'Ktrans_whole_phg_histogram': "../../" + whole_phg_histogram_exclude_path, + 'Ktrans_whole_putamen_histogram': "../../" + whole_putamen_histogram_exclude_path, + 'Ktrans_whole_pallidum_histogram': "../../" + whole_pallidum_histogram_exclude_path, + 'Ktrans_whole_thalamus_histogram': "../../" + whole_thalamus_histogram_exclude_path, + 'Ktrans_whole_caudate_histogram': "../../" + whole_caudate_histogram_exclude_path, + 'Ktrans_whole_amygdala_histogram': "../../" + whole_amygdala_histogram_exclude_path, + 'Ktrans_whole_entorhinal_cortex_histogram': "../../" + whole_entorhinal_cortex_histogram_exclude_path, + 'Ktrans_whole_fusiform_gyrus_cortex_histogram': "../../" + whole_fusiform_gyrus_cortex_histogram_exclude_path, + 'Ktrans_whole_fusiform_gyrus_WM_histogram': "../../" + whole_fusiform_gyrus_WM_histogram_exclude_path, + 'Ktrans_whole_insula_WM_histogram': "../../" + whole_insula_WM_histogram_exclude_path, + 'Ktrans_whole_superior_temporal_cortex_histogram': "../../" + whole_superior_temporal_cortex_histogram_exclude_path, + 'Ktrans_whole_inferior_temporal_cortex_histogram': "../../" + whole_inferior_temporal_cortex_histogram_exclude_path, + 'Ktrans_whole_posterior_cingulate_cortex_histogram': "../../" + whole_posterior_cingulate_cortex_histogram_exclude_path, + 'Ktrans_whole_medial_temporal_cortex_histogram': "../../" + whole_medial_temporal_cortex_histogram_exclude_path +} + +output_exclude = template.render(data) + +# make reports directory if it doesn't exist +if not os.path.exists(dir + '/reports'): + os.makedirs(dir + '/reports') + +run_folder = output_dir[1:] +# make output_dir directory if it doesn't exist +if not os.path.exists(dir + '/reports/' + run_folder): + os.makedirs(dir + '/reports/' + run_folder) + +# write html to file +with open(dir + '/reports/' + run_folder + '/population_report' + output_dir + "_" + date_filename + '.html', 'w') as f: + f.write(output) + +with open(dir + '/reports/' + run_folder + '/population_report_exclude' + output_dir + "_" + date_filename + '.html', 'w') as f: + f.write(output_exclude) + +print('Report generated in ' + dir + '/reports/' + run_folder + '/population_report' + output_dir + "_" + date_filename + '.html') +print('Excluded report generated in ' + dir + '/reports/' + run_folder + '/population_report_exclude' + output_dir + "_" + date_filename + '.html') + +if os.path.exists(os.path.join(dir, '../dce_available_3524_ac.xlsx')): + # add apoe and cdr fields to population_data + df = pd.read_excel(os.path.join(dir, '../dce_available_3524_ac.xlsx'), sheet_name="main") + + # get apoe and cdr values for each subject + for subject in population_data.keys(): + # get subject's ID + subject_id = subject.split("_")[0] + subject_id = subject_id.split("-")[1] + # get subject's timepoint + timepoint = subject.split("_")[1] + # get subject's apoe and cdr values + if subject_id.startswith("4") or subject_id.startswith("3"): + # insert underscore after 1st character + subject_id = subject_id[:1] + "_" + subject_id[1:] + try: + if int(subject_id) in df['Subject_ID'].values: + apoe = df.loc[df['Subject_ID'] == int(subject_id), 'APOE'].values[0] + # cdr = df.loc[df['Subject_ID'] == int(subject_id), 'CDR'].values[0] + # bmi = df.loc[df['Subject_ID'] == int(subject_id), 'BMI'].values[0] + else: + apoe = "N/A" + cdr = "N/A" + bmi = "N/A" + # add to population_data + except Exception as e: + print(e) + # print("Subject " + subject_id + " not found in dce_available_3524_ac.xlsx") + apoe = "N/A" + cdr = "N/A" + bmi = "N/A" + population_data[subject]["APOE"] = apoe + # population_data[subject]["CDR"] = cdr + # population_data[subject]["BMI"] = bmi + + for subject in population_data_exclude.keys(): + # get subject's ID + subject_id = subject.split("_")[0] + subject_id = subject_id.split("-")[1] + # get subject's timepoint + timepoint = subject.split("_")[1] + # get subject's apoe and cdr values + if subject_id.startswith("4") or subject_id.startswith("3"): + # insert underscore after 1st character + subject_id = subject_id[:1] + "_" + subject_id[1:] + try: + apoe = df.loc[df['Subject_ID'] == int(subject_id), 'APOE'].values[0] + # cdr = df.loc[df['Subject_ID'] == int(subject_id), 'CDR'].values[0] + # bmi = df.loc[df['Subject_ID'] == int(subject_id), 'BMI'].values[0] + # add to population_data + except Exception as e: + print(e) + print("Subject " + subject_id + " not found in dce_available_3524_ac.xlsx") + apoe = "N/A" + cdr = "N/A" + bmi = "N/A" + population_data_exclude[subject]["APOE"] = apoe + # population_data_exclude[subject]["CDR"] = cdr + # population_data_exclude[subject]["BMI"] = bmi + + # for subject in population_data_exclude_signa.keys(): + # # get subject's ID + # subject_id = subject.split("_")[0] + # subject_id = subject_id.split("-")[1] + # # get subject's timepoint + # timepoint = subject.split("_")[1] + # # get subject's apoe and cdr values + # if subject_id.startswith("4") or subject_id.startswith("3"): + # # insert underscore after 1st character + # subject_id = subject_id[:1] + "_" + subject_id[1:] + # try: + # apoe = df.loc[df['Subject_ID'] == int(subject_id), 'APOE'].values[0] + # cdr = df.loc[df['Subject_ID'] == int(subject_id), 'CDR'].values[0] + # bmi = df.loc[df['Subject_ID'] == int(subject_id), 'BMI'].values[0] + # # add to population_data + # except Exception as e: + # print(e) + # print("Subject " + subject_id + " not found in dce_available_3524_ac.xlsx") + # apoe = "N/A" + # cdr = "N/A" + # bmi = "N/A" + # population_data_exclude_signa[subject]["APOE"] = apoe + # population_data_exclude_signa[subject]["CDR"] = cdr + # population_data_exclude_signa[subject]["BMI"] = bmi + + for subject in population_data_failed.keys(): + # get subject's ID + subject_id = subject.split("_")[0] + subject_id = subject_id.split("-")[1] + # get subject's timepoint + timepoint = subject.split("_")[1] + # get subject's apoe and cdr values + if subject_id.startswith("4") or subject_id.startswith("3"): + # insert underscore after 1st character + subject_id = subject_id[:1] + "_" + subject_id[1:] + try: + apoe = df.loc[df['Subject_ID'] == int(subject_id), 'APOE'].values[0] + # cdr = df.loc[df['Subject_ID'] == int(subject_id), 'CDR'].values[0] + # bmi = df.loc[df['Subject_ID'] == int(subject_id), 'BMI'].values[0] + # add to population_data + except Exception as e: + print(e) + print("Subject " + subject_id + " not found in dce_available_3524_ac.xlsx") + apoe = "N/A" + cdr = "N/A" + bmi = "N/A" + population_data_failed[subject]["APOE"] = apoe + # population_data_failed[subject]["CDR"] = cdr + # population_data_failed[subject]["BMI"] = bmi + + # read EXCLUDED sheet from dce_available_3524_ac.xlsx and move subjects to population_data_exclude + df = pd.read_excel(os.path.join(dir, '../dce_available_3524_ac.xlsx'), sheet_name="EXCLUDED") + # get subject ID and timepoint (first and second columns) + subjects_excluded = df['Subject_ID'] + timepoints_excluded = df['Timepoint'] + # get exclusion reasons (third column) + exclusion_reasons = df['REASON'] + dates_excluded = df['Study_Date'] + # if excluded subject is in population_data, move to population_data_exclude + for subject, timepoint, exclusion_reason in zip(subjects_excluded, timepoints_excluded, exclusion_reasons): + # skip if reason includes "aliasing" + if "aliasing" in exclusion_reason.lower(): + continue + if "no t1w" in exclusion_reason.lower(): + continue + if "t1w only" in exclusion_reason.lower(): + continue + if "no fas" in exclusion_reason.lower(): + continue + # get subject's ID + subject_id = subject + # get subject's timepoint + # if NaN, set to 1 + if pd.isnull(timepoint): + timepoint = 1 + timepoint = int(timepoint) + entry = f"{subject_id}_ses-0{timepoint}" + if entry in population_data.keys(): + # move to population_data_exclude + population_data_exclude[entry] = population_data.pop(entry) + # add exclusion reason if not already there + if "Reason" not in population_data_exclude[entry].keys(): + population_data_exclude[entry]["Reason"] = exclusion_reason + else: + population_data_exclude[entry]["Reason"] += ", " + exclusion_reason + # add date + # population_data_exclude[entry]["Date"] = dates_excluded[subjects_excluded == subject].values[0] + # remove from population_data + # del population_data[entry] + elif entry not in population_data.keys() and entry not in population_data_failed.keys() and entry not in population_data_exclude.keys():# and entry not in population_data_exclude_signa.keys(): + # read whole row from dce_available_3524_ac.xlsx + row = df.loc[df['Subject_ID'] == subject_id] + population_data_exclude[entry] = {} + population_data_exclude[entry]["Reason"] = exclusion_reason + population_data_exclude[entry]["Timepoint"] = row['Timepoint'].values[0] + population_data_exclude[entry]["APOE"] = row['APOE'].values[0] + population_data_exclude[entry]["Sex"] = row['Sex'].values[0] + population_data_exclude[entry]["Age"] = row['Age'].values[0] + population_data_exclude[entry]["Date"] = row['Study_Date'].values[0] + # population_data_exclude[entry]["CDR"] = row['CDR'].values[0] + # population_data_exclude[entry]["BMI"] = row['BMI'].values[0] + # Fill rest of fields with default values + # fields = [ + # "Machine", "Institution", "Coil", "TR", "Time_resolution", "TE", "Flip_angle", "n_reps", + # "Approximate SNR", "AIFitness", "aif_fitted_r2", "manual_aif_status", "max_disp", "T1_blood", "T1_wm_median", "T1_gm_median", + # "Ktrans_wm_median", "Ktrans_gm_median", "Ktrans_Hippo_median", "Ktrans_PhG_median", "Ktrans_Putamen_median", "Ktrans_Pallidum_median", + # "Ktrans_Thalamus_median", "Ktrans_Caudate_median", "Ktrans_Amygdala_median", "Ktrans_Entorhinal_cortex_median", + # "Ktrans_Fusiform_gyrus_cortex_median", "Ktrans_Fusiform_gyrus_WM_median", "Ktrans_Insula_WM_median", + # "Ktrans_Superior_temporal_cortex_median", "Ktrans_Inferior_temporal_cortex_median", "Ktrans_Posterior_cingulate_cortex_median", "Ktrans_Medial_temporal_cortex_median", + # "Vp_Hippo_median", "Vp_PhG_median", "Vp_Putamen_median", "Vp_Pallidum_median", "Vp_Thalamus_median", + # "Vp_Caudate_median", "Vp_Amygdala_median", "Vp_Entorhinal_cortex_median", "Vp_Fusiform_gyrus_cortex_median", + # "Vp_Fusiform_gyrus_WM_median", "Vp_Insula_WM_median", "Vp_Superior_temporal_cortex_median", + # "Vp_Inferior_temporal_cortex_median", "Vp_Posterior_cingulate_cortex_median", "Vp_Medial_temporal_cortex_median", + # "hippo_vol", "phg_vol", "putamen_vol", "pallidum_vol", "thalamus_vol", "caudate_vol", "amygdala_vol", + # "entorhinal_cortex_vol", "fusiform_gyrus_cortex_vol", "fusiform_gyrus_wm_vol", "insula_wm_vol", + # "superior_temporal_cortex_vol", "inferior_temporal_cortex_vol", "posterior_cingulate_cortex_vol", "medial_temporal_cortex_vol", + # "bankssts_thickness_avg", "bankssts_thickness_std", "caudalanteriorcingulate_thickness_avg", "caudalanteriorcingulate_thickness_std", + # "caudalmiddlefrontal_thickness_avg", "caudalmiddlefrontal_thickness_std", "cuneus_thickness_avg", "cuneus_thickness_std", + # "entorhinal_thickness_avg", "entorhinal_thickness_std", "fusiform_thickness_avg", "fusiform_thickness_std", + # "inferiorparietal_thickness_avg", "inferiorparietal_thickness_std", "inferiortemporal_thickness_avg", "inferiortemporal_thickness_std", + # "isthmuscingulate_thickness_avg", "isthmuscingulate_thickness_std", "lateraloccipital_thickness_avg", "lateraloccipital_thickness_std", + # "lateralorbitofrontal_thickness_avg", "lateralorbitofrontal_thickness_std", "lingual_thickness_avg", "lingual_thickness_std", + # "medialorbitofrontal_thickness_avg", "medialorbitofrontal_thickness_std", "middletemporal_thickness_avg", "middletemporal_thickness_std", + # "parahippocampal_thickness_avg", "parahippocampal_thickness_std", "paracentral_thickness_avg", "paracentral_thickness_std", + # "parsopercularis_thickness_avg", "parsopercularis_thickness_std", "parsorbitalis_thickness_avg", "parsorbitalis_thickness_std", + # "parstriangularis_thickness_avg", "parstriangularis_thickness_std", "pericalcarine_thickness_avg", "pericalcarine_thickness_std", + # "postcentral_thickness_avg", "postcentral_thickness_std", "posteriorcingulate_thickness_avg", "posteriorcingulate_thickness_std", + # "precentral_thickness_avg", "precentral_thickness_std", "precuneus_thickness_avg", "precuneus_thickness_std", + # "rostralanteriorcingulate_thickness_avg", "rostralanteriorcingulate_thickness_std", "rostralmiddlefrontal_thickness_avg", + # "rostralmiddlefrontal_thickness_std", "superiorfrontal_thickness_avg", "superiorfrontal_thickness_std", + # "superiorparietal_thickness_avg", "superiorparietal_thickness_std", "superiortemporal_thickness_avg", "superiortemporal_thickness_std", + # "supramarginal_thickness_avg", "supramarginal_thickness_std", "frontalpole_thickness_avg", "frontalpole_thickness_std", + # "temporalpole_thickness_avg", "temporalpole_thickness_std", "transversetemporal_thickness_avg", "transversetemporal_thickness_std", + # "insula_thickness_avg", "insula_thickness_std" + # ] + # for field in fields: + # if field not in population_data_exclude[entry]: + # population_data_exclude[entry][field] = -1 + +# make excel file +if not os.path.exists(os.path.join(dir, "spreadsheets", output_dir[1:])): + os.makedirs(os.path.join(dir, "spreadsheets", output_dir[1:])) +writer = pd.ExcelWriter( + os.path.join(dir, "spreadsheets", output_dir[1:], "dataset_ktrans" + output_dir + "_" + date_filename + ".xlsx"), + date_format='YYYY/MM/DD', + datetime_format='YYYY/MM/DD', + engine='xlsxwriter' +) +# make dataframe +df_success = pd.DataFrame(population_data) +# df_exclude = pd.DataFrame(population_data_exclude) + +order = ["Date", "APOE", "Sex", "Age", "Machine", "Institution", "Coil", "TR", "Time_resolution", "TE", "Flip_angle", "n_reps", + "Approximate SNR", "AIFitness", "aif_fitted_r2", "manual_aif_status", "max_disp", "T1_blood", "T1_wm_median", "T1_gm_median", + "Ktrans_wm_median", "Ktrans_gm_median", "Ktrans_Hippo_median", "Ktrans_PhG_median", "Ktrans_Putamen_median", "Ktrans_Pallidum_median", + "Ktrans_Thalamus_median", "Ktrans_Caudate_median", "Ktrans_Amygdala_median", "Ktrans_Entorhinal_cortex_median", + "Ktrans_Fusiform_gyrus_cortex_median", "Ktrans_Fusiform_gyrus_WM_median", "Ktrans_Insula_WM_median", + "Ktrans_Superior_temporal_cortex_median", "Ktrans_Inferior_temporal_cortex_median", "Ktrans_Posterior_cingulate_cortex_median", "Ktrans_Medial_temporal_cortex_median", + "Vp_Hippo_median", "Vp_PhG_median", "Vp_Putamen_median", "Vp_Pallidum_median", "Vp_Thalamus_median", + "Vp_Caudate_median", "Vp_Amygdala_median", "Vp_Entorhinal_cortex_median", "Vp_Fusiform_gyrus_cortex_median", + "Vp_Fusiform_gyrus_WM_median", "Vp_Insula_WM_median", "Vp_Superior_temporal_cortex_median", + "Vp_Inferior_temporal_cortex_median", "Vp_Posterior_cingulate_cortex_median", "Vp_Medial_temporal_cortex_median", + "hippo_vol", "phg_vol", "putamen_vol", "pallidum_vol", "thalamus_vol", "caudate_vol", "amygdala_vol", + "entorhinal_cortex_vol", "fusiform_gyrus_cortex_vol", "fusiform_gyrus_wm_vol", "insula_wm_vol", + "superior_temporal_cortex_vol", "inferior_temporal_cortex_vol", "posterior_cingulate_cortex_vol", "medial_temporal_cortex_vol", + "bankssts_thickness_avg", "bankssts_thickness_std", "caudalanteriorcingulate_thickness_avg", "caudalanteriorcingulate_thickness_std", + "caudalmiddlefrontal_thickness_avg", "caudalmiddlefrontal_thickness_std", "cuneus_thickness_avg", "cuneus_thickness_std", + "entorhinal_thickness_avg", "entorhinal_thickness_std", "fusiform_thickness_avg", "fusiform_thickness_std", + "inferiorparietal_thickness_avg", "inferiorparietal_thickness_std", "inferiortemporal_thickness_avg", "inferiortemporal_thickness_std", + "isthmuscingulate_thickness_avg", "isthmuscingulate_thickness_std", "lateraloccipital_thickness_avg", "lateraloccipital_thickness_std", + "lateralorbitofrontal_thickness_avg", "lateralorbitofrontal_thickness_std", "lingual_thickness_avg", "lingual_thickness_std", + "medialorbitofrontal_thickness_avg", "medialorbitofrontal_thickness_std", "middletemporal_thickness_avg", "middletemporal_thickness_std", + "parahippocampal_thickness_avg", "parahippocampal_thickness_std", "paracentral_thickness_avg", "paracentral_thickness_std", + "parsopercularis_thickness_avg", "parsopercularis_thickness_std", "parsorbitalis_thickness_avg", "parsorbitalis_thickness_std", + "parstriangularis_thickness_avg", "parstriangularis_thickness_std", "pericalcarine_thickness_avg", "pericalcarine_thickness_std", + "postcentral_thickness_avg", "postcentral_thickness_std", "posteriorcingulate_thickness_avg", "posteriorcingulate_thickness_std", + "precentral_thickness_avg", "precentral_thickness_std", "precuneus_thickness_avg", "precuneus_thickness_std", + "rostralanteriorcingulate_thickness_avg", "rostralanteriorcingulate_thickness_std", "rostralmiddlefrontal_thickness_avg", + "rostralmiddlefrontal_thickness_std", "superiorfrontal_thickness_avg", "superiorfrontal_thickness_std", + "superiorparietal_thickness_avg", "superiorparietal_thickness_std", "superiortemporal_thickness_avg", "superiortemporal_thickness_std", + "supramarginal_thickness_avg", "supramarginal_thickness_std", "frontalpole_thickness_avg", "frontalpole_thickness_std", + "temporalpole_thickness_avg", "temporalpole_thickness_std", "transversetemporal_thickness_avg", "transversetemporal_thickness_std", + "insula_thickness_avg", "insula_thickness_std"] + +df_success = df_success.T +df_success = df_success[order] + + +order_exclude = order.copy() +order_exclude.insert(0, "Reason") + +# name first column +df_success.index.name = "Subject_ID" + +# write to excel +df_success.to_excel(writer, sheet_name='Success') +cell_format = writer.book.add_format() +cell_format.set_text_wrap() +cell_format.set_align('center') +cell_format.set_align('vcenter') +index_cell_format = writer.book.add_format() +index_cell_format.set_text_wrap() +index_cell_format.set_align('center') +index_cell_format.set_align('vcenter') +# unbold index +index_cell_format.set_bold(False) +if len(population_data_exclude) > 0: + df_exclude = pd.DataFrame(population_data_exclude) + df_exclude = df_exclude.T + df_exclude = df_exclude[order_exclude] + df_exclude.index.name = "Subject_ID" + df_exclude.to_excel(writer, sheet_name='Pre-Exclude') + for column in df_exclude.columns: + max_length = df_exclude[column].map(str).map(len).max() + max_length = max(max_length, len(column)) + if column == "Date": + writer.sheets['Pre-Exclude'].set_column(df_exclude.columns.get_loc(column)+1, df_exclude.columns.get_loc(column)+1, 10, cell_format) + else: + writer.sheets['Pre-Exclude'].set_column(df_exclude.columns.get_loc(column)+1, df_exclude.columns.get_loc(column)+1, max_length+2, cell_format) + + writer.sheets['Pre-Exclude'].set_column(0, 0, 20, index_cell_format) + writer.sheets['Pre-Exclude'].autofilter(0, 0, len(df_exclude), len(df_exclude.columns)) + +if len(population_data_failed) > 0: + df_fail = pd.DataFrame(population_data_failed) + df_fail = df_fail.T + df_fail = df_fail[order_exclude] + df_fail.index.name = "Subject_ID" + df_fail.to_excel(writer, sheet_name='Fail') + +if len(population_data_missing) > 0: + df_missing = pd.DataFrame(population_data_missing) + df_missing = df_missing.T + df_missing.index.name = "Subject_ID" + df_missing.to_excel(writer, sheet_name='Missing') + +# if len(population_data_exclude_signa) > 0: +# df_exclude_signa = pd.DataFrame(population_data_exclude_signa) +# df_exclude_signa = df_exclude_signa.T +# df_exclude_signa = df_exclude_signa[order_exclude] +# df_exclude_signa.index.name = "Subject_ID" +# df_exclude_signa.to_excel(writer, sheet_name='Crazy GE Data') + +for column in df_success.columns: + max_length = df_success[column].map(str).map(len).max() + max_length = max(max_length, len(column)) + if column == "Date": + writer.sheets['Success'].set_column(df_success.columns.get_loc(column)+1, df_success.columns.get_loc(column)+1, 10, cell_format) + else: + writer.sheets['Success'].set_column(df_success.columns.get_loc(column)+1, df_success.columns.get_loc(column)+1, max_length+2, cell_format) + +writer.sheets['Success'].set_column(0, 0, 20, index_cell_format) +# autofilter +writer.sheets['Success'].autofilter(0, 0, len(df_success), len(df_success.columns)) +# change date column data format to MM/DD/YYYY +writer.close() + +# now backfill each subject's placement in the population +imgs = [] +imgs_exclude = [] +for subject_id in subjects: +# for subject_id, timepoint in zip(subjects, timepoints): + # list _timepoint directories in subject directory + for timepoint in subject_timepoints[subject_id]: + if timepoint.startswith("ses-"): + placement_wm_histogram_path = os.path.join(dceprep_dir, subject_id, timepoint, "figures/placement_wm_histogram.png") + placement_gm_histogram_path = os.path.join(dceprep_dir, subject_id, timepoint, "figures/placement_gm_histogram.png") + # get subject's Ktrans_wm_mean + try: + if f"{subject_id}_{timepoint}" in population_data.keys(): + case_wm_median = population_data[subject_id + "_" + timepoint]["Ktrans_wm_median"] + case_gm_median = population_data[subject_id + "_" + timepoint]["Ktrans_gm_median"] + elif f"{subject_id}_{timepoint}" in population_data_exclude.keys(): + case_wm_median = population_data_exclude[subject_id + "_" + timepoint]["Ktrans_wm_median"] + case_gm_median = population_data_exclude[subject_id + "_" + timepoint]["Ktrans_gm_median"] + # elif f"{subject_id}_{timepoint}" in population_data_exclude_signa.keys(): + # case_wm_median = population_data_exclude_signa[subject_id + "_" + timepoint]["Ktrans_wm_median"] + # case_gm_median = population_data_exclude_signa[subject_id + "_" + timepoint]["Ktrans_gm_median"] + elif f"{subject_id}_{timepoint}" in population_data_failed.keys(): + case_wm_median = population_data_failed[subject_id + "_" + timepoint]["Ktrans_wm_median"] + case_gm_median = population_data_failed[subject_id + "_" + timepoint]["Ktrans_gm_median"] + else: + case_wm_median = -1 + case_gm_median = -1 + except Exception as e: + print(f"Error getting {subject_id} {timepoint} wm or gm median.") + print(e) + continue + + if os.path.exists(placement_wm_histogram_path) and os.path.exists(placement_gm_histogram_path): + # plot histograms + plt.hist(wm_histogram, bins=30) + plt.title("Ktrans White Matter Median") + plt.xlabel("Ktrans (10^-3/min)") + plt.axvline(x=case_wm_median, color='black') + # put percentile text in top right corner + plt.text(0.9, 0.95, "Percentile: " + str(round((len([x for x in wm_histogram if x < case_wm_median]) / len(wm_histogram)) * 100, 2)) + "%", horizontalalignment='center', verticalalignment='center', transform=plt.gca().transAxes) + plt.savefig(placement_wm_histogram_path, bbox_inches='tight') + plt.close() + + plt.hist(gm_histogram, bins=30) + plt.title("Ktrans Gray Matter Median") + plt.xlabel("Ktrans (10^-3/min)") + plt.axvline(x=case_gm_median, color='black') + # put percentile text in top right corner + plt.text(0.9, 0.95, "Percentile: " + str(round((len([x for x in gm_histogram if x < case_gm_median]) / len(gm_histogram)) * 100, 2)) + "%", horizontalalignment='center', verticalalignment='center', transform=plt.gca().transAxes) + plt.savefig(placement_gm_histogram_path, bbox_inches='tight') + plt.close() + + # append to html file + try: + filename = os.path.join(dceprep_dir, subject_id, timepoint, f"reports/{subject_id}_{timepoint}_desc-casereport.html") + if os.path.exists(filename): + with open(filename, "r") as f: + report_content = f.read() + + # replace placeholder with histogram path + report_content = report_content.replace("placeholder_wm", "../../figures/placement_wm_histogram.png") + report_content = report_content.replace("placeholder_gm", "../../figures/placement_gm_histogram.png") + # write html to file + with open(filename, 'w') as f: + f.write(report_content) + + report_path = os.path.join(dceprep_dir, subject_id, timepoint, f"reports/{subject_id}_{timepoint}_desc-report.png") + + if f"{subject_id}_{timepoint}" in population_data.keys(): + imgs.append(report_path) + elif f"{subject_id}_{timepoint}" in population_data_exclude.keys(): + imgs_exclude.append(report_path) + except Exception as e: + print(f"Error appending {subject_id} {timepoint} placement histograms.") + print(e) + +# make scrollable report +# Function to add images to PDF +def add_image_to_pdf(pdf, image_path): + pdf.drawImage(image_path, 50, 50, width=letter[0]-85, height=letter[1]-50) # Adjust coordinates and dimensions as needed + pdf.showPage() + +# Create a PDF canvas +pdf_file = f"{dir}/reports/EZQCreport" + output_dir + "_" + date_filename + ".pdf" +pdf = canvas.Canvas(pdf_file, pagesize=letter) + +# Add images to the PDF +for report in imgs: + try: + add_image_to_pdf(pdf, report) + except Exception as e: + print(f"Error adding {report} to PDF.") + print(e) + +# Save the PDF to dir/reports +pdf.save() + +pdf_file = f"{dir}/reports/EZQCreport_exclude" + output_dir + "_" + date_filename + ".pdf" +pdf = canvas.Canvas(pdf_file, pagesize=letter) + +# Add images to the PDF +for report in imgs_exclude: + try: + add_image_to_pdf(pdf, report) + except Exception as e: + print(f"Error adding {report} to PDF.") + print(e) + +pdf.save() diff --git a/population_template.html b/population_template.html new file mode 100644 index 0000000..7a2fc12 --- /dev/null +++ b/population_template.html @@ -0,0 +1,353 @@ + + + {{ title }} + + + + + +

Population Summary

+
    +
  • Subjects: {{ Subjects }}
  • + +
  • + Successful Cases: + {% for link, case in Combo %} + {{ case }}{% if not loop.last %}, {% endif %} + {% endfor %} +
  • +
  • + Failed Cases: + {% for link, case in Failed_combo %} + {{ case }}{% if not loop.last %}, {% endif %} + {% endfor %} +
  • + +
  • Successes: {{ Successes }}
  • +
  • Excludes: {{ Excludes }}
  • + +
  • Date: {{ Date }}
  • +
  • Commit: {{ Commit }}
  • +
  • ROCKETSHIP Commit: {{ ROCKETSHIP_commit }}
  • +
  • Manufacturer: {{ Manufacturers }}
  • +
  • Model: {{ Machines }}
  • +
  • Field Strength: {{ Field_strengths }}
  • +
  • Institute: {{ Institutions }}
  • +
+

ROCKETSHIP Script Preferences (defaults overridden by JSONs)

+
    +
  • Default TR: {{ pref_tr }}
  • +
  • Default FA: {{ pref_fa }}
  • +
  • Default Hematocrit: {{ pref_hematocrit }}
  • +
  • SNR Threshold: {{ pref_SNR }}
  • +
  • Relaxivity: {{ pref_relaxivity }}
  • +
  • Default T1 blood: {{ pref_t1blood }}
  • +
  • Starting repetition: {{ pref_start_t }}
  • +
  • Ending repetition: {{ pref_end_t }}
  • +
  • Time Resolution: {{ pref_timeres }}
  • +
  • Model: {{ pref_model }}
  • + +
+

T1 stats

+
    +
  • White matter mean: {{ T1_wm_mean }}
  • +
  • White matter median: {{ T1_wm_median }}
  • +
  • White matter stdev: {{ T1_wm_std }}
  • +
  • White matter 5th%: {{ T1_wm_5th_percentile }}
  • +
  • White matter 95th% {{ T1_wm_95th_percentile }}
  • +
    +
  • Gray matter mean: {{ T1_gm_mean }}
  • +
  • Gray matter median: {{ T1_gm_median }}
  • +
  • Gray matter stdev: {{ T1_gm_std }}
  • +
  • Gray matter 5th%: {{ T1_gm_5th_percentile }}
  • +
  • Gray matter 95th% {{ T1_gm_95th_percentile }}
  • +
+

AIF stats

+
    +
  • AIFitness mean: {{ AIFitness_mean }}
  • +
  • AIFitness median: {{ AIFitness_median }}
  • +
  • AIFitness stdev: {{ AIFitness_std }}
  • +
  • AIFitness 5th%: {{ AIFitness_5th_percentile }}
  • +
    +
  • mmol mean: {{ aif_mmol_mean }}
  • +
  • mmol median: {{ aif_mmol_median }}
  • +
  • mmol stdev: {{ aif_mmol_std }}
  • +
  • mmol 5th%: {{ aif_mmol_5th_percentile }}
  • +
  • mmol 95th% {{ aif_mmol_95th_percentile }}
  • +
+
+ missing AIFitness histogram + missing aif mmol histogram +
+ missing aif curves +
  • T1 blood mean median: {{ T1_blood_mean }}
  • +
  • T1 blood median median: {{ T1_blood_median }}
  • +
  • T1 blood stdev median: {{ T1_blood_std }}
  • +
  • T1 blood 5th%: {{ T1_blood_5th_percentile }}
  • +
  • T1 blood 95th% {{ T1_blood_95th_percentile }}
  • + missing T1_blood histogram +

    Ktrans stats (10^-3/min, >10^-5)

    +
      +
    • White matter mean median Ktrans: {{ Ktrans_wm_mean }}
    • +
    • White matter median median Ktrans: {{ Ktrans_wm_median }}
    • +
    • White matter stdev median Ktrans: {{ Ktrans_wm_std }}
    • +
    • Gray matter mean median Ktrans: {{ Ktrans_gm_mean }}
    • +
    • Gray matter median median Ktrans: {{ Ktrans_gm_median }}
    • +
    • Gray matter stdev median Ktrans: {{ Ktrans_gm_std }}
    • +
    +

    Ktrans Histogram

    +
      +
    • wm outliers (> 5): {{ ktrans_wm_outliers }}
    • +
    • gm outliers (> 5): {{ ktrans_gm_outliers }}
    • +
    +
    + missing white matter histogram + missing gray matter histogram +
    +

    recon-all Ktrans (10^-3/min, >10^-5)

    +

    Parahippocampal Gyrus

    +
      +
    • Whole PHG mean median: {{ whole_phg_Ktrans_mean }}
    • +
    • Whole PHG median median: {{ whole_phg_Ktrans_median }}
    • +
    • Whole PHG stdev median: {{ whole_phg_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_phg_outliers }}
    • + missing whole PHG histogram +
    +

    Hippocampus

    +
      +
    • Whole hippocampus mean median: {{ whole_hippo_Ktrans_mean }}
    • +
    • Whole hippocampus median median: {{ whole_hippo_Ktrans_median }}
    • +
    • Whole hippocampus stdev median: {{ whole_hippo_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_hippo_outliers }}
    • + missing whole hippocampus histogram +
    +

    Putamen

    +
      +
    • Whole putamen mean median: {{ whole_putamen_Ktrans_mean }}
    • +
    • Whole putamen median median: {{ whole_putamen_Ktrans_median }}
    • +
    • Whole putamen stdev median: {{ whole_putamen_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_putamen_outliers }}
    • + missing whole putamen histogram +
    +

    Pallidum

    +
      +
    • Whole pallidum mean median: {{ whole_pallidum_Ktrans_mean }}
    • +
    • Whole pallidum median median: {{ whole_pallidum_Ktrans_median }}
    • +
    • Whole pallidum stdev median: {{ whole_pallidum_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_pallidum_outliers }}
    • + missing whole pallidum histogram +
    +

    Thalamus

    +
      +
    • Whole thalamus mean median: {{ whole_thalamus_Ktrans_mean }}
    • +
    • Whole thalamus median median: {{ whole_thalamus_Ktrans_median }}
    • +
    • Whole thalamus stdev median: {{ whole_thalamus_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_thalamus_outliers }}
    • + missing whole thalamus histogram +
    +

    Caudate

    +
      +
    • Whole caudate mean median: {{ whole_caudate_Ktrans_mean }}
    • +
    • Whole caudate median median: {{ whole_caudate_Ktrans_median }}
    • +
    • Whole caudate stdev median: {{ whole_caudate_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_caudate_outliers }}
    • + missing whole caudate histogram +
    +

    Amygdala

    +
      +
    • Whole amygdala mean median: {{ whole_amygdala_Ktrans_mean }}
    • +
    • Whole amygdala median median: {{ whole_amygdala_Ktrans_median }}
    • +
    • Whole amygdala stdev median: {{ whole_amygdala_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_amygdala_outliers }}
    • + missing whole amygdala histogram +
    + +

    Entorhinal Cortex

    +
      +
    • Whole entorhinal cortex mean median: {{ whole_entorhinal_cortex_Ktrans_mean }}
    • +
    • Whole entorhinal cortex median median: {{ whole_entorhinal_cortex_Ktrans_median }}
    • +
    • Whole entorhinal cortex stdev median: {{ whole_entorhinal_cortex_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_entorhinal_cortex_outliers }}
    • + missing whole entorhinal cortex histogram +
    + +

    Fusiform Gyrus

    +

    Fusiform Gyrus Cortex

    +
      +
    • Whole fusiform gyrus cortex mean median: {{ whole_fusiform_gyrus_cortex_Ktrans_mean }}
    • +
    • Whole fusiform gyrus cortex median median: {{ whole_fusiform_gyrus_cortex_Ktrans_median }}
    • +
    • Whole fusiform gyrus cortex stdev median: {{ whole_fusiform_gyrus_cortex_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_fusiform_gyrus_cortex_outliers }}
    • + missing whole fusiform gyrus cortex histogram +
    + +

    Fusiform Gyrus White Matter

    +
      +
    • Whole fusiform gyrus white matter mean median: {{ whole_fusiform_gyrus_WM_Ktrans_mean }}
    • +
    • Whole fusiform gyrus white matter median median: {{ whole_fusiform_gyrus_WM_Ktrans_median }}
    • +
    • Whole fusiform gyrus white matter stdev median: {{ whole_fusiform_gyrus_WM_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_fusiform_gyrus_WM_outliers }}
    • + missing whole fusiform gyrus white matter histogram +
    + +

    Insula White Matter

    +
      +
    • Whole insula white matter mean median: {{ whole_insula_WM_Ktrans_mean }}
    • +
    • Whole insula white matter median median: {{ whole_insula_WM_Ktrans_median }}
    • +
    • Whole insula white matter stdev median: {{ whole_insula_WM_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_insula_WM_outliers }}
    • + missing whole insula white matter histogram +
    + +

    Superior Temporal Cortex

    +
      +
    • Whole superior temporal cortex mean median: {{ whole_superior_temporal_cortex_Ktrans_mean }}
    • +
    • Whole superior temporal cortex median median: {{ whole_superior_temporal_cortex_Ktrans_median }}
    • +
    • Whole superior temporal cortex stdev median: {{ whole_superior_temporal_cortex_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_superior_temporal_cortex_outliers }}
    • + missing whole superior temporal cortex histogram +
    +

    Posterior Cingulate Cortex

    +
      +
    • Whole posterior cingulate cortex mean median: {{ whole_posterior_cingulate_cortex_Ktrans_mean }}
    • +
    • Whole posterior cingulate cortex median median: {{ whole_posterior_cingulate_cortex_Ktrans_median }}
    • +
    • Whole posterior cingulate cortex stdev median: {{ whole_posterior_cingulate_cortex_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_posterior_cingulate_cortex_outliers }}
    • + missing whole posterior cingulate cortex histogram +
    +

    Medial Temporal Cortex

    +
      +
    • Whole medial temporal cortex mean median: {{ whole_medial_temporal_cortex_Ktrans_mean }}
    • +
    • Whole medial temporal cortex median median: {{ whole_medial_temporal_cortex_Ktrans_median }}
    • +
    • Whole medial temporal cortex stdev median: {{ whole_medial_temporal_cortex_Ktrans_std }}
    • +
    • outliers (> 5): {{ whole_medial_temporal_cortex_outliers }}
    • + missing whole medial temporal cortex histogram +
    +

    Flagged Cases (motion > {{ Motion_threshold }}mm, AIFitness < {{ AIFitness_threshold }})

    +
  • + {% for link, case in Flagged_combo %} + {% if "dceprep-" in link %} + {{ case }}{% if not loop.last %}, {% endif %} + {% else %} + {{ case }}{% if not loop.last %}, {% endif %} + {% endif %} + {% endfor %} +
  • + + diff --git a/preprocess_all.sh b/preprocess_all.sh index bad4c65..626c84f 100755 --- a/preprocess_all.sh +++ b/preprocess_all.sh @@ -1,48 +1,131 @@ #!/bin/bash shopt -s extglob -# FSL, AFNI, Matlab, ROCKETSHIP + parametric_scripts, and Python are required -# Within parametric_scripts should be a custom scripts folder with T1mapping_fit.m -# control variables +# FSL, Matlab, ROCKETSHIP, ANTS, Python, and a BIDS compliant dataset are required +# variables +COMPARISON_MODE=0 EN_Z_NORM=0 EN_BIAS1=0 EN_BIAS2=0 -ff=0 +EN_MOTION_CORR=0 +T1_ONLY=0 +USE_AUTO_AIF=0 +AIF_SUFFIX="desc-AIF_mask" +AIF_TRAINING_SUFFIX="desc-trainingAIF_mask" +SKIP_IF_SUCCESS=0 +SCRIPT_LOOP_DIRS=sub-*/ses-* +AUTOAIF_WEIGHT_PATH="docker/files/model_weight_huber1.h5" +AUTOAIF_MODEL="best" + +# internal vars (don't change) fail=0 +failures=0 clean=0 -#EN_MOTION_CORR=1 - -# make this your main data directory or pass it as an option to -d -#DATA_DIR=/media/network_mriphysics/USC-PPG/data +count=0 +current=0 +ETA=0 +mETA=0 +prog=0 +successes=0 # options -while getopts ":d:bBZFhc" options; do +while getopts ":d:bBa:A:ZfhcC:mMstl:T:w:" options; do case "${options}" in + a) + AIF_SUFFIX=${OPTARG} + ;; + A) + AUTO_AIF_PATH=$(find $HOME -wholename '*main_vif.py' -printf '%h\n' -quit || find / -name '*main_vif.py' -printf '%h\n' -quit) &> /dev/null + case "${OPTARG}" in + M) + USE_AUTO_AIF=0 + ;; + A) + USE_AUTO_AIF=1 + ;; + T) + USE_AUTO_AIF=2 + ;; + *) + echo "Invalid argument for -A. Use A, M, or T." + exit 1 + ;; + esac + ;; b) EN_BIAS1=1 ;; - B) EN_BIAS2=1 + B) + EN_BIAS2=1 + ;; + C) + COMPARISON_MODE=1 + OUTPUT_DIR=${OPTARG} ;; c) clean=1 ;; d) DATA_DIR=${OPTARG} - ;; - F) - ff=1 + if [ ${DATA_DIR::-1} == "/" ] + then + DATA_DIR=${DATA_DIR::-1} + fi + DATE=$(date +%Y-%m-%d) + # derivatives dir is up 1 level from data dir + DERIV_DIR=$(dirname $DATA_DIR)/derivatives + if [ ! -d "$DERIV_DIR" ] + then + mkdir -p "$DERIV_DIR" + fi + # make log directory if it doesn't exist + if [ ! -d "$DERIV_DIR/logs" ] + then + mkdir -p "$DERIV_DIR/logs" + fi + LOG_FILE=$DERIV_DIR/logs/preprocessing_log_$DATE.txt + # write command to log file + echo "Command: $0 $@" > $LOG_FILE ;; h) echo "This script runs through all subject folders of a specified main data directory, preprocessing every folder ending in '_timepoint'." echo "The output is the DCE input, which are the corrected dynamic images, brain mask, T1 maps." + echo "-a: specify AIF suffix (default is 'desc-AIF_mask'). .nii.gz will be appended to the suffix." + echo "-A: enable AutoAIF with argument A (All automatic), M (Manual if available), or T (Manual + Training if available)" echo "-b: enable first round of bias field corrections" echo "-B: enable second round of bias field corrections, post-Z-norm if enabled" - echo "-c: clean generated files prior to processing" - echo "-Z: enable Z-slice normalization" - echo "-d: specify main data directory containing all subject folders" - echo "-F: fail fast, any command failures will end the script" + echo "-c: clean case's derivative folder prior to processing, ensures \"fresh\" runs but cannot use skips" + echo "-C [name]: enable comparison mode, which will output all files to the specified directory within each timepoint" + echo "-d [dir_path]: specify BIDS compliant data directory containing all subject folders (sub-*/ses-*/anat|dce/*.nii|*.json)" echo "-h: display this message" + echo "-m: enable motion correction" + echo "-s: skip preprocessing if DCE input file already exists" + echo "-T [dir_path]: target the subject(s)/session(s) to run (default is 'sub-*/ses-*/')" + echo "-t: only run up to T1 mapping" + echo "-w [path]: specify the path to the AutoAIF weights file" + echo "-Z: enable Z-slice normalization" exit 0 ;; + l) + INPUT_LIST=$DATA_DIR/../code/${OPTARG} + ;; + m) + EN_MOTION_CORR=1 + ;; + M) + AUTOAIF_MODEL=${OPTARG} + ;; + s) + SKIP_IF_SUCCESS=1 + ;; + S) + SCRIPT_LOOP_DIRS=${OPTARG} + ;; + t) + T1_ONLY=1 + ;; + w) + AUTOAIF_WEIGHT_PATH=${OPTARG} + ;; Z) EN_Z_NORM=1 ;; @@ -55,467 +138,719 @@ done if [ -z "$DATA_DIR" ] then - echo "ERROR: Please use '-d [dir_path]' to pass the path to your main data directory to this script." + echo "ERROR: Please use '-d [dir_path]' to pass the path to your BIDS compliant data directory to this script." exit 1 fi -cd $DATA_DIR + if [[ "$OSTYPE" == "linux-gnu" ]]; then - ROCKETSHIP_PATH=$(find $HOME -name '*run_dce_auto.m' -printf '%h\n' -quit) - SCRIPT_PATH=$(find $HOME -name '*auto_analysis.py' -printf '%h\n' -quit) - GPUFIT_PATH=$(find $HOME -name 'GpufitConstrainedMex.mexa64' -printf '%h\n' -quit || find / -name 'GpufitConstrainedMex.mexa64' -printf '%h\n' -quit) - GPUFIT_M_PATH=$(find $HOME -name 'ModelID.m' -printf '%h\n' -quit || find / -name 'ModelID.m' -printf '%h\n' -quit) + ROCKETSHIP_PATH=$(find $HOME -name '*run_dce_cli.m' -printf '%h\n' -quit || find / -name '*run_dce_cli.m' -printf '%h\n' -quit) &> /dev/null + SCRIPT_PATH=$(dirname "$(realpath $0)") + GPUFIT_PATH=$(find $HOME -name 'GpufitCudaAvailableMex.mexa64' -printf '%h\n' -quit || find / -name 'GpufitCudaAvailableMex.mexa64' -printf '%h\n' -quit) &> /dev/null + GPUFIT_M_PATH=$(find $HOME -name 'ModelID.m' -printf '%h\n' -quit || find / -name 'ModelID.m' -printf '%h\n' -quit) &> /dev/null else ROCKETSHIP_PATH=$(find $HOME -type d -name ROCKETSHIP) SCRIPT_PATH=$(find $HOME -type d -name in-house_toolbox) - GPUFIT_PATH=$(find $HOME -type d -name Gpufit-build)/matlab + GPUFIT_PATH=$(find $HOME -type d -name Gpufit-build) fi +cd $DATA_DIR || exit 1 + +# count timepoints +for source_dir in $DATA_DIR/$SCRIPT_LOOP_DIRS; do + ((count++)) +done +# Function to calculate and display progress and estimated remaining time +function show_progress { + local current_iteration=$1 + local start_time=$2 + local total_iterations=$3 + local runtime=$4 + + # Calculate elapsed time + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) -# Run bias correction on VFA data + # Calculate estimated total time + estimated_total_time=$((runtime * total_iterations)) + + # Calculate remaining time + remaining_time=$((estimated_total_time - elapsed_time)) + + # Display progress and estimated remaining time + echo -ne "Progress: $((elapsed_time * 100 / estimated_total_time))% - " + echo -ne "Elapsed time: $(($elapsed_time / 60))m $(($elapsed_time % 60))s - " + if [ $remaining_time -lt 0 ] + then + echo -ne "Estimated remaining time: calculating...\r" + else + echo -ne "Estimated remaining time: $(($remaining_time / 60))m $(($remaining_time % 60))s\r" + fi +} +# Run bias correction on VFA data # ------------------------------ -for dir in */*_timepoint/; do - date - echo Preprocessing ${dir}... - SUBJECT_TP_PATH=$(realpath $dir) - cd $dir +for source_dir in $DATA_DIR/$SCRIPT_LOOP_DIRS; do + start_time=$(date +%s) + if [ ${source_dir::-1} == "/" ]; then + source_dir=$DATA_DIR/${source_dir::-1} + fi + date >> $LOG_FILE + echo "Preprocessing ${source_dir}..." + ((current++)) + + # get subject ID and session + SUBJECT=$(echo $source_dir | grep -o 'sub-[^/]*') + SESSION=$(echo $source_dir | grep -o 'ses-[0-9]*') + PREFIX=${SUBJECT}_${SESSION} + + if [ $COMPARISON_MODE -eq 1 ] + then + if [ ! -d "$DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION" ] + then + echo "Comparison mode enabled. Creating output directory $OUTPUT_DIR..." >> $LOG_FILE + mkdir -p $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce + fi + if [ ! $USE_AUTO_AIF -eq 1 ] + then + mask_copied=0 + cp $DERIV_DIR/dceprep-manualAIF_refresh/$SUBJECT/$SESSION/dce/*$AIF_SUFFIX* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/ && mask_copied=1 + # cp $DERIV_DIR/dceprep/$SUBJECT/$SESSION/dce/*$AIF_TRAINING_SUFFIX* $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/ && mask_copied=1 + # Extract the session number from the session string + session_num=$(echo $SESSION | grep -o '[0-9]\+') + # turn sub-* into * + pat=${SUBJECT#sub-} + # Map the session number to the corresponding session string + case $session_num in + 01) session_str="1st" ;; + 02) session_str="2nd" ;; + 03) session_str="3rd" ;; + *) session_str="" ;; + esac + # If the session string is not empty, copy the masks + if [ $mask_copied -eq 0 ] && [[ -n $session_str ]]; then + echo "Copying masks for $SUBJECT $SESSION..." >> $LOG_FILE + cp /media/network_mriphysics/USC-PPG/AI_training/loos_model/test/masks/sub-${pat}_ses-${session_num}_desc-AIF_mask.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${SUBJECT}_${SESSION}_${AIF_TRAINING_SUFFIX}.nii.gz && mask_copied=1 + cp /media/network_mriphysics/USC-PPG/AI_training/loos_model/test/masks/${pat}_${session_str}_timepoint.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${SUBJECT}_${SESSION}_${AIF_TRAINING_SUFFIX}.nii.gz && mask_copied=1 + cp /media/network_mriphysics/USC-PPG/AI_training/loos_model/train/masks/sub-${pat}_ses-${session_num}_desc-AIF_mask.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${SUBJECT}_${SESSION}_${AIF_TRAINING_SUFFIX}.nii.gz && mask_copied=1 + cp /media/network_mriphysics/USC-PPG/AI_training/loos_model/train/masks/${pat}_${session_str}_timepoint.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${SUBJECT}_${SESSION}_${AIF_TRAINING_SUFFIX}.nii.gz && mask_copied=1 + cp /media/network_mriphysics/USC-PPG/AI_training/loos_model/val/masks/sub-${pat}_ses-${session_num}_desc-AIF_mask.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${SUBJECT}_${SESSION}_${AIF_TRAINING_SUFFIX}.nii.gz && mask_copied=1 + cp /media/network_mriphysics/USC-PPG/AI_training/loos_model/val/masks/${pat}_${session_str}_timepoint.nii.gz $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${SUBJECT}_${SESSION}_${AIF_TRAINING_SUFFIX}.nii.gz && mask_copied=1 + fi + if [ $mask_copied -eq 0 ] && [ ! -f $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${PREFIX}_${AIF_SUFFIX}.nii.gz ] && [ ! -f $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/dce/${PREFIX}_${AIF_TRAINING_SUFFIX}.nii.gz ] && [ $USE_AUTO_AIF -eq 2 ] + then + echo "No AIF file found for $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION/. Skipping timepoint..." >> $LOG_FILE + rm -rf $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION + if [ -z "$(ls -A $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT)" ] + then + rm -rf $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT + fi + cd $DATA_DIR + continue + fi + fi + cd $DERIV_DIR/dceprep-$OUTPUT_DIR/$SUBJECT/$SESSION || exit 1 + else + mkdir -p $DERIV_DIR/dceprep/$SUBJECT/$SESSION + cd $DERIV_DIR/dceprep/$SUBJECT/$SESSION || exit 1 + fi + SUBJECT_TP_PATH=$(pwd) - if [ ! -f "2.nii" ] || [ ! -f "5.nii" ] || [ ! -f "10.nii" ] || [ ! -f "12.nii" ] || [ ! -f "15.nii" ] || [ ! -f "DCE.nii" ] + # --- LOCKING FOR MULTI-MACHINE PROCESSING --- + LOCKFILE="preprocessing_lock.txt" + LOCKDIR=$(pwd) + LOCKPATH="$LOCKDIR/$LOCKFILE" + LOCKHOST=$(hostname) + LOCKPID=$$ + LOCKLIST="$DERIV_DIR/locks_${LOCKHOST}.txt" + echo "Attempting to lock $LOCKPATH on $LOCKHOST with PID $LOCKPID" + + # Try to create lock file atomically + if ( set -o noclobber; echo "$LOCKHOST:$LOCKPID" > "$LOCKPATH" ) 2> /dev/null; then + echo "$LOCKPATH" >> "$LOCKLIST" + trap 'for f in $(cat "$LOCKLIST" 2>/dev/null); do rm -f "$f"; done; rm -f "$LOCKLIST"; exit $?' INT TERM EXIT + else + echo "Skipping $dir because it is currently being processed by $(cat $LOCKPATH)." >> $LOG_FILE + cd $DERIV_DIR + continue + fi + + if [ ! -f $DERIV_DIR/dceprep/$SUBJECT/$SESSION/dce/${PREFIX}_${AIF_SUFFIX}.nii.gz ] && [ ! -f $DERIV_DIR/dceprep/$SUBJECT/$SESSION/dce/${PREFIX}_${AIF_SUFFIX}.nii ] && [ ! -f $DERIV_DIR/dceprep-manualAIF/$SUBJECT/$SESSION/dce/${PREFIX}_${AIF_TRAINING_SUFFIX}.nii.gz ] && [ $USE_AUTO_AIF -eq 0 ] then - echo Base files missing! Skipping timepoint... - cd ../.. + echo "No ${PREFIX}_${AIF_SUFFIX} file found for $DERIV_DIR/dceprep/$SUBJECT/$SESSION/. Skipping timepoint..." >> $LOG_FILE + cd $DATA_DIR continue fi - + + if [ $SKIP_IF_SUCCESS -eq 1 ] + then + if [ -f "dce/${PREFIX}_desc-bfcz_DCE.nii.gz" ] && [ -f "anat/${PREFIX}_space-DCEref_desc-brain_mask.nii.gz" ] && \ + [ -f "dce/${PREFIX}_desc-AIF_T1map.nii.gz" ] && [ -f "anat/${PREFIX}_space-DCEref_T1map.nii" ] #&& [ -f "reports/${PREFIX}_desc-casereport.html" ] + then + echo "Skipping ${source_dir} because it has already been processed." >> $LOG_FILE + let successes++ + cd $DATA_DIR + continue + fi + fi + + # get list of VFAs and sort them + VFA_LIST=($(ls $source_dir/anat/*.nii* | grep -v "$source_dir/anat/*T1w.nii*" | grep -v "$source_dir/dce/*aif.nii*")) + VFA_LIST=($(printf '%s\n' "${VFA_LIST[@]}" | grep -o -E 'flip-[0-9]+' | sort -n)) + echo "Found ${#VFA_LIST[@]} VFAs: ${VFA_LIST[@]}" + VFA_NUMS=($(printf '%s\n' "${VFA_LIST[@]}" | grep -o -E 'flip-[0-9]+' | grep -o -E '[0-9]+')) + + if [ ${#VFA_LIST[@]} -eq 0 ] + then + echo "$source_dir No VFAs found! Skipping timepoint..." >> $LOG_FILE + cd $DATA_DIR + continue + fi + + if [ ! -f "$source_dir/dce/${PREFIX}_DCE.nii.gz" ] || [ ! -f "$source_dir/anat/${PREFIX}_T1w.nii.gz" ] + then + missing_files="" + [ ! -f "$source_dir/dce/${PREFIX}_DCE.nii.gz" ] && missing_files+=" $source_dir/dce/${PREFIX}_DCE.nii.gz" + [ ! -f "$source_dir/anat/${PREFIX}_T1w.nii.gz" ] && missing_files+=" $source_dir/anat/${PREFIX}_T1w.nii.gz" + + echo "$source_dir Base file(s) missing! Missing file(s):$missing_files. Skipping timepoint..." >> "$LOG_FILE" + cd $DATA_DIR + continue + fi + + mkdir dce &> /dev/null if [ $clean -eq 1 ] then - rm !(2.nii|5.nii|10.nii|12.nii|15.nii|DCE.nii|aif.nii) + echo Cleaning folder... $PWD + # remove all files except for the AIF + rm -rf anat figures reports + cd dce + rm -f !(${PREFIX}_${AIF_SUFFIX}.nii.gz|${PREFIX}_${AIF_TRAINING_SUFFIX}.nii.gz) + cd $SUBJECT_TP_PATH fi - - # FSL brain mask extraction from VFA 2 image - bet 2.nii brain.nii -R -m -f 0.45 -g 0 -Z - fslcpgeom 2.nii brain_mask.nii - - # FAST documentation recommends brain masking first - #fslmaths 2.nii -mas brain_mask.nii.gz 2_masked.nii - #fslmaths 5.nii -mas brain_mask.nii.gz 5_masked.nii - #fslmaths 10.nii -mas brain_mask.nii.gz 10_masked.nii - #fslmaths 12.nii -mas brain_mask.nii.gz 12_masked.nii - #fslmaths 15.nii -mas brain_mask.nii.gz 15_masked.nii - cp 2.nii 2_masked.nii - cp 5.nii 5_masked.nii - cp 10.nii 10_masked.nii - cp 12.nii 12_masked.nii - cp 15.nii 15_masked.nii - gzip -f *_masked.nii - - if [ $EN_BIAS1 -eq 1 ] - then - if [ ! -f "15_bfc.nii" ] + mkdir anat &> /dev/null + + # HD-BET brain extraction & segmentations from MP-RAGE + SECONDS=0 + echo -ne "HD-BET MP-RAGE [ ] $prog% ($current/$count) Calculating runtime... \r" + + if [ ! -f "anat/${PREFIX}_desc-brain_mask.nii.gz" ] && [ -f "$source_dir/anat/${PREFIX}_T1w.nii.gz" ] then - echo Bias field correction with FAST - # don't forget to remove all unnecessary images - fast -t 3 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 2_masked.nii - rm 2_masked_mixeltype.nii.gz - rm 2_masked_pve_0.nii.gz - rm 2_masked_pve_1.nii.gz - rm 2_masked_pve_2.nii.gz - rm 2_masked_pveseg.nii.gz - rm 2_masked_seg.nii.gz - 3dcalc -a 2_masked.nii -b 2_masked_bias.nii.gz -expr a/b -prefix 2_bfc.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 5_masked.nii - rm 5_masked_mixeltype.nii.gz - rm 5_masked_pve_0.nii.gz - rm 5_masked_pve_1.nii.gz - rm 5_masked_pve_2.nii.gz - rm 5_masked_pveseg.nii.gz - rm 5_masked_seg.nii.gz - 3dcalc -a 5_masked.nii -b 5_masked_bias.nii.gz -expr a/b -prefix 5_bfc.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 10_masked.nii - rm 10_masked_mixeltype.nii.gz - rm 10_masked_pve_0.nii.gz - rm 10_masked_pve_1.nii.gz - rm 10_masked_pve_2.nii.gz - rm 10_masked_pveseg.nii.gz - rm 10_masked_seg.nii.gz - 3dcalc -a 10_masked.nii -b 10_masked_bias.nii.gz -expr a/b -prefix 10_bfc.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 12_masked.nii - rm 12_masked_mixeltype.nii.gz - rm 12_masked_pve_0.nii.gz - rm 12_masked_pve_1.nii.gz - rm 12_masked_pve_2.nii.gz - rm 12_masked_pveseg.nii.gz - rm 12_masked_seg.nii.gz - 3dcalc -a 12_masked.nii -b 12_masked_bias.nii.gz -expr a/b -prefix 12_bfc.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 15_masked.nii - rm 15_masked_mixeltype.nii.gz - rm 15_masked_pve_0.nii.gz - rm 15_masked_pve_1.nii.gz - rm 15_masked_pve_2.nii.gz - rm 15_masked_pveseg.nii.gz - #rm 15_masked_seg.nii.gz - 3dcalc -a 15_masked.nii -b 15_masked_bias.nii.gz -expr a/b -prefix 15_bfc.nii -overwrite + if [ nvidia-smi ] + then + hd-bet -i $source_dir/anat/${PREFIX}_T1w.nii.gz -o anat/${PREFIX}_desc-brain.nii.gz --save_bet_mask &> /dev/null + mETA=$(echo "scale=0; $SECONDS * 34 * ($count - $current + 1) / 60" | bc -l) else - echo Found BFC VFAs. Skipping BFC... + hd-bet -i $source_dir/anat/${PREFIX}_T1w.nii.gz -o anat/${PREFIX}_desc-brain.nii.gz -device cpu --save_bet_mask &> /dev/null + mETA=$(echo "scale=0; $SECONDS * 2 * ($count - $current + 1) / 60" | bc -l) fi - # threshold and binarize wm mask - fslmaths 15_masked_seg.nii.gz -thr 3 -uthr 3 15_wm.nii - - # apply wm mask to all VFAs - fslmaths 2_bfc.nii -mas 15_wm.nii.gz 2_bfc_wm.nii - fslmaths 5_bfc.nii -mas 15_wm.nii.gz 5_bfc_wm.nii - fslmaths 10_bfc.nii -mas 15_wm.nii.gz 10_bfc_wm.nii - fslmaths 12_bfc.nii -mas 15_wm.nii.gz 12_bfc_wm.nii - fslmaths 15_bfc.nii -mas 15_wm.nii.gz 15_bfc_wm.nii + mv anat/${PREFIX}_desc-brain_bet.nii.gz anat/${PREFIX}_desc-brain_mask.nii.gz + mv anat/${PREFIX}_desc-brain.nii.gz anat/${PREFIX}_desc-brain_T1w.nii.gz + elif [ -f "$source_dir/anat/${PREFIX}_T2w.nii.gz" ] + then + # assume mouse + # we gotta do some orientation shenanigans + echo + fi + prog=$(echo "scale=2; $prog + 3.33 / $count" | bc -l) + + # Motion correction of dynamic images to 2nd rep using FSL + # ------------------------------ + if [ $EN_MOTION_CORR -eq 1 ] + then + if [ ! -f "dce/${PREFIX}_desc-hmc_DCE.nii.gz" ] + then + mcflirt -in $source_dir/dce/${PREFIX}_DCE.nii.gz -refvol 1 -cost mutualinfo -report -plots -o dce/${PREFIX}_desc-hmc_DCE.nii &> /dev/null + if [ ! -f "dce/${PREFIX}_desc-hmc_DCE.nii.gz" ] + then + echo $SUBJECT_TP_PATH/dce "Missing motion corrected DCE file." >> $LOG_FILE + cd $DATA_DIR + fail=1 + continue + fi + fi + # mkdir -p $source_dir/figures &> /dev/null + mkdir -p figures &> /dev/null + max=$(python3 $SCRIPT_PATH/max_disp.py $SUBJECT_TP_PATH/dce ${PREFIX}) + echo -e "$max" > dce/${PREFIX}_desc-hmc_maxdisp.txt + fslmerge -n 1 dce/${PREFIX}_desc-hmc_DCEref.nii dce/${PREFIX}_desc-hmc_DCE.nii.gz &> /dev/null + DCE_REF_VOL=dce/${PREFIX}_desc-hmc_DCEref.nii.gz + else + # cp $source_dir/DCE.nii.gz DCE_mc.nii.gz + fslmerge -n 1 dce/${PREFIX}_DCEref.nii $source_dir/dce/${PREFIX}_DCE.nii &> /dev/null + DCE_REF_VOL=dce/${PREFIX}_DCEref.nii.gz + # gunzip -f $source_dir/ref_rep_noMC.nii.gz + fi + + REF_SPACE=space-DCEref + if [ ! -f anat/${PREFIX}_from-T1w_to-DCEref.mat ] && [ $EN_MOTION_CORR -eq 1 ] + then + # MPRAGE -> dynamic registration + antsRegistration --verbose 0 --dimensionality 3 --float 0 \ + --collapse-output-transforms 1 --output [ anat/${PREFIX}_${REF_SPACE}_T1w,anat/${PREFIX}_${REF_SPACE}_T1w.nii.gz ] \ + --interpolation Linear --use-histogram-matching 0 --winsorize-image-intensities [ 0.005,0.995 ] \ + --transform Rigid[ 0.1 ] --metric MI[ $DCE_REF_VOL,${source_dir}/anat/${PREFIX}_T1w.nii.gz,1,32,Regular,0.25 ] \ + --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + mv anat/${PREFIX}_${REF_SPACE}_T1w0GenericAffine.mat anat/${PREFIX}_from-T1w_to-DCEref.mat + structural_to_DCEref=anat/${PREFIX}_from-T1w_to-DCEref.mat + elif [ $EN_MOTION_CORR -eq 0 ] + then + antsRegistration --verbose 0 --dimensionality 3 --float 0 \ + --collapse-output-transforms 1 --output [ anat/${PREFIX}_${REF_SPACE}_T1w,anat/${PREFIX}_${REF_SPACE}_T1w.nii.gz ] \ + --interpolation Linear --use-histogram-matching 0 --winsorize-image-intensities [ 0.005,0.995 ] \ + --transform Rigid[ 0.1 ] --metric MI[ $DCE_REF_VOL,${source_dir}/anat/${PREFIX}_T1w.nii.gz,1,32,Regular,0.25 ] \ + --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + mv anat/${PREFIX}_${REF_SPACE}_T1w0GenericAffine.mat anat/${PREFIX}_from-T1w_to-DCEref.mat + structural_to_DCEref=anat/${PREFIX}_from-T1w_to-DCEref.mat + fi + T1w_to_DCEref=anat/${PREFIX}_from-T1w_to-DCEref.mat + # VFA -> dynamic registration + VFA_reg() { + local VFA=$1 + antsRegistration --verbose 0 --dimensionality 3 --float 0 \ + --collapse-output-transforms 1 --output [ anat/${PREFIX}_flip-${VFA}_${REF_SPACE},anat/${PREFIX}_flip-${VFA}_${REF_SPACE}_VFA.nii.gz ] \ + --interpolation Linear --use-histogram-matching 0 --winsorize-image-intensities [ 0.005,0.995 ] \ + --transform Rigid[ 0.1 ] --metric MI[ $DCE_REF_VOL,$source_dir/anat/${PREFIX}_flip-${VFA}_VFA.nii.gz,1,32,Regular,0.25 ] \ + --convergence [ 1000x500x250x100,1e-6,10 ] --shrink-factors 12x8x4x2 --smoothing-sigmas 4x3x2x1vox + # mv anat/${VFA}_dynWarped.nii.gz anat/${VFA}_dyn.nii.gz + # TODO: re-register with fewer iterations if any slice is empty or bad + mv anat/${PREFIX}_flip-${VFA}_${REF_SPACE}0GenericAffine.mat anat/${PREFIX}_from-VFA${VFA}_to-DCEref.mat + } + + for VFA in "${VFA_NUMS[@]}"; do + if [ ! -f "anat/${PREFIX}_flip-${VFA}_VFA.nii.gz" ] + then + VFA_reg "$VFA" & + fi + done + wait + # make array of VFA dynamic images + VFA_DYN_LIST=($(ls -1 anat/${PREFIX}_flip-*.nii*)) + VFA_DYN_LIST=($(printf '%s\n' "${VFA_DYN_LIST[@]}" | grep -o -E 'flip-[0-9]+*'| sort -n | uniq)) + + # logic for dealing with empty slices due to registration + # check slices of each VFA, discard empty slices from ALL images + for VFA in "${VFA_DYN_LIST[@]}"; do + fslslice anat/${PREFIX}_${VFA}_${REF_SPACE}_VFA.nii.gz anat/${PREFIX}_${VFA}_${REF_SPACE}_VFA + done + + # check if any slices are empty + EMPTY_SLICES=0 + problem_slice=0 + for slice in $(ls -1 anat/${PREFIX}_${VFA}_${REF_SPACE}_VFA*.nii*); do + if [ $(fslstats $slice -V | awk '{print $1}') -lt 100 ] + then + rm $slice + # remove corresponding slice from all VFAs + slice=$(echo $slice | grep -o -E '_[0-9]+.nii' | grep -o -E '[0-9]+') + rm anat/${PREFIX}_flip-*_${REF_SPACE}_VFA_slice_$slice.nii* + problem_slice=$slice + EMPTY_SLICES=1 + echo "Removing empty slice $slice from all images" >> $LOG_FILE + fi + done + + if [ $EMPTY_SLICES -eq 1 ] + then + anat_files=$(ls anat/*${REF_SPACE}*.nii* | grep -v "slice") + echo $anat_files + for registered_img in $anat_files; do + echo "Re-merging $registered_img" + reg_img_no_ext=${registered_img%.nii*} + reg_img_no_ext=${reg_img_no_ext%_slice_*} + fslslice $registered_img $reg_img_no_ext + rm ${reg_img_no_ext}_slice_$problem_slice.nii* + fslmerge -z $registered_img ${reg_img_no_ext}_slice_*.nii* &> /dev/null + done + dce_files=$(ls dce/*.nii* | grep -v "slice" | grep -v ".par") + for img in $dce_files; do + img_no_ext=${img%.nii*} + img_no_ext=${img_no_ext%_slice_*} + fslslice $img $img_no_ext + rm ${img_no_ext}_slice_$problem_slice.nii* + fslmerge -z $img ${img_no_ext}_slice_*.nii* &> /dev/null + done + # re-merge VFAs + for VFA in "${VFA_DYN_LIST[@]}"; do + fslmerge -z anat/${PREFIX}_${VFA}_${REF_SPACE}_VFA.nii.gz anat/${PREFIX}_${VFA}_${REF_SPACE}_VFA_slice_*.nii* &> /dev/null + done + rm anat/*slice_*.nii* dce/*slice_*.nii* + else + rm anat/*slice_*.nii* + fi + + if [ ! -f anat/${PREFIX}_label-WM_mask.nii.gz ] + then + echo -ne "T1 SEG w/ FAST [=> ] $prog% ($current/$count) ~$mETA min remaining \r" + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b --nopve -g -o anat/${PREFIX}_label- anat/${PREFIX}_desc-brain_T1w.nii.gz + # rename segmented files + mv anat/${PREFIX}_label-_bias.nii.gz anat/${PREFIX}_desc-bias_T1w.nii.gz + mv anat/${PREFIX}_label-_seg_0.nii.gz anat/${PREFIX}_label-CSF_mask.nii.gz + mv anat/${PREFIX}_label-_seg_1.nii.gz anat/${PREFIX}_label-GM_mask.nii.gz + mv anat/${PREFIX}_label-_seg_2.nii.gz anat/${PREFIX}_label-WM_mask.nii.gz + rm anat/${PREFIX}_label-_seg.nii.gz + antsApplyTransforms -i anat/${PREFIX}_label-WM_mask.nii.gz -r $DCE_REF_VOL -t $structural_to_DCEref -o anat/${PREFIX}_${REF_SPACE}_label-WM_mask.nii.gz &> /dev/null + ETA=$(echo "scale=0; $mETA - ($SECONDS/60)" | bc -l) + #ETA=$(echo "scale=0; $mETA - $mETA * .0667" | bc -l) + prog=$(echo "scale=2; $prog + 6.67 / $count" | bc -l) + fi + + fslmaths anat/${PREFIX}_${REF_SPACE}_label-WM_mask.nii.gz -thr 0.9 -bin anat/${PREFIX}_${REF_SPACE}_label-WM_mask.nii.gz &> /dev/null + # copy VFA files to _masked.nii + for VFA in "${VFA_DYN_LIST[@]}"; do + # get VFA number + # VFA_NUM=$(echo $VFA | grep -o '[0-9]*') + # FAST documentation recommends brain masking first + # fslmaths $VFA -mas T1_bet_mask.nii.gz ${VFA_NUM}_masked.nii + cp anat/${PREFIX}_${VFA}_${REF_SPACE}_VFA.nii.gz anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-brain_VFA.nii.gz + done + # gzip -f *_masked.nii + + if [ $EN_BIAS1 -eq 1 ] && [ ! -f "anat/${PREFIX}_${VFA_LIST[0]}_${REF_SPACE}_desc-bfc_VFA.nii.gz" ] + then + VFA_FAST () { + local VFA=$1 + # VFA_NUM=$(echo $VFA | grep -o '[0-9]*') + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -B --nopve -o anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-brain_VFA.nii.gz + # ETA=$(echo "scale=0; $mETA - ($SECONDS)/60" | bc -l) + # prog=$(echo "scale=2; $prog + 6 / $count" | bc -l) + # echo -ne "BFC FAST VFA${VFA_NUM} [=======> ] $prog% ($current/$count) ~$ETA min remaining \r" + mv anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-brain_VFA_restore* anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-bfc_VFA.nii.gz + # rm ${VFA}_masked_[mps]* + + # apply wm mask to all VFAs + fslmaths anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-bfc_VFA.nii.gz -mas anat/${PREFIX}_${REF_SPACE}_label-WM_mask.nii.gz anat/${PREFIX}_${VFA}_${REF_SPACE}_seg-WM_VFA.nii.gz + } + + #echo "Bias field correction with FAST" + # FAST every VFA + echo -ne "BFC FAST VFAS [=======> ] $prog% ($current/$count) ~$ETA min remaining \r" + for VFA in "${VFA_DYN_LIST[@]}"; do + echo "BFC FAST $VFA" + VFA_FAST "$VFA" & + done + wait + echo -ne "Z NORM VFAS [================> ] $prog% ($current/$count) ~$ETA min remaining \r" + rm -f $source_dir/[0-9]*_masked_[mps]* else # dumb file name management for norm only runs - fslmaths 2.nii -mas brain_mask.nii.gz 2_bfc.nii - fslmaths 5.nii -mas brain_mask.nii.gz 5_bfc.nii - fslmaths 10.nii -mas brain_mask.nii.gz 10_bfc.nii - fslmaths 12.nii -mas brain_mask.nii.gz 12_bfc.nii - fslmaths 15.nii -mas brain_mask.nii.gz 15_bfc.nii - - echo Skipping BFC... but still segmenting one VFA for matter masks - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 15_bfc.nii - rm 15_bfc_mixeltype.nii.gz - rm 15_bfc_pve_0.nii.gz - rm 15_bfc_pve_1.nii.gz - rm 15_bfc_pve_2.nii.gz - rm 15_bfc_pveseg.nii.gz - #rm 15_bfc_seg.nii.gz - - # threshold and binarize wm mask - fslmaths 15_bfc_seg.nii.gz -thr 3 -uthr 3 15_wm.nii + # fslmaths 2.nii -mas T1_bet_mask.nii.gz 2_bfc.nii &> /dev/null + # fslmaths 5.nii -mas T1_bet_mask.nii.gz 5_bfc.nii &> /dev/null + # fslmaths 10.nii -mas T1_bet_mask.nii.gz 10_bfc.nii &> /dev/null + # fslmaths 12.nii -mas T1_bet_mask.nii.gz 12_bfc.nii &> /dev/null + # fslmaths 15.nii -mas T1_bet_mask.nii.gz 15_bfc.nii &> /dev/null # apply wm mask to all VFAs - fslmaths 2_bfc.nii -mas 15_wm.nii.gz 2_bfc_wm.nii - fslmaths 5_bfc.nii -mas 15_wm.nii.gz 5_bfc_wm.nii - fslmaths 10_bfc.nii -mas 15_wm.nii.gz 10_bfc_wm.nii - fslmaths 12_bfc.nii -mas 15_wm.nii.gz 12_bfc_wm.nii - fslmaths 15_bfc.nii -mas 15_wm.nii.gz 15_bfc_wm.nii + # fslmaths 2_bfc.nii -mas T1_wm_mask.nii.gz 2_bfc_wm.nii &> /dev/null + # fslmaths 5_bfc.nii -mas T1_wm_mask.nii.gz 5_bfc_wm.nii &> /dev/null + # fslmaths 10_bfc.nii -mas T1_wm_mask.nii.gz 10_bfc_wm.nii &> /dev/null + # fslmaths 12_bfc.nii -mas T1_wm_mask.nii.gz 12_bfc_wm.nii &> /dev/null + # fslmaths 15_bfc.nii -mas T1_wm_mask.nii.gz 15_bfc_wm.nii &> /dev/null + + # apply MP-RAGE wm mask + for VFA in "${VFA_LIST[@]}"; do + # VFA_NUM=$(echo $VFA | grep -o '[0-9]*') + fslmaths anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-brain_VFA.nii.gz -mas anat/${PREFIX}_${REF_SPACE}_label-WM_mask.nii.gz anat/${PREFIX}_${VFA}_${REF_SPACE}_seg-WM_VFA.nii.gz &> /dev/null + done fi # Run Z-axis normalization VFA data # ------------------------------ - if [ $EN_Z_NORM -eq 1 ] - then - echo begin slice normalization - python3 $SCRIPT_PATH/VFA_norm.py $SUBJECT_TP_PATH - fi - - if [ $ff -eq 1 ] + if [ $EN_Z_NORM -eq 1 ] then - if [ ! -f "15_BFC_Z.nii" ] - then - echo "Missing Z-normalized files. Z-norm likely failed due to non-existent inputs." - fail=1 - cd ../.. - continue - fi + if [ ! -f "anat/${PREFIX}_${VFA_LIST[0]}_${REF_SPACE}_desc-bfcz_VFA.nii.gz" ] + then + #echo begin slice normalization + python3 $SCRIPT_PATH/VFA_norm.py $SUBJECT_TP_PATH/anat $PREFIX $EN_BIAS1 &> /dev/null + prog=$(echo "scale=2; $prog + .33 / $count" | bc -l) + echo -ne "VFA MOTIONCORR [===================> ] $prog% ($current/$count) ~$ETA min remaining \r" + fi + if [ ! -f "anat/${PREFIX}_${VFA_LIST[0]}_${REF_SPACE}_desc-bfcz_VFA.nii.gz" ] + then + echo $source_dir "Missing Z-normalized VFA files. Z-norm likely failed due to non-existent inputs." >> $LOG_FILE + cd $DATA_DIR + fail=1 + continue + fi + else + mkdir -p figures &> /dev/null fi - + + VFA_INPUT="" if [ $EN_BIAS2 -eq 1 ] then # 2nd Bias correction VFA data # ------------------------------ echo Begin second round of BFC # Bias field correction with FAST - # don't forget to remove all unnecessary images - fast -t 3 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 2_BFC_Z.nii - rm 2_BFC_Z_mixeltype.nii.gz - rm 2_BFC_Z_pve_0.nii.gz - rm 2_BFC_Z_pve_1.nii.gz - rm 2_BFC_Z_pve_2.nii.gz - rm 2_BFC_Z_pveseg.nii.gz - rm 2_BFC_Z_seg.nii.gz - 3dcalc -a 2_BFC_Z.nii -b 2_BFC_Z_bias.nii.gz -expr a/b -prefix 2_b2corr.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 5_BFC_Z.nii - rm 5_BFC_Z_mixeltype.nii.gz - rm 5_BFC_Z_pve_0.nii.gz - rm 5_BFC_Z_pve_1.nii.gz - rm 5_BFC_Z_pve_2.nii.gz - rm 5_BFC_Z_pveseg.nii.gz - rm 5_BFC_Z_seg.nii.gz - 3dcalc -a 5_BFC_Z.nii -b 5_BFC_Z_bias.nii.gz -expr a/b -prefix 5_b2corr.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 10_BFC_Z.nii - rm 10_BFC_Z_mixeltype.nii.gz - rm 10_BFC_Z_pve_0.nii.gz - rm 10_BFC_Z_pve_1.nii.gz - rm 10_BFC_Z_pve_2.nii.gz - rm 10_BFC_Z_pveseg.nii.gz - rm 10_BFC_Z_seg.nii.gz - 3dcalc -a 10_BFC_Z.nii -b 10_BFC_Z_bias.nii.gz -expr a/b -prefix 10_b2corr.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 12_BFC_Z.nii - rm 12_BFC_Z_mixeltype.nii.gz - rm 12_BFC_Z_pve_0.nii.gz - rm 12_BFC_Z_pve_1.nii.gz - rm 12_BFC_Z_pve_2.nii.gz - rm 12_BFC_Z_pveseg.nii.gz - rm 12_BFC_Z_seg.nii.gz - 3dcalc -a 12_BFC_Z.nii -b 12_BFC_Z_bias.nii.gz -expr a/b -prefix 12_b2corr.nii -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 15_BFC_Z.nii - rm 15_BFC_Z_mixeltype.nii.gz - rm 15_BFC_Z_pve_0.nii.gz - rm 15_BFC_Z_pve_1.nii.gz - rm 15_BFC_Z_pve_2.nii.gz - rm 15_BFC_Z_pveseg.nii.gz - rm 15_BFC_Z_seg.nii.gz - 3dcalc -a 15_BFC_Z.nii -b 15_BFC_Z_bias.nii.gz -expr a/b -prefix 15_b2corr.nii -overwrite - - # concatenates 5 images in one VFA.nii image - 3dTcat -prefix VFA.nii 2_b2corr.nii 5_b2corr.nii 10_b2corr.nii 12_b2corr.nii 15_b2corr.nii -overwrite + # don't forget to remove all unnecessary images + for VFA in "${VFA_LIST[@]}"; do + VFA_NUM=$(echo $VFA | grep -o '[0-9]*') + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -B --nopve -o ${VFA_NUM}_BFC_Z.nii ${VFA_NUM}_BFC_Z.nii &> /dev/null + # fslmaths ${VFA_NUM}_BFC_Z.nii -div ${VFA_NUM}_BFC_Z_bias.nii.gz ${VFA_NUM}_b2corr.nii &> /dev/null + mv ${VFA_NUM}_BFC_Z_restore* ${VFA_NUM}_bfc2.nii.gz + rm -f ${VFA_NUM}_BFC_Z_[mps]* &> /dev/null + done + # concatenates all VFA images in one 4D VFA.nii.gz image + fslmerge -t VFA.nii.gz ${VFA_NUMS[@]/%/_bfc2.nii.gz} &> /dev/null + + # remove all unnecessary images + rm [0-9]*_BFC_Z_* elif [ $EN_Z_NORM -eq 1 ] then - echo Concatenating Z-norm\'d images - # concatenates 5 images in one VFA.nii image - 3dTcat -prefix VFA.nii 2_BFC_Z.nii 5_BFC_Z.nii 10_BFC_Z.nii 12_BFC_Z.nii 15_BFC_Z.nii -overwrite - + #echo Concatenating Z-norm\'d images + # concatenates VFA images in one 4D VFA.nii.gz image + # fslmerge -t $source_dir/VFA_BFC_Z.nii.gz "${VFA_NUMS[@]/#/\/$source_dir\/}"_BFC_Z.nii.gz + for VFA in "${VFA_DYN_LIST[@]}"; do + VFA_INPUT+="anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-bfcz_VFA.nii.gz " + done + fslmerge -t anat/${PREFIX}_${REF_SPACE}_desc-bfczunified_VFA.nii.gz $VFA_INPUT + VFA_INPUT="desc-bfczunified_VFA" elif [ $EN_BIAS1 -eq 1 ] then - echo Concatenating non Z\'d images - 3dTcat -prefix VFA.nii 2_bfc.nii 5_bfc.nii 10_bfc.nii 12_bfc.nii 15_bfc.nii -overwrite + # echo Concatenating non Z\'d images + for VFA in "${VFA_DYN_LIST[@]}"; do + VFA_INPUT+="anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-bfc_VFA.nii.gz " + done + fslmerge -t anat/${PREFIX}_${REF_SPACE}_desc-bfcunified_VFA.nii.gz $VFA_INPUT &> /dev/null + VFA_INPUT="desc-bfcunified_VFA" else echo Concatenating raw images - 3dTcat -prefix VFA.nii 2_masked.nii 5_masked.nii 10_masked.nii 12_masked.nii 15_masked.nii -overwrite + for VFA in "${VFA_DYN_LIST[@]}"; do + VFA_INPUT+="anat/${PREFIX}_${VFA}_${REF_SPACE}_desc-brain_VFA.nii.gz " + done + fslmerge -t anat/${PREFIX}_${REF_SPACE}_desc-unified_VFA.nii.gz $VFA_INPUT + VFA_INPUT="desc-unified_VFA" fi + gunzip -f "anat/${PREFIX}_${REF_SPACE}_$VFA_INPUT.nii.gz" - if [ $ff -eq 1 ] + if [ ! -f "anat/${PREFIX}_${REF_SPACE}_$VFA_INPUT.nii" ] then - if [ ! -f "VFA.nii" ] - then - echo "Missing VFA file. Component files may have failed." - fail=1 - cd ../.. - continue - fi + echo "$source_dir missing VFA file. Component files may have failed." >> $LOG_FILE + cd $DATA_DIR + fail=1 + continue fi - # motion correction of VFA - # ------------------------------ - mcflirt -in VFA.nii -refvol 'VFA.nii[0]' -cost mutualinfo -report -verbose -plots -o VFA_mc.nii - gunzip -f VFA_mc.nii.gz - - if [ $ff -eq 1 ] - then - if [ ! -f "VFA_mc.nii" ] - then - echo "Missing VFA_mc file. Motion correction may have failed." - fail=1 - cd ../.. - continue - fi - fi - # smooth - #3dBlurToFWHM -input VFA_mc.nii -FWHM 5 -prefix VFA_mc_blurred.nii + prog=$(echo "scale=2; $prog + .5 / $count" | bc -l) + echo -ne "MAKE T1 MAPS [===================> ] $prog% ($current/$count) ~$ETA min remaining \r" - # T1 mapping where the input image is 'VFA.motioncorrected.nii' + # T1 mapping where the input image is 'VFA.nii' # ------------------------------ - matlab -nodisplay -r "cd('$ROCKETSHIP_PATH/parametric_scripts/custom_scripts'); addpath '$ROCKETSHIP_PATH'; addpath '$ROCKETSHIP_PATH/dce'; addpath '$ROCKETSHIP_PATH/external_programs'; addpath '$ROCKETSHIP_PATH/external_programs/niftitools'; addpath '$ROCKETSHIP_PATH/parametric_scripts'; addpath '$GPUFIT_PATH'; addpath '$GPUFIT_M_PATH'; T1mapping_fit('$SUBJECT_TP_PATH/'); exit;" - if [ $ff -eq 1 ] + matlab -nodisplay -r "cd('$ROCKETSHIP_PATH/parametric_scripts/custom_scripts'); addpath '$ROCKETSHIP_PATH'; \ + addpath '$ROCKETSHIP_PATH/dce'; addpath '$ROCKETSHIP_PATH/external_programs'; \ + addpath '$ROCKETSHIP_PATH/external_programs/niftitools'; addpath '$ROCKETSHIP_PATH/parametric_scripts'; \ + addpath '$GPUFIT_PATH'; addpath '$GPUFIT_M_PATH'; T1mapping_fit('$source_dir/anat', '$SUBJECT_TP_PATH/anat', '${PREFIX}_${REF_SPACE}_${VFA_INPUT}.nii'); exit;" &> /dev/null + mv anat/T1_map_t1_fa_fit_${PREFIX}_${REF_SPACE}_${VFA_INPUT}.nii anat/${PREFIX}_${REF_SPACE}_T1map.nii + mv anat/T1_map_t1_fa_fit_${PREFIX}_${REF_SPACE}_${VFA_INPUT}.mat anat/${PREFIX}_${REF_SPACE}_T1map.mat + mv anat/T1_map_t1_fa_fit_${PREFIX}_${REF_SPACE}_${VFA_INPUT}.txt anat/${PREFIX}_${REF_SPACE}_T1map.txt + mv anat/Rsquared_t1_fa_fit_${PREFIX}_${REF_SPACE}_${VFA_INPUT}.nii anat/${PREFIX}_${REF_SPACE}_desc-rsquared_T1map.nii + mv anat/CI_low_t1_fa_fit_${PREFIX}_${REF_SPACE}_${VFA_INPUT}.nii anat/${PREFIX}_${REF_SPACE}_desc-CIlow_T1map.nii + mv anat/CI_high_t1_fa_fit_${PREFIX}_${REF_SPACE}_${VFA_INPUT}.nii anat/${PREFIX}_${REF_SPACE}_desc-CIhigh_T1map.nii + ((diff = SECONDS - diff)) + ETA=$(echo "scale=0; $mETA - ($SECONDS)/60" | bc -l) + prog=$(echo "scale=2; $prog + 1.33 / $count" | bc -l) + echo -ne "DCE MOTIONCORR [====================> ] $prog% ($current/$count) ~$ETA min remaining \r" + if [ ! -f anat/${PREFIX}_${REF_SPACE}_T1map.nii ] then - if [ ! -f "T1_map_t1_fa_fit_VFA_mc.nii" ] - then - echo "Missing T1 map file. T1 mapping may have failed." - fail=1 - cd ../.. - continue - fi + echo $source_dir "Missing T1 map file. T1 mapping may have failed." >> $LOG_FILE + cd $DATA_DIR + fail=1 + continue fi - # Motion correction of dynamic images using AFNI - # ------------------------------ - echo Motion correcting dynamic images... - mcflirt -in DCE.nii -refvol 'DCE.nii[1]' -cost mutualinfo -report -plots -o DCE_mc.nii - max=$(python3 $SCRIPT_PATH/max_disp.py $SUBJECT_TP_PATH) - echo -e "\e[1;33m$max\e[0m" - if [ $ff -eq 1 ] + + if [ $T1_ONLY -eq 1 ] then - if [ ! -f "DCE_mc.nii.gz" ] - then - echo "Missing motion corrected DCE file." - fail=1 - cd ../.. - continue - fi + cd $DATA_DIR + continue fi + # Align T1 map with Dynamic data - # ------------------------------ - # MC or no? - 3dTcat -prefix ref_rep.nii DCE_mc.nii'[1]' -overwrite - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii T1_map_t1_fa_fit_VFA_mc.nii t1_map_fixed_use_me.nii.gz - if [ $ff -eq 1 ] + # ----------------------------- + prog=$(echo "scale=2; $prog + 2.77 / $count" | bc -l) + echo -ne "REG BET MASK [========================> ] $prog% ($current/$count) ~$ETA min remaining \r" + antsApplyTransforms -i anat/${PREFIX}_desc-brain_mask.nii.gz -r $DCE_REF_VOL -t $structural_to_DCEref -o anat/${PREFIX}_${REF_SPACE}_desc-brain_mask_pv.nii.gz &> /dev/null + fslmaths anat/${PREFIX}_${REF_SPACE}_desc-brain_mask_pv.nii.gz -thr 1 -bin anat/${PREFIX}_${REF_SPACE}_desc-brain_mask.nii.gz &> /dev/null + rm anat/${PREFIX}_${REF_SPACE}_desc-brain_mask_pv.nii.gz + prog=$(echo "scale=2; $prog + 0.55 / $count" | bc -l) + ETA=$(echo "scale=0; $mETA - ($SECONDS)/60" | bc -l) + echo -ne "FAST DCE REP 1 [========================> ] $prog% ($current/$count) ~$ETA min remaining \r" + + mkdir -p figures &> /dev/null + if [ $USE_AUTO_AIF -eq 1 ] || [ ! -f "dce/${PREFIX}_${AIF_SUFFIX}.nii.gz" ] && [ ! -f "dce/${PREFIX}_${AIF_SUFFIX}.nii" ] && [ ! -f "dce/${PREFIX}_${AIF_TRAINING_SUFFIX}.nii.gz" ] then - if [ ! -f "t1_map_fixed_use_me.nii.gz" ] - then - echo "Missing registered T1 map." - fail=1 - cd ../.. - continue - fi + # run AutoAIF + if [ $EN_MOTION_CORR -eq 1 ] + then + python3 $AUTO_AIF_PATH/main_vif.py --mode inference --input_path dce/${PREFIX}_desc-hmc_DCE.nii.gz --save_output_path $PWD/dce \ + --model_weight_path $SCRIPT_PATH/$AUTOAIF_WEIGHT_PATH \ + --model_name $AUTOAIF_MODEL \ + --save_image 1 &> /dev/null + # rename output + mv dce/${PREFIX}_desc-hmc_DCE_float_mask.nii dce/${PREFIX}_desc-AIFfloat_mask.nii + mv dce/${PREFIX}_desc-hmc_DCE_mask.nii dce/${PREFIX}_desc-AIFtopvoxels_mask.nii + mv dce/${PREFIX}_desc-hmc_DCE_curve.svg figures/${PREFIX}_desc-AIF_resampledcurve.svg + mv dce/${PREFIX}_desc-hmc_DCE_mask.svg figures/${PREFIX}_desc-AIF_mask.svg + # fslmaths aif_floats.nii -thr 0.95 aif_mask.nii + fslmaths anat/${PREFIX}_${REF_SPACE}_T1map.nii.gz -mas dce/${PREFIX}_desc-AIFtopvoxels_mask.nii dce/${PREFIX}_desc-AIF_T1map.nii + else + python3 $AUTO_AIF_PATH/main_vif.py --mode inference --input_path $source_dir/dce/${PREFIX}_DCE.nii.gz --save_output_path $PWD/dce \ + --model_weight_path $SCRIPT_PATH/$AUTOAIF_WEIGHT_PATH \ + --model_name $AUTOAIF_MODEL \ + --save_image 1 &> /dev/null + mv dce/${PREFIX}_DCE_float_mask.nii dce/${PREFIX}_AIFfloat_mask.nii + mv dce/${PREFIX}_DCE_mask.nii dce/${PREFIX}_AIFtopvoxels_mask.nii + mv dce/${PREFIX}_DCE_curve.svg figures/${PREFIX}_AIF_resampledcurve.svg + mv dce/${PREFIX}_DCE_mask.svg figures/${PREFIX}_AIF_mask.svg + fslmaths anat/${PREFIX}_${REF_SPACE}_T1map.nii.gz -mas dce/${PREFIX}_AIFtopvoxels_mask.nii dce/${PREFIX}_desc-AIF_T1map.nii + fi + elif [ $USE_AUTO_AIF -eq 2 ] + then + # use all available manual AIFs, including those reserved for training + if [ -f "dce/${PREFIX}_${AIF_SUFFIX}.nii.gz" ] + then + # use manual AIF + fslmaths anat/${PREFIX}_${REF_SPACE}_T1map.nii.gz -mas dce/${PREFIX}_${AIF_SUFFIX}.nii.gz dce/${PREFIX}_desc-AIF_T1map.nii.gz + elif [ -f "dce/${PREFIX}_${AIF_TRAINING_SUFFIX}.nii.gz" ] + then + # use training manual AIF + fslmaths anat/${PREFIX}_${REF_SPACE}_T1map.nii.gz -mas dce/${PREFIX}_${AIF_TRAINING_SUFFIX}.nii.gz dce/${PREFIX}_desc-AIF_T1map.nii.gz + fi + else + # use manual AIF + fslmaths anat/${PREFIX}_${REF_SPACE}_T1map.nii.gz -mas dce/${PREFIX}_${AIF_SUFFIX}.nii.gz dce/${PREFIX}_desc-AIF_T1map.nii.gz fi - # align and apply brain mask - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii brain_mask.nii.gz brain_mask_dyn.nii.gz - # ensure AIF is included in mask - fslcpgeom 2.nii brain_mask_dyn.nii - cp aif.nii aif_aligned.nii - fslcpgeom brain_mask_dyn.nii aif_aligned.nii - fslmaths aif_aligned.nii -thr 0 aif_pos.nii - rm aif_aligned.nii - fslmaths brain_mask_dyn.nii -add aif_pos.nii -thr 1 -bin brain_mask_dyn_aif.nii - fslmaths DCE_mc.nii -mas brain_mask_dyn_aif.nii.gz DCE_mc_masked.nii - + # fslcpgeom 2.nii T1_bet_mask_dyn.nii.gz + cp dce/${PREFIX}_desc-AIF_T1map.nii.gz dce/${PREFIX}_desc-AIFaligned_T1map.nii.gz + fslcpgeom anat/${PREFIX}_${REF_SPACE}_desc-brain_mask.nii.gz dce/${PREFIX}_desc-AIFaligned_T1map.nii + fslmaths dce/${PREFIX}_desc-AIFaligned_T1map.nii.gz -thr 0 dce/${PREFIX}_desc-AIFpos_T1map.nii &> /dev/null + rm dce/${PREFIX}_desc-AIFaligned_T1map.nii.gz + fslmaths anat/${PREFIX}_${REF_SPACE}_desc-brain_mask.nii.gz -add dce/${PREFIX}_desc-AIFpos_T1map.nii -thr 1 -bin anat/${PREFIX}_${REF_SPACE}_desc-brainAIF_mask.nii.gz &> /dev/null + # apply AIF mask to all DCE images + if [ $EN_MOTION_CORR -eq 1 ] + then + fslmaths dce/${PREFIX}_desc-hmc_DCE.nii.gz -mas anat/${PREFIX}_${REF_SPACE}_desc-brainAIF_mask.nii.gz dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz &> /dev/null + else + fslmaths $source_dir/dce/${PREFIX}_DCE.nii.gz -mas anat/${PREFIX}_${REF_SPACE}_desc-brainAIF_mask.nii.gz dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz &> /dev/null + fi if [ $EN_BIAS1 -eq 1 ] then - if [ ! -f "DCE_mc_bfc.nii" ] + if [ ! -f "dce/${PREFIX}_desc-bfc_DCE.nii.gz" ] then # Applying bias field correction on dynamic images # ------------------------------ - echo Applying BFC to dynamic images... - 3dTcat -prefix 1st_rep.nii DCE_mc_masked.nii'[0]' -overwrite # extract images from different DCE repetitions - 3dTcat -prefix 5th_rep.nii DCE_mc_masked.nii'[4]' -overwrite - 3dTcat -prefix 10th_rep.nii DCE_mc_masked.nii'[9]' -overwrite - 3dTcat -prefix 20th_rep.nii DCE_mc_masked.nii'[19]' -overwrite - 3dTcat -prefix 30th_rep.nii DCE_mc_masked.nii'[29]' -overwrite - 3dTcat -prefix 40th_rep.nii DCE_mc_masked.nii'[39]' -overwrite - 3dTcat -prefix 50th_rep.nii DCE_mc_masked.nii'[49]' -overwrite - 3dTcat -prefix 60th_rep.nii DCE_mc_masked.nii'[59]' -overwrite - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 1st_rep.nii - rm 1st_rep_mixeltype.nii.gz - rm 1st_rep_pve_0.nii.gz - rm 1st_rep_pve_1.nii.gz - rm 1st_rep_pve_2.nii.gz - rm 1st_rep_pveseg.nii.gz - rm 1st_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 5th_rep.nii - rm 5th_rep_mixeltype.nii.gz - rm 5th_rep_pve_0.nii.gz - rm 5th_rep_pve_1.nii.gz - rm 5th_rep_pve_2.nii.gz - rm 5th_rep_pveseg.nii.gz - rm 5th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 10th_rep.nii - rm 10th_rep_mixeltype.nii.gz - rm 10th_rep_pve_0.nii.gz - rm 10th_rep_pve_1.nii.gz - rm 10th_rep_pve_2.nii.gz - rm 10th_rep_pveseg.nii.gz - rm 10th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 20th_rep.nii - rm 20th_rep_mixeltype.nii.gz - rm 20th_rep_pve_0.nii.gz - rm 20th_rep_pve_1.nii.gz - rm 20th_rep_pve_2.nii.gz - rm 20th_rep_pveseg.nii.gz - rm 20th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 30th_rep.nii - rm 30th_rep_mixeltype.nii.gz - rm 30th_rep_pve_0.nii.gz - rm 30th_rep_pve_1.nii.gz - rm 30th_rep_pve_2.nii.gz - rm 30th_rep_pveseg.nii.gz - rm 30th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 40th_rep.nii - rm 40th_rep_mixeltype.nii.gz - rm 40th_rep_pve_0.nii.gz - rm 40th_rep_pve_1.nii.gz - rm 40th_rep_pve_2.nii.gz - rm 40th_rep_pveseg.nii.gz - rm 40th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 50th_rep.nii - rm 50th_rep_mixeltype.nii.gz - rm 50th_rep_pve_0.nii.gz - rm 50th_rep_pve_1.nii.gz - rm 50th_rep_pve_2.nii.gz - rm 50th_rep_pveseg.nii.gz - rm 50th_rep_seg.nii.gz - - fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b -o 60th_rep.nii - rm 60th_rep_mixeltype.nii.gz - rm 60th_rep_pve_0.nii.gz - rm 60th_rep_pve_1.nii.gz - rm 60th_rep_pve_2.nii.gz - rm 60th_rep_pveseg.nii.gz - rm 60th_rep_seg.nii.gz - + #echo Applying BFC to dynamic images... + reps=$(fslnvols dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz) + rep_interval=$((reps / 8)) + # round rep_interval up + rep_interval=$(echo "scale=0; ($rep_interval + 0.5) / 1" | bc -l) + + # take 9 repetitions with rep_interval from DCE_mc_masked.nii + fslmerge -n 0 rep_0.nii dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz &> /dev/null + for i in {1..8} + do + # name file with rep_interval*i + fslmerge -n $((rep_interval*i-1)) rep_$((rep_interval*i-1)).nii dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz &> /dev/null + done + wait + + DCE_FAST () { + local i=$1 + local rep_interval=$2 + # if i is 0, then we're on the first repetition + if [ ! $i -eq 0 ] + then + # run FAST on rep_interval*i + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b --nopve -o rep_$((rep_interval*i-1)).nii + else + # run FAST on first repetition + fast -t 1 -n 3 -H 0.1 -I 4 -l 20.0 -b --nopve -o rep_0.nii + fi + } + + # BFC each repetition + ETA=$(echo "scale=0; $mETA - ($SECONDS)/60" | bc -l) + prog=$(echo "scale=2; $prog + 6 / $count" | bc -l) + echo -ne "FAST DCE 8REPS [===========================> ] $prog% ($current/$count) ~$ETA min remaining \r" + DCE_FAST "0" "$rep_interval" & + for i in {1..8} + do + DCE_FAST "$i" "$rep_interval" & + # echo -ne "FAST DCE REP $((rep_interval*i-1)) [====================================> ] $prog% ($current/$count) ~$ETA min remaining \r" + done + wait + ETA=$(echo "scale=0; $mETA - ($SECONDS)/60" | bc -l) + prog=$(echo "scale=2; $prog + 48 / $count" | bc -l) + echo -ne "DCE BFC + NORM [================================================> ] $prog% ($current/$count) ~$ETA min remaining \r" + # Concatenation1 - 3dTcat -prefix dyn_bias.nii 1st_rep_bias.nii.gz 5th_rep_bias.nii.gz 10th_rep_bias.nii.gz 20th_rep_bias.nii.gz 30th_rep_bias.nii.gz 40th_rep_bias.nii.gz 50th_rep_bias.nii.gz 60th_rep_bias.nii.gz -overwrite - + fslmerge -t dce/dyn_bias.nii.gz rep_*_bias.nii.gz &> /dev/null + # Computing average across 8 bias field that have been sampled - 3dTstat -mean -prefix mean_dyn_bias_map.nii dyn_bias.nii'[0..7]' -overwrite + fslmaths dce/dyn_bias.nii.gz -Tmean dce/mean_dyn_bias_map.nii.gz &> /dev/null # Normalizing motion corrected DCE image with mean bias field - 3dcalc -a DCE_mc_masked.nii -b mean_dyn_bias_map.nii -expr a/b -prefix DCE_mc_bfc.nii -overwrite - - # don't forget to remove all unnecessary images - rm 1st_rep.nii - rm 1st_rep_bias.nii.gz - rm 5th_rep.nii - rm 5th_rep_bias.nii.gz - rm 10th_rep.nii - rm 10th_rep_bias.nii.gz - rm 20th_rep.nii - rm 20th_rep_bias.nii.gz - rm 30th_rep.nii - rm 30th_rep_bias.nii.gz - rm 40th_rep.nii - rm 40th_rep_bias.nii.gz - rm 50th_rep.nii - rm 50th_rep_bias.nii.gz - rm 60th_rep.nii - rm 60th_rep_bias.nii.gz + fslmaths dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz -div dce/mean_dyn_bias_map.nii.gz dce/${PREFIX}_desc-bfc_DCE.nii.gz &> /dev/null + + mv dce/dyn_bias.nii.gz dce/${PREFIX}_desc-biases_DCE.nii.gz + mv dce/mean_dyn_bias_map.nii.gz dce/${PREFIX}_desc-meanbias_DCE.nii.gz + # remove sampled files + rm rep_*.nii.gz else - echo Skipping DCE BFC... + echo Skipping DCE BFC because it already exists... fi else #echo Motion correcting dynamic set - #3dvolreg -heptic -verbose -base 'DCE.nii[1]' -dfile DCE_motion.txt -prefix DCE_mc_bfc.nii DCE.nii - #3dTcat -prefix ref_rep.nii dce_mc_bfc'[1]' - gunzip -f DCE_mc.nii.gz - mv DCE_mc.nii DCE_mc_bfc.nii + cp dce/${PREFIX}_desc-AIFincluded_DCE.nii.gz dce/${PREFIX}_desc-bfc_DCE.nii.gz + # gunzip -f DCE_mc_bfc.nii.gz fi # align existing white matter mask to dynamic images and re-binarize - bash $SCRIPT_PATH/tktregistration.sh ref_rep.nii 15_wm.nii.gz 15_wm_mask_dyn.nii.gz - fslmaths 15_wm_mask_dyn.nii.gz -thr 1.7 -bin 15_wm_mask_dyn.nii + # antsApplyTransforms -i T1_wm_mask.nii.gz -r ref_rep.nii -t T1_dyn0GenericAffine.mat -o T1_wm_mask_dyn_pv.nii &> /dev/null # apply wm mask to all DCE images - fslmaths DCE_mc_bfc.nii -mas 15_wm_mask_dyn.nii.gz DCE_mc_bfc_wm.nii.gz + if [ $EN_BIAS1 -eq 1 ] + then + fslmaths dce/${PREFIX}_desc-bfc_DCE.nii.gz -mas anat/${PREFIX}_${VFA}_${REF_SPACE}_seg-WM_VFA.nii.gz dce/${PREFIX}_seg-WM_DCE.nii.gz &> /dev/null + else + fslmaths $source_dir/dce/${PREFIX}_DCE.nii.gz -mas anat/${PREFIX}_${REF_SPACE}_label-WM_mask.nii.gz dce/${PREFIX}_seg-WM_DCE.nii.gz &> /dev/null + fi # normalize dynamic images # ------------------------------ - echo Normalizing dynamic images... - python3 $SCRIPT_PATH/DCE_norm.py $SUBJECT_TP_PATH - if [ $ff -eq 1 ] + #echo Normalizing dynamic images... + if [ $EN_Z_NORM -eq 1 ] then - if [ ! -f "DCE_mc_bfc_norm.nii" ] - then - echo "Missing normalized DCE file." - fail=1 - cd ../.. - continue - fi + python3 $SCRIPT_PATH/DCE_norm.py $SUBJECT_TP_PATH/dce &> /dev/null + else + cp dce/${PREFIX}_desc-bfc_DCE.nii.gz dce/${PREFIX}_desc-bfcz_DCE.nii.gz fi - # smooth dynamic set - #3dBlurToFWHM -input DCE_mc_bfc_norm.nii -FWHM 4 -prefix DCE_mc_bfc_norm_blurred.nii + gzip -f dce/${PREFIX}_desc-bfcz_DCE.nii - cd ../../ - echo $dir preprocessing complete! Be sure to have an AIF drawn \(aif.nii\) for DCE. + if [ ! -f "dce/${PREFIX}_desc-bfcz_DCE.nii.gz" ] + then + echo $source_dir "Missing normalized DCE file." >> $LOG_FILE + cd $DATA_DIR + fail=1 + continue + fi + + ETA=$(echo "scale=0; $mETA - ($SECONDS)/60" | bc -l) + prog=$(echo "scale=2; $prog + .6 / $count" | bc -l) + echo -ne "SUBJ COMPLETED [==================================================] $prog% ($current/$count) ~$ETA min remaining \r" + + cd $DATA_DIR + echo $source_dir preprocessing complete! >> $LOG_FILE + let successes++ done + prog=$(echo "scale=2; 100.00" | bc -l) + echo -ne "PREP COMPLETED [==================================================] $prog% ($current/$count)" + +((failures=count-successes)) +echo Completed preprocessing for $count cases. >> $LOG_FILE +echo $successes subjects succeeded >> $LOG_FILE +echo $failures subjects failed >> $LOG_FILE + if [ $fail -eq 1 ] then exit 1 diff --git a/report.py b/report.py deleted file mode 100644 index 4731c8f..0000000 --- a/report.py +++ /dev/null @@ -1,82 +0,0 @@ -import matplotlib.pyplot as plt -import matplotlib.image as mpimg -import matplotlib.gridspec as gridspec -from matplotlib.axes import _secondary_axes -from matplotlib.animation import adjusted_figsize -from matplotlib.pyplot import subplots_adjust -from mpl_toolkits.axes_grid1 import ImageGrid -import numpy as np -import nibabel as nib -from pathlib import Path -import sys -from nibabel import orientations - - -dir = Path(sys.argv[1]) -try: - cmap = str(Path(sys.argv[2])) -except: - cmap = 'gnuplot' - - -analysis = mpimg.imread(str(dir) + '/T1_Ktrans_analysis.png') -zeros = mpimg.imread(str(dir) + '/T1_Ktrans_zeros.png') -aif_curve = mpimg.imread(str(dir) + '/dceAIF_fitting.png') -timecurves = mpimg.imread(str(dir) + '/dce_timecurves.png') -curves = [] -curves.append(mpimg.imread(str(dir) + '/dceAIF_fitting.png')) -curves.append(mpimg.imread(str(dir) + '/dce_timecurves.png')) -ktrans = nib.load(str(dir) + '/dce_patlak_fit_Ktrans.nii') - -dim = {0,1,2} -ktrans_data = ktrans.get_fdata() -ktrans_shape = ktrans_data.shape -slice_num = min(ktrans_shape[0], ktrans_shape[1], ktrans_shape[2]) -slice_loc = ktrans_shape.index(slice_num) -ktrans_data = np.reshape(ktrans_data, (ktrans_shape[min(dim-set([slice_loc]))], ktrans_shape[max(dim-set([slice_loc]))], slice_num)) -slices = [] -for i in range(slice_num): - slices.append(ktrans_data[:,:,i].T) - -fig, axs = plt.subplots(4, 1, figsize=(8.5,11)) -subject = str(dir).split('/')[5] -axs[0].set_title(subject, y=1.02) #+ " (" + str(dir) + "") -plt.suptitle(str(dir), fontsize='small', y=1) -axs[0].axis('off') -axs[0].imshow(analysis) -axs[1].axis('off') -x = axs[1].imshow(zeros, cmap='gnuplot', vmin=0, vmax=.009) -axs[2].axis('off') -axs[3].axis('off') - -gridspec = axs[2].get_subplotspec().get_gridspec() -gridspec2 = axs[3].get_subplotspec().get_gridspec() -subfig = fig.add_subfigure(gridspec[2,:]) -subfig2 = fig.add_subfigure(gridspec[3,:]) -row = subfig.subplots(2,int(slice_num/2)) -curve_rows = subfig2.subplots(1,2) - -# cmap = 'gnuplot' -i = 0 -for ax in row.flat: - ax.axis('off') - ax.set_xlim(30, 290) - ax.set_ylim(20, 310) - # ax.pcolormesh(slices[i], cmap=cmap, vmin=0, vmax=.009) - ax.imshow(slices[i], cmap=cmap, vmin=0, vmax=0.009) - i+=1 - -i = 0 -for ax in curve_rows: - ax.axis('off') - ax.imshow(curves[i]) - i+=1 - -# fig.tight_layout(pad=-.7) -subplots_adjust(top=0.99, bottom=0.0, left=-0.0, right=1.0, hspace=0, wspace=-.0) -# cax = fig.add_axes([0.0, 0.23, 1, .02]) -bozo = fig.colorbar(x, orientation='horizontal', label='Ktrans (/min)', pad=.02, aspect = 60) -bozo.set_label('Ktrans (/min)', labelpad=-34.5, fontsize = 'x-small') - -# plt.show() -plt.savefig(str(dir) + '/report.png', bbox_inches='tight') diff --git a/requirements.txt b/requirements.txt index 7857774..881ea65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,17 @@ -matplotlib==3.6.0 -nibabel==3.1.1 -numpy==1.23.2 -Pillow==9.2.0 -PySimpleGUI==4.60.3 -scipy==1.9.0 +CairoSVG +imageio +Jinja2 +lmfit +matplotlib +nibabel +nilearn +numpy +pandas +Pillow +pydicom +PySimpleGUI +reportlab +scikit_learn +scipy +openpyxl +xlsxwriter diff --git a/run_docker.sh b/run_docker.sh new file mode 100755 index 0000000..ac5ce5c --- /dev/null +++ b/run_docker.sh @@ -0,0 +1,33 @@ +#!/bin/bash + + +# MAKE SURE MATLAB & FREESURFER LICENSE FILES, DATA DIRECTORY, SCRIPT PREFERENCE FOLDER (docker/files/) AND /etc/ ARE SHARED WITH DOCKER + +bozo=$(cat /home/*/.matlab/R*_licenses/*lic* || cat /usr/local/MATLAB/*/licenses/*lic* | grep _HOSTID) + +MAC=${bozo#*MATLAB_HOSTID=} +MAC=${MAC%%:*} +MAC=${MAC:0:2}:${MAC:2:2}:${MAC:4:2}:${MAC:6:2}:${MAC:8:2}:${MAC:10:2} +# echo $MAC + +MATLAB_PATH=/usr/local/MATLAB/$(ls /usr/local/MATLAB/ | sort -V | tail -n 1) +# echo $MATLAB_PATH +MATLAB_VERSION=$(ls /usr/local/MATLAB/ | sort -V | tail -n 1)-dev + +LICENSE=$(ls $MATLAB_PATH/licenses) +# echo $LICENSE + +export UID=$(id -u) +export GID=$(id -g) + +sudo docker run --rm -it -e MLM_LICENSE_FILE=/opt/matlab/licenses/$LICENSE \ + -v /media/network_mriphysics/USC-PPG/docker_test:/data/ \ + -v $FREESURFER_HOME/license.txt:/usr/local/freesurfer/8.1.0/license.txt \ + -v $MATLAB_PATH/licenses:/opt/matlab/licenses \ + -v /etc/passwd:/etc/passwd:ro \ + -v $PWD/docker/files/script_preferences.txt:/opt/ROCKETSHIP/ROCKETSHIP-dev/script_preferences.txt \ + -v $PWD/docker/files/model_weight_huber1.h5:/opt/vascular_function/docker/files/model_weight_huber1.h5 \ + --shm-size=512M --mac-address $MAC \ + --user $UID:$GID \ + --gpus all \ + lsaca05/dce:$MATLAB_VERSION diff --git a/sort_dicom.py b/sort_dicom.py new file mode 100755 index 0000000..aed57b5 --- /dev/null +++ b/sort_dicom.py @@ -0,0 +1,117 @@ +#!/usr/bin/python3 + + +from datetime import time, timedelta, datetime +from statistics import mean +import json +import pydicom +import subprocess +import os +import shutil +import sys + +sort_dir = "/media/network_mriphysics/USC-PPG/bids_ppg/sourcedata/dicom" +output_dir = "/media/network_mriphysics/USC-PPG/bids_ppg/derivatives" + +print(sort_dir) +directory = os.fsencode(sort_dir) +dir_str = os.fsdecode(directory) + +subfolders = [ f for f in os.scandir(directory) if f.is_dir() ] +for subfolder in subfolders: + new_dir = os.fsdecode(subfolder.path) + subname = os.fsdecode(subfolder.name) + new_7z = os.path.join(dir_str,subname+".7z") + if not "_s2" in subname.lower(): + session_id = '01' + subject_id = subname + else: + session_id = '02' + subject_id = subname.replace("_s2","") + subject_id = subject_id.replace("_S2","") #cover both cases + + print("Processing ", subject_id, " session ", session_id) + + + #7zip + try: + #output = subprocess.check_output(['7z',"a",new_7z,new_dir+"/*"]) + print("7z successful") + except: + print(subname + " failed 7zip") + + #find dicom and log directories + file_list = os.listdir(new_dir) + #folder should only have a "dicom" folder and a "log" folder + if not len(file_list)==2: + sys.exit("warning: folder has wrong structure (doesn't have only 2 subdirectories), exiting") + dicom_dir = "" + log_dir = "" + if os.path.isdir(os.path.join(new_dir,file_list[0])): + if file_list[0].lower()=='dicom': + dicom_dir = os.path.join(new_dir,file_list[0]) + if file_list[0].lower()=='log': + log_dir = os.path.join(new_dir,file_list[0]) + if os.path.isdir(os.path.join(new_dir,file_list[1])): + if file_list[1].lower()=='dicom': + dicom_dir = os.path.join(new_dir,file_list[1]) + if file_list[1].lower()=='log': + log_dir = os.path.join(new_dir,file_list[1]) + if dicom_dir=="" or log_dir=="": + sys.exit("warning: cannot find DICOM or LOG folder, exiting") + + # Calculate temporal resolution from difference between acquisition times + # dce_dir = dicom_dir + "/I1164341/dicom" + + # ac_time = [] + # for i in range(1,2000,40): + # file = dce_dir + "/15-" + str(i) + ".dcm" + # dataset = pydicom.dcmread(file, specific_tags=["AcquisitionTime"]) + # formatted_time = dataset.AcquisitionTime + # formatted_time = formatted_time[0:2] + ':' + formatted_time[2:4] + ':' + formatted_time[4:] + # struct = time.fromisoformat(formatted_time) + # bozo = timedelta(hours=struct.hour, minutes=struct.minute, seconds=struct.second, microseconds=struct.microsecond) + # ac_time.append(datetime.strptime(formatted_time, '%H:%M:%S.%f')) + # # diff = [] + # # for i in range(1,50): + # # diff.append(float(str(ac_time[i] - ac_time[i-1])[5:])) + # diff = ac_time[1] - ac_time[0] + # TemporalResolution = str(diff)[5:] + # + # with open('config.json', 'r+') as f: + # data = json.load(f) + # data['descriptions'][1]['sidecarChanges']['TemporalResolution'] = str(TemporalResolution) + # f.seek(0) + # json.dump(data,f,indent=4) + # f.truncate() + + #DICOM to BIDS conversion + try: + + output = subprocess.check_output(['dcm2bids', + "-d", dicom_dir, + "-p", subject_id, + "-s", session_id, + "-c", "config.json", + "-o", output_dir]) + print("dcm2bids successful") + except: + print(subname+" failed dcm2bids") + print("Files sorted") + #remove unsorted files + #shutil.rmtree(dicom_dir) + + #move log files + bids_dir = os.path.join(output_dir,"sub-"+subject_id,"ses-"+session_id,"logs") + os.makedirs(bids_dir,exist_ok=True) + for root, dirs, files in os.walk(log_dir): + for f in files: + shutil.copy(os.path.join(root,f),bids_dir) + + + #move unzipped files + move_dir = new_dir + dest_dir = os.path.join(dir_str,"..") + shutil.move(move_dir,dest_dir) + + print("Completed") diff --git a/template.html b/template.html new file mode 100644 index 0000000..40580dd --- /dev/null +++ b/template.html @@ -0,0 +1,285 @@ + + + {{ title }} + + + + +

    +

    Summary

    +
      +
    • {{ Subject }}
    • +
    • {{ Timepoint }}
    • +
    • {{ Date }}
    • +
    • {{ Commit }}
    • +
    • {{ ROCKETSHIP_commit }}
    • +
    • {{ Institute }}
    • +
    • {{ Machine }}
    • +
    +

    Ktrans (/min)

    + {{ ELYOUEL }} +

    Preprocessing

    +

    Anatomical Info

    +
  • {{ Dimensions }}
  • +
  • {{ Voxel_Size }}
  • +

    T1w Segmentation and Brain Mask

    +
    + {{ image_alt1 }} + {{ image_alt2 }} +
    +
    + {{ image_alt1 }} + {{ image_alt2 }} +
    +
    + {{ image_alt1 }} + {{ image_alt2 }} +
    +

    T1w Registration to DCEref Space

    + {{ image_alt1 }} +

    VFA Z-Normalization

    + {% for i in range(0, num_FAs) %} +

    {{ FAs[i] }}

    + {{ image_alt1 }} + {% endfor %} +

    T1 Map

    + +
  • {{ T1_TR }}
  • +
  • {{ T1_FAs }}
  • +
  • {{ T1_GPU }}
  • +
  • {{ T1_wm_median }}
  • +
  • {{ T1_wm_std }}
  • +
  • {{ T1_gm_median }}
  • +
  • {{ T1_gm_std }}
  • + {{ image_alt1 }} + +

    MCFLIRT Displacements

    + {{ image_alt1 }} +

    AIF mask and curve

    + AutoAIF img missing + AutoAIF img missing +
    + +

    {{ AIF_metric }}

    + +

    Registrations to Dynamic Space

    + {{ image_alt1 }} + {{ image_alt1 }} + {{ image_alt1 }} +

    DCE Z-Normalization

    + {{ image_alt1 }} +

    DCE Outputs

    +

    RUN A

    +
      +
    • {{ DCE_TR }}
    • +
    • {{ DCE_FA }}
    • +
    • {{ Hematocrit }}
    • +
    • {{ SNR_Threshold }}
    • +
    • {{ Relaxivity }}
    • +
    • {{ T1_blood }}
    • +
    • {{ A_last_line }}
    • +
    + +

    RUN B

    +
      +
    • {{ Time_Resolution }}
    • +
    • {{ R_squared_fit }}
    • +
    • {{ R_squared_raw }}
    • +
    • {{ B_last_line }}
    • +
    + +
    + {{ image_alt1 }} + {{ image_alt1 }} +
    +

    RUN D

    +
      +
    • {{ DCE_model }}
    • +
    • {{ GPU_DCE }}
    • +
    • {{ DCE_elapsed_time }}
    • +
    +

    Ktrans maps (10^-3/min)

    +
      + +
    • {{ ktrans_wm_median }}
    • +
    • {{ ktrans_wm_std }}
    • + +
    • {{ ktrans_gm_median }}
    • +
    • {{ ktrans_gm_std }}
    • +
    + {{ ELYOUEL }} + +

    Ktrans stats

    +
    + {{ image_alt1 }} + {{ image_alt1 }} +
    +

    wmparc Registration

    +
    + {{ image_alt1 }} + {{ image_alt2 }} +
    + + +

    Placement in Population

    + to be inserted post-population report + to be inserted post-population report + + + diff --git a/tktregistration.sh b/tktregistration.sh deleted file mode 100755 index 1158ad3..0000000 --- a/tktregistration.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -3dAFNItoNIFTI -prefix TempDataset1.nii.gz $1 -3dAFNItoNIFTI -prefix TempDataset2.nii.gz $2 - -#if [[ $1 = "rat" ]] -#then -#FOV='64' -#fi - -#if [[ $1 = "human" ]] -#then -FOV='256' -#fi - -tkregister2 --targ TempDataset1.nii.gz --mov TempDataset2.nii.gz --reg Register.dat --regheader --noedit --fov $FOV - -mri_vol2vol --mov TempDataset2.nii.gz --targ TempDataset1.nii.gz --reg Register.dat --o OutputDataset.nii.gz - -3dcalc -a OutputDataset.nii.gz -expr 'a' -prefix $3 -overwrite - -rm TempDataset* -rm OutputDataset* - -echo ' ' -echo '*** Registration Done ***' -echo ' ' diff --git a/utils/constants.py b/utils/constants.py new file mode 100644 index 0000000..9ba72f9 --- /dev/null +++ b/utils/constants.py @@ -0,0 +1,2 @@ +# values below this are physiologically nonsensical and is supported by historical analysis +KTRANS_MIN_THRESHOLD = 1e-5 diff --git a/venv_requirements.txt b/venv_requirements.txt new file mode 100644 index 0000000..e6247b9 --- /dev/null +++ b/venv_requirements.txt @@ -0,0 +1,110 @@ +absl-py==2.1.0 +asteval==1.0.5 +astunparse==1.6.3 +cachetools==5.5.0 +cairocffi==1.7.1 +CairoSVG==2.7.1 +certifi==2024.12.14 +cffi==1.17.1 +chardet==5.2.0 +charset-normalizer==3.4.0 +contourpy==1.3.1 +cssselect2==0.7.0 +cycler==0.12.1 +defusedxml==0.7.1 +dill==0.3.9 +flatbuffers==24.3.25 +fonttools==4.55.3 +gast==0.4.0 +google-auth==2.37.0 +google-auth-oauthlib==1.0.0 +google-pasta==0.2.0 +grpcio==1.68.1 +h5py==3.12.1 +idna==3.10 +imageio==2.36.1 +importlib_resources==6.4.5 +jax==0.4.30 +jaxlib==0.4.30 +Jinja2==3.1.5 +joblib==1.4.2 +keras==3.7.0 +kiwisolver==1.4.7 +libclang==18.1.1 +llvmlite==0.43.0 +lmfit==1.3.2 +lxml==5.3.0 +Markdown==3.7 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +matplotlib==3.10.0 +mdurl==0.1.2 +ml-dtypes==0.4.1 +namex==0.0.8 +nibabel==5.3.2 +nilearn==0.11.0 +numba==0.60.0 +numpy==2.1.0 +nvidia-cublas-cu12==12.5.3.2 +nvidia-cuda-cupti-cu12==12.5.82 +nvidia-cuda-nvcc-cu12==12.5.82 +nvidia-cuda-nvrtc-cu12==12.5.82 +nvidia-cuda-runtime-cu12==12.5.82 +nvidia-cudnn-cu12==9.3.0.75 +nvidia-cufft-cu12==11.2.3.61 +nvidia-curand-cu12==10.3.6.82 +nvidia-cusolver-cu12==11.6.3.83 +nvidia-cusparse-cu12==12.5.1.3 +nvidia-nccl-cu12==2.21.5 +nvidia-nvjitlink-cu12==12.5.82 +oauthlib==3.2.2 +opt_einsum==3.4.0 +optree==0.13.1 +packaging==24.2 +pandas==2.2.3 +pandas-flavor==0.6.0 +patsy==1.0.1 +pillow==11.0.0 +pingouin==0.5.5 +plotly==5.24.1 +protobuf==4.25.5 +psutil==6.1.1 +pyasn1==0.6.1 +pyasn1_modules==0.4.1 +pycparser==2.22 +pydicom==3.0.1 +Pygments==2.18.0 +pyparsing==3.2.0 +PySimpleGUI==5.0.7 +python-dateutil==2.9.0.post0 +pytz==2024.2 +reportlab==4.2.5 +requests==2.32.3 +requests-oauthlib==2.0.0 +rich==13.9.4 +rsa==4.9 +scikit-learn==1.6.0 +scipy==1.14.1 +seaborn==0.13.2 +six==1.17.0 +statsmodels==0.14.4 +tabulate==0.9.0 +tenacity==9.0.0 +tensorboard==2.18.0 +tensorboard-data-server==0.7.2 +tensorflow==2.18.0 +tensorflow-addons==0.23.0 +tensorflow-estimator==2.12.0 +tensorflow-io-gcs-filesystem==0.37.1 +termcolor==2.5.0 +threadpoolctl==3.5.0 +tinycss2==1.4.0 +typeguard==2.13.3 +typing_extensions==4.12.2 +tzdata==2024.2 +uncertainties==3.2.2 +urllib3==2.2.3 +webencodings==0.5.1 +Werkzeug==3.1.3 +wrapt==1.14.1 +xarray==2024.11.0