From 82cd0aa25c591a60312612589c3cb5600e93a705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20K=C3=BCgler?= Date: Fri, 11 Oct 2024 15:04:50 +0200 Subject: [PATCH] =?UTF-8?q?run=5Ffastsurfer.sh=20-=20Adds=20the=20--edits?= =?UTF-8?q?=20flag=20-=20Add=20additional=20log=20messages=20for=20edits?= =?UTF-8?q?=20-=20Support=20for=20manedit=20files=20for=20asegdkt=5Fsegfil?= =?UTF-8?q?e.=20-=20Also=20propagate=20asegdkt=5Fsegfile=20into=C2=A0=20as?= =?UTF-8?q?eg=5Fsegfile=20and=20mask=5Fname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit recon_surf/recon-surf.sh - Adds the --edits flag - Add additional log messages for edits - Change check for existing run into a message instead a fail criteria in edits mode - Do not delete logfile in edits mode - Make mask and aseg_noCC required for edits mode (otherwise it might get unclear what should or was already done between run_fastsurfer and recon-surf) - Add manedit "search" for mask and aseg_nocc (the latter is recon_surf/talairach-reg.sh - Add an extra argument (new argument #3 to indicate whether talairach.reg should support edits) - Support for edits: some messages and if in edit mode, only run talairach registration if mri/transforms/talairach.xfm and mri/transforms/talairach.auto.xfm are the same (so there are no edits) -- thus edits in talairach.xfm superceed automatic results - talairch-reg.sh will now fail if mri/transforms/talairach.xfm exists, but the script is not running in edits mode (independent if talairach.auto.xfm is the same or not). recon_surf/functions.sh - add the add_file_suffix function, which adds a file suffix (for example manedits) to a file name --- recon_surf/functions.sh | 23 +++++++++++- recon_surf/recon-surf.sh | 75 ++++++++++++++++++++++++++++++++-------- run_fastsurfer.sh | 70 +++++++++++++++++++++++++++++++++---- 3 files changed, 146 insertions(+), 22 deletions(-) diff --git a/recon_surf/functions.sh b/recon_surf/functions.sh index 20c0f895..5053cef0 100644 --- a/recon_surf/functions.sh +++ b/recon_surf/functions.sh @@ -190,4 +190,25 @@ function echo_quoted() sep=" " done echo "" -} \ No newline at end of file +} + +function add_file_suffix() +{ + # params: + # 1: filename + # 2: suffix to add + + # example: add_file_suffix /path/to/file.nii.gz suffix -> /path/to/file.suffix.nii.gz + + # file extensions supported: + file_extensions=(nii.gz nii mgz stats annot ctab label log txt lta xfm yaml) + for extension in "${file_extensions[@]}" + do + pattern="\.${extension//./\\.}$" + if [[ "$1" =~ $pattern ]] + then + length=$((${#1} - ${#extension})) + echo "${1:0:$length}$2.$extension" + fi + done +} diff --git a/recon_surf/recon-surf.sh b/recon_surf/recon-surf.sh index c3e9d815..04ab5ded 100755 --- a/recon_surf/recon-surf.sh +++ b/recon_surf/recon-surf.sh @@ -88,6 +88,13 @@ FLAGS: that segmentations produced otherwise are conformed. Requires an ABSOLUTE Path! Default location: \$SUBJECTS_DIR/\$sid/mri/aparc.DKTatlas+aseg.deep.mgz + --mask_name Path to the brainmask file to use. Default location: + \$SUBJECTS_DIR/\$sid/mri/mask.mgz + --edits Disable the check for existing recon-surf.sh run, + replaces mri/wm.mgz by mri/wm.manedit.mgz as well as + mri/aseg.auto_noCC.mgz by mri/aseg.auto_noCC.manedit.mgz, + includes brain.finalsurfs.manedit.mgz edits, and enables + FreeSurfer-style WM control points. --fstess Revert to FreeSurfer mri_tesselate for surface creation (default: mri_mc) --fsqsphere Revert to FreeSurfer iterative inflation for qsphere @@ -177,9 +184,11 @@ case $key in asegdkt_segfile="$1" shift # past value ;; + --mask_name) mask="$1" ; shift ;; --vol_segstats) echo "WARNING: The --vol_segstats flag is obsolete and will be removed, --vol_segstats ignored." ;; + --edits) edits="true" ;; --segstats_legacy) segstats_legacy="true" ;; --fstess) fstess=1 ;; --fsqsphere) fsqsphere=1 ;; @@ -269,7 +278,7 @@ fi if [[ "$base" == "1" ]] then - if [ ! -f "$sd/$subject/base-tps.fastsurfer" ] ; then + if [ ! -f "$SUBJECTS_DIR/$subject/base-tps.fastsurfer" ] ; then echo "ERROR: $subject is either not found in SUBJECTS_DIR" echo "or it is not a longitudinal template directory (base)," echo "which needs to contain base-tps.fastsurfer file. Please ensure that" @@ -347,9 +356,17 @@ fi # Check if running on an existing subject directory if [ -f "$SUBJECTS_DIR/$subject/mri/wm.mgz" ] || [ -f "$SUBJECTS_DIR/$subject/mri/aparc.DKTatlas+aseg.orig.mgz" ] then - echo "ERROR: Running on top of an existing subject directory!" - echo " The output directory must not contain data from a previous invocation of recon-surf." - exit 1 + on_existing_run="true" + if [[ "$edits" == "true" ]] + then + echo "INFO: Running on top of an existing subject directory, but edits is $edits." + else + echo "ERROR: Running on top of an existing subject directory!" + echo " The output directory must not contain data from a previous invocation of recon-surf." + exit 1 + fi +else + on_existing_run="false" fi # collect info @@ -375,14 +392,16 @@ sdir="$SUBJECTS_DIR/$subject/surf" statsdir="$SUBJECTS_DIR/$subject/stats" ldir="$SUBJECTS_DIR/$subject/label" -mask="$mdir/mask.mgz" - +# set default mask and make mask absolute +if [[ -z "$mask" ]] ; then mask="$mdir/mask.mgz" +elif [[ "${mask:0:1}" != "/" ]] ; then mask="$SUBJECTS_DIR/$subject/$mask" +fi # Set up log file DoneFile="$SUBJECTS_DIR/$subject/scripts/recon-surf.done" if [ "$DoneFile" != /dev/null ] ; then rm -f "$DoneFile" ; fi LF="$SUBJECTS_DIR/$subject/scripts/recon-surf.log" -if [ "$LF" != /dev/null ] ; then rm -f "$LF" ; fi +if [ "$LF" != /dev/null ] && [[ "$edits" != "true" ]]; then rm -f "$LF" ; fi echo "Log file for recon-surf.sh" >> "$LF" { # all output tee -a "$LF" date 2>&1 @@ -394,13 +413,16 @@ echo "Log file for recon-surf.sh" >> "$LF" cat "$FREESURFER_HOME/build-stamp.txt" 2>&1 echo "$VERSION" uname -a 2>&1 + if [[ "$on_existing_run" == "true" ]] + then + echo "Running on top of an existing subject directory with edits=$edits!" + fi echo " " if [ "$base" == "1" ] ; then echo " " echo "================== BASE - Longitudinal Template Creation =========================" echo " " - fi - if [ "$long" == "1" ] ; then + elif [ "$long" == "1" ] ; then echo " " echo "================== LONG - Longitudinal Timpe Point Creation ======================" echo "long: using template directory (base) $baseid" @@ -468,6 +490,12 @@ fi cmd="mri_convert $t1 $mdir/orig.mgz" RunIt "$cmd" "$LF" +asegdkt_segfile_manedit=$(add_file_suffix "$asegdkt_segfile" "manedit") +if [[ ! "$asegdkt_segfile_manedit" =~ (\.manedit){2,}\. ]] && # skipping; exclude double manedit + [[ -f "$asegdkt_segfile_manedit" ]] +then + asegdkt_segfile="$asegdkt_segfile_manedit" # use the manedit file +fi cmd="mri_convert $asegdkt_segfile $mdir/aparc.DKTatlas+aseg.orig.mgz" RunIt "$cmd" "$LF" @@ -492,13 +520,15 @@ if [ "$long" == "1" ] ; then run_it "$LF" "${cmda[@]}" fi -if [ ! -f "$mask" ] || [ ! -f "$mdir/aseg.auto_noCCseg.mgz" ] ; then +aseg_nocc="aseg.auto_noCCseg.mgz" +if [ ! -f "$mask" ] || [ ! -f "$mdir/$aseg_nocc" ] ; then + # independently of the existence of manedit files, generate the baseline files. + # Mask or aseg.auto_noCCseg not found; create them from aparc.DKTatlas+aseg { - # Mask or aseg.auto_noCCseg not found; create them from aparc.DKTatlas+aseg echo " " echo "============= Creating aseg.auto_noCCseg (map aparc labels back) ===============" echo " " - echo "WARNING: mri/mask.mgz or mri/aseg.auto_noCCseg.mgz are missing, but these files are" + echo "WARNING: $mask or mri/$aseg_nocc are missing, but these files are" echo " required in recon-surf.sh and always created in the segmentation pipeline run." echo " It is recommended to transfer these files from there!" } | tee -a "$LF" @@ -518,8 +548,18 @@ if [ ! -f "$mask" ] || [ ! -f "$mdir/aseg.auto_noCCseg.mgz" ] ; then fi run_it "$LF" "${cmda[@]}" + mask_generated_in_recon_surf="true" +else + mask_generated_in_recon_surf="false" fi +# replace mask by manedit-ed mask +mask_manedit="$(add_file_suffix "$mask" "manedit")" +if [[ "$edits" == "true" ]] && [[ -e "$mask_manedit" ]] +then + echo "INFO: mri/$mask_manedit detected, supersedes mri/$mask in recon_surf.sh." + mask="$mask_manedit" +fi # ============================= NU BIAS CORRECTION ======================================= @@ -557,6 +597,7 @@ if [[ ! -f "$mdir/transforms/talairach.lta" ]] || [[ ! -f "$mdir/transforms/tala cmda=("$binpath/talairach-reg.sh" "$LF" --dir "$mdir" --conformed_name "$mdir/orig.mgz" --norm_name "$mdir/orig_nu.mgz") if [[ "$long" == "1" ]] ; then cmda+=(--long "$basedir") ; fi + if [[ "$edits" == "1" ]] ; then cmda+=(--edits) ; fi if [[ "$atlas3T" == "true" ]] ; then cmda+=(--3T) ; fi { @@ -577,7 +618,7 @@ fi } | tee -a $LF # the difference between nu and orig_nu is the fact that nu has the talairach-registration header -# create norm by masking nu +# create norm by masking nu (supports manedit-ed mask) cmda=(mri_mask "$mdir/nu.mgz" "$mask" "$mdir/norm.mgz") run_it "$LF" "${cmda[@]}" if [ "$get_t1" == "1" ] @@ -598,7 +639,7 @@ then # cmd="mri_normalize -g 1 -seed 1234 -mprage $base_flags $mdir/nu.mgz $mdir/T1.mgz $noconform_if_hires" cmda=(mri_normalize -g 1 -seed 1234 -mprage "$mdir/nu.mgz" "$mdir/T1.mgz" $noconform_if_hires) run_it "$LF" "${cmda[@]}" - # create brainmask by masking T1 + # create brainmask by masking T1 (supports manedit-ed mask) cmda=(mri_mask "$mdir/T1.mgz" "$mask" "$mdir/brainmask.mgz") run_it "$LF" "${cmda[@]}" else @@ -619,7 +660,7 @@ fi # create aseg.auto including corpus callosum segmentation and 46 sec, requires norm.mgz # Note: if original input segmentation already contains CC, this will exit with ERROR # in the future maybe check and skip this step (and next) -cmd="mri_cc -aseg aseg.auto_noCCseg.mgz -o aseg.auto.mgz -lta $mdir/transforms/cc_up.lta $subject" +cmd="mri_cc -aseg $aseg_nocc -o aseg.auto.mgz -lta $mdir/transforms/cc_up.lta $subject" RunIt "$cmd" "$LF" # add CC into aparc.DKTatlas+aseg.deep (not sure if this is really needed) cmd="$python ${binpath}paint_cc_into_pred.py -in_cc $mdir/aseg.auto.mgz -in_pred $asegdkt_segfile -out $mdir/aparc.DKTatlas+aseg.deep.withCC.mgz" @@ -775,6 +816,10 @@ for hemi in lh rh ; do # ============================= FIX - WHITEPREAPARC ================================================== + wm_file="wm.mgz" + wm_manedit="$(add_file_suffix "$wm_file" "manedit")" + if [[ -e "$wm_manedit" ]] ; then wm_file="$wm_manedit" ; fi + # In Long stream we skip topo fix if [ "$long" == "0" ] ; then diff --git a/run_fastsurfer.sh b/run_fastsurfer.sh index 6b4ff965..f23d5ea9 100755 --- a/run_fastsurfer.sh +++ b/run_fastsurfer.sh @@ -57,6 +57,7 @@ norm_name_t2="" seg_log="" run_talairach_registration="false" atlas3T="false" +edits="false" viewagg="auto" device="auto" batch_size="1" @@ -73,6 +74,8 @@ threads="1" python="python3.10 -s" allow_root=() version_and_quit="" +warn_seg_only=() +warn_base=() base=0 # flag for longitudinal template (base) run long=0 # flag for longitudinal time point run baseid="" # baseid for logitudinal time point run @@ -132,6 +135,12 @@ FLAGS: The voxel size (whether set manually or derived) determines whether the surfaces are processed with highres options (below 1mm) or not. + --edits Enables manual edits by replacing select intermediate/ + result files by manedit substitutes (*.manedit.). + Segmentation: and . + Surface: Disables check for existing recon-surf.sh run; + edits of mri/wm.mgz and brain.finalsurfs.mgz + as well as FreeSurfer-style WM control points. --version Print version information and exit; is optional. may be empty, just prints the version number, +git_branch also prints the current branch, and any @@ -161,7 +170,8 @@ SEGMENTATION PIPELINE: \$SUBJECTS_DIR/\$sid/mri/orig_nu.mgz --tal_reg Perform the talairach registration for eTIV estimates in --seg_only stream and stats files (is affected by - the --3T flag, see below). + the --3T flag, see below). Manual talairach + registrations are not replaced in --edits mode. MODULES: By default, all modules are run. @@ -354,8 +364,8 @@ case $key in --t1) t1="$1" ; shift ;; --t2) t2="$1" ; shift ;; --seg_log) seg_log="$1" ; shift ;; - --conformed_name) conformed_name="$1" ; shift ;; - --norm_name) norm_name="$1" ; shift ;; + --conformed_name) conformed_name="$1" ; warn_seg_only+=("$key" "$1") ; shift ;; + --norm_name) norm_name="$1" ; warn_seg_only+=("$key" "$1") ; shift ;; --norm_name_t2) norm_name_t2="$1" ; shift ;; --seg|--asegdkt_segfile|--aparc_aseg_segfile) if [[ "$key" != "--asegdkt_segfile" ]] @@ -368,6 +378,7 @@ case $key in --vox_size) vox_size="$1" ; shift ;; # --3t: both for surface pipeline and the --tal_reg flag --3t) surf_flags+=("--3T") ; atlas3T="true" ;; + --edits) surf_flags+=("$key") ; edits="true" ;; --threads) threads="$1" ; shift ;; --py) python="$1" ; shift ;; -h|--help) usage ; exit ;; @@ -429,7 +440,7 @@ case $key in ;; --asegdkt_statsfile) asegdkt_statsfile="$1" ; shift ;; --aseg_segfile) aseg_segfile="$1" ; shift ;; - --mask_name) mask_name="$1" ; shift ;; + --mask_name) mask_name="$1" ; warn_seg_only+=("$key" "$1") ; warn_base+=("$key" "$1") ; shift ;; --merged_segfile) merged_segfile="$1" ; shift ;; # cereb module options @@ -546,6 +557,13 @@ then exit 1 fi +if [[ "${#warn_seg_only[@]}" -gt 0 ]] && [[ "$run_surf_pipeline" == "1" ]] +then + echo "WARNING: Specifying '${warn_seg_only[*]}' only affects the segmentation " + echo " pipeline and not the surface pipeline. It can therefore have unexpected consequences" + echo " on surface processing." +fi + # DEFAULT FILE NAMES if [[ -z "$merged_segfile" ]] ; then merged_segfile="${sd}/${subject}/mri/fastsurfer.merged.mgz" ; fi if [[ -z "$asegdkt_segfile" ]] ; then asegdkt_segfile="${sd}/${subject}/mri/aparc.DKTatlas+aseg.deep.mgz" ; fi @@ -644,6 +662,7 @@ then echo "ERROR: To run the cerebellum segmentation but no asegdkt, the aseg segmentation must already exist." echo " You passed --no_asegdkt but the asegdkt segmentation ($asegdkt_segfile) could not be found." echo " If the segmentation is not saved in the default location ($asegdkt_segfile_default)," + echo " specify the absolute path and name via --asegdkt_segfile" exit 1 fi fi @@ -708,7 +727,10 @@ then fi # base can only be run with the template image from base-setup: t1="$sd/$subject/mri/orig.mgz" - + if [[ "${#warn_base[@]}" -gt 0 ]] ; then + echo "ERROR: Specifying '${warn_base[*]}' is not supported for base (template) creation." + exit 1 + fi fi if [[ "$long" == "1" ]] @@ -765,6 +787,7 @@ then } | tee -a "$seg_log" fi +asegdkt_segfile_manedit=$(add_file_suffix "$asegdkt_segfile" "manedit") if [[ "$run_seg_pipeline" == "1" ]] then @@ -794,6 +817,34 @@ then echo "ERROR: FastSurfer asegdkt segmentation failed." | tee -a "$seg_log" exit 1 fi + if [[ -e "$asegdkt_segfile_manedit" ]] + then + if [[ "$edits" == "true" ]] + then + { + echo "INFO: $asegdkt_segfile_manedit (manedit file for ) detected," + echo " supersedes $asegdkt_segfile for creation of $aseg_segfile" + echo " and $mask_name!" + } | tee -a "$seg_log" + asegdkt_segfile="$asegdkt_segfile_manedit" + cmd=($python "$fastsurfercnndir/reduce_to_aseg.py" -i "$asegdkt_segfile" -o "$aseg_segfile" + --outmask "$mask_name" --fixwm) + echo_quoted "${cmd[@]}" | tee -a "$seg_log" + "${cmd[@]}" | tee -a "$seg_log" + exit_code="${PIPESTATUS[0]}" + if [[ "${exit_code}" -ne 0 ]] + then + echo "ERROR: Reduction of asegdkt to aseg failed." | tee -a "$seg_log" + exit 1 + fi + else + { + echo "ERROR: $asegdkt_segfile_manedit (manedit file for ) detected," + echo " but edit was not passed. Please delete $asegdkt_segfile_manedit, or add --edits!" + } | tee -a "$seg_log" + exit 1 + fi + fi fi if [[ -n "$t2" ]] then @@ -833,6 +884,7 @@ then cmd=("$reconsurfdir/talairach-reg.sh" "$seg_log" --dir "$sd/$subject/mri" --conformed_name "$conformed_name" --norm_name "$norm_name") if [[ "$long" == "1" ]] ; then cmd+=(--long "$basedir") ; fi + if [[ "$edits" == "1" ]] ; then cmd+=(--edits) ; fi if [[ "$atlas3T" == "true" ]] ; then cmd+=(--3T) ; fi { echo "INFO: Running talairach registration..." @@ -848,6 +900,8 @@ then if [[ "$run_asegdkt_module" ]] then + mask_name_manedit=$(add_file_suffix "$mask_name" "manedit") + if [[ -e "$mask_name_manedit" ]] ; then mask_name="$mask_name_manedit" ; fi cmd=($python "${fastsurfercnndir}/segstats.py" --segfile "$asegdkt_segfile" --segstatsfile "$asegdkt_statsfile" --normfile "$norm_name" --threads "$threads" "${allow_root[@]}" --empty --excludeid 0 @@ -974,6 +1028,10 @@ then # then # ln -s -r "$asegdkt_segfile" "$merged_segfile" # fi +else # not running segmentation pipeline + # Replace asegdkt_segfile and aseg_segfile variables with manedit file here, + # if the manedit exists, so recon-surf uses the manedit file. + if [[ -e "$asegdkt_segfile_manedit" ]] ; then asegdkt_segfile="$asegdkt_segfile_manedit" ; fi fi if [[ "$run_surf_pipeline" == "1" ]] @@ -982,7 +1040,7 @@ then # use recon-surf to create surface models based on the FastSurferCNN segmentation. pushd "$reconsurfdir" > /dev/null || exit 1 echo "cd $reconsurfdir" | tee -a "$seg_log" - cmd=("./recon-surf.sh" --sid "$subject" --sd "$sd" --t1 "$conformed_name" + cmd=("./recon-surf.sh" --sid "$subject" --sd "$sd" --t1 "$conformed_name" --mask_name "$mask_name" --asegdkt_segfile "$asegdkt_segfile" --threads "$threads" --py "$python" "${surf_flags[@]}" "${allow_root[@]}") echo_quoted "${cmd[@]}" | tee -a "$seg_log"