diff --git a/FastSurferCNN/segstats.py b/FastSurferCNN/segstats.py index f2b93d52..ba467de2 100644 --- a/FastSurferCNN/segstats.py +++ b/FastSurferCNN/segstats.py @@ -725,7 +725,7 @@ def read_classes_from_lut(lut_file: str | Path): } return pd.read_csv( lut_file, - delim_whitespace=True, + sep='\s+', index_col=False, skip_blank_lines=True, comment="#", diff --git a/FastSurferCNN/utils/mapper.py b/FastSurferCNN/utils/mapper.py index fa96a83f..11b7bcc1 100644 --- a/FastSurferCNN/utils/mapper.py +++ b/FastSurferCNN/utils/mapper.py @@ -999,7 +999,7 @@ def __init__( self._data = pandas.read_csv( file_or_buffer, - delim_whitespace=True, + sep='\s+', index_col=0, skip_blank_lines=True, comment="#", diff --git a/doc/overview/INSTALL.md b/doc/overview/INSTALL.md index 746995d1..38be4204 100644 --- a/doc/overview/INSTALL.md +++ b/doc/overview/INSTALL.md @@ -61,9 +61,9 @@ sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test sudo apt install -y g++-11 ``` -You also need to have bash-4.0 or higher (check with `bash --version`). +You also need to have bash-3.2 or higher (check with `bash --version`). -You also need a working version of python3 (we recommend python 3.10 -- we do not support other versions). These packages should be sufficient to install python dependencies and then run the FastSurfer neural network segmentation. If you want to run the full pipeline, you also need a [working installation of FreeSurfer](https://surfer.nmr.mgh.harvard.edu/fswiki/rel7downloads) (including its dependencies). +You also need a working version of python3.10 (we do not support other versions). These packages should be sufficient to install python dependencies and then run the FastSurfer neural network segmentation. If you want to run the full pipeline, you also need a [working installation of FreeSurfer](https://surfer.nmr.mgh.harvard.edu/fswiki/rel7downloads) (including its dependencies). If you are using pip, make sure pip is updated as older versions will fail. @@ -165,26 +165,24 @@ Continue with the example in [Example 1](EXAMPLES.md#example-1-fastsurfer-docker ### Native -On modern Macs with the Apple Silicon M1 or M2 ARM-based chips, we recommend a native installation as it runs much faster than Docker in our tests. The experimental support for the built-in AI Accelerator is also only available on native installations. Native installation also supports older Intel chips. +On modern Macs with the Apple Silicon M1 or M2 ARM-based chips, we recommend a native installation as it runs much faster than Docker in our tests. The experimental support for the built-in AI accelerator (MPS) is also only available on native installations. Native installation also supports older Intel chips. -#### 1. Git and Bash -If you do not have git and a recent bash (version > 4.0 required!) installed, install them via the packet manager, e.g. brew. -This installs brew and then bash: +#### 1. Dependency packages +If you do not have git, python3.10 or bash (at least 3.2) you can install these via the packet manager brew. +This installs brew and then git and python3.10: ```sh /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew install bash +brew install git python@3.10 ``` -Make sure you use this bash and not the older one provided with MacOS! - #### 2. Python Create a python environment, activate it, and upgrade pip. Here we use pip, but you should also be able to use conda for python: ```sh -python3 -m venv $HOME/python-envs/fastsurfer +python3.10 -m venv $HOME/python-envs/fastsurfer source $HOME/python-envs/fastsurfer/bin/activate -python3 -m pip install --upgrade pip +python3.10 -m pip install --upgrade pip ``` #### 3. FastSurfer and Requirements @@ -195,10 +193,9 @@ cd FastSurfer export PYTHONPATH="${PYTHONPATH}:$PWD" ``` - Install the FastSurfer requirements ```sh -python3 -m pip install -r requirements.mac.txt +python3.10 -m pip install -r requirements.mac.txt ``` If this step fails, you may need to edit ```requirements.mac.txt``` and adjust version number to what is available. @@ -212,24 +209,22 @@ pip3 install --no-binary=h5py h5py You can also download all network checkpoint files (this should be done if you are installing for multiple users): ```sh -python3 FastSurferCNN/download_checkpoints.py --all +python3.10 FastSurferCNN/download_checkpoints.py --all ``` -Once all dependencies are installed, run the FastSurfer segmentation only (!!) by calling ```bash ./run_fastsurfer.sh --seg_only ....``` with the appropriate command line flags, see the [commandline documentation](../../README.md#usage). - -Note: You may always need to prepend the command with `bash` (i.e. `bash run_fastsurfer.sh <...>`) to ensure that bash 4.0 is used instead of the system default. +Once all dependencies are installed, you can run the FastSurfer segmentation only by calling ```./run_fastsurfer.sh --seg_only ....``` with the appropriate command line flags, see the [commandline documentation](../../README.md#usage). -To run the full pipeline, install and source also the supported FreeSurfer version according to their [Instructions](https://surfer.nmr.mgh.harvard.edu/fswiki/rel7downloads). There is a freesurfer email list, if you run into problems during this step. +To run the full pipeline, install and source also the supported FreeSurfer version according to their [Instructions](https://surfer.nmr.mgh.harvard.edu/fswiki/rel7downloads). There is a freesurfer email list, if you run into problems during this step. Note, that currently FreeSurfer for MacOS supports no ARM, but only Intel, so on modern M-chips it might be slow due to the emulation. #### 4. Apple AI Accelerator support -You can also try the experimental support for the Apple Silicon AI Accelerator by setting `PYTORCH_ENABLE_MPS_FALLBACK` and passing `--device mps`: +You can also try the experimental support for the Apple Silicon AI Accelerator by setting `PYTORCH_ENABLE_MPS_FALLBACK` and passing `--device mps` for the segmentation module to make use of the fast GPU: ```sh export PYTORCH_ENABLE_MPS_FALLBACK=1 ./run_fastsurfer.sh --seg_only --device mps .... ``` -This will be at least twice as fast as `--device cpu`. The fallback environment variable is necessary as one function is not yet implemented for the GPU and will fall back to CPU. +This will be at least twice as fast as `--device cpu`. The fallback environment variable is necessary as `aten::max_unpool2d` is not yet implemented for MPS and will fall back to CPU. ## Windows diff --git a/pyproject.toml b/pyproject.toml index 3cf938d8..2d2a06aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,20 +32,23 @@ classifiers = [ ] dependencies = [ 'h5py>=3.7', - 'lapy>=0.4.1', - 'matplotlib>=3.5.1', - 'nibabel>=3.2.2', - 'numpy>=1.21', - 'pandas>=1.4.3', - 'torch>=1.12.0', + 'lapy>=1.0.1', + 'matplotlib>=3.7.1', + 'nibabel>=5.1.0', + 'numpy>=1.25,<2', + 'pandas>=1.5.3', 'pyyaml>=6.0', - 'scipy>=1.8.0', - 'yacs>=0.1.8', - 'simpleitk>=2.1.1', - 'scipy>=1.8.0', - 'tensorboard>=2.9.1', + 'requests>=2.31.0', + 'scikit-image>=0.19.3', + 'scikit-learn>=1.2.2', + 'scipy>=1.10.1,!=1.13.0', + 'simpleitk>=2.2.1', + 'tensorboard>=2.12.1', + 'torch>=2.0.1', 'torchio>=0.18.83', - 'tqdm>=4.64', + 'torchvision>=0.15.2', + 'tqdm>=4.65', + 'yacs>=0.1.8', ] [project.optional-dependencies] diff --git a/recon_surf/functions.sh b/recon_surf/functions.sh index 0b719a09..a5439418 100644 --- a/recon_surf/functions.sh +++ b/recon_surf/functions.sh @@ -30,12 +30,13 @@ function RunIt() if [[ $# -eq 3 ]] then local CMDF=$3 - echo "echo ${cmd@Q}" |& tee -a $CMDF - echo "$timecmd $cmd" |& tee -a $CMDF + printf -v tmp %q "$cmd" + echo "echo $tmp" 2>&1 | tee -a $CMDF + echo "$timecmd $cmd" 2>&1 | tee -a $CMDF echo "if [ \${PIPESTATUS[0]} -ne 0 ] ; then exit 1 ; fi" >> $CMDF else - echo $cmd |& tee -a $LF - $timecmd $cmd |& tee -a $LF + echo $cmd 2>&1 | tee -a $LF + $timecmd $cmd 2>&1 | tee -a $LF if [ ${PIPESTATUS[0]} -ne 0 ] ; then exit 1 ; fi fi } @@ -107,21 +108,21 @@ function softlink_or_copy() if [[ $# -eq 4 ]] then local CMDF=$4 - echo "echo \"$ln_cmd\" " |& tee -a $CMDF - echo "$timecmd $ln_cmd " |& tee -a $CMDF - echo "if [ \${PIPESTATUS[0]} -ne 0 ]" |& tee -a $CMDF - echo "then " |& tee -a $CMDF - echo " echo \"$cp_cmd\" " |& tee -a $CMDF - echo " $timecmd $cp_cmd " |& tee -a $CMDF + echo "echo \"$ln_cmd\" " 2>&1 | tee -a $CMDF + echo "$timecmd $ln_cmd " 2>&1 | tee -a $CMDF + echo "if [ \${PIPESTATUS[0]} -ne 0 ]" 2>&1 | tee -a $CMDF + echo "then " 2>&1 | tee -a $CMDF + echo " echo \"$cp_cmd\" " 2>&1 | tee -a $CMDF + echo " $timecmd $cp_cmd " 2>&1 | tee -a $CMDF echo " if [ \${PIPESTATUS[0]} -ne 0 ] ; then exit 1 ; fi" >> $CMDF - echo "fi" |& tee -a $CMDF + echo "fi" 2>&1 | tee -a $CMDF else - echo $ln_cmd |& tee -a $LF - $timecmd $ln_cmd |& tee -a $LF + echo $ln_cmd 2>&1 | tee -a $LF + $timecmd $ln_cmd 2>&1 | tee -a $LF if [ ${PIPESTATUS[0]} -ne 0 ] then - echo $cp_cmd |& tee -a $LF - $timecmd $cp_cmd |& tee -a $LF + echo $cp_cmd 2>&1 | tee -a $LF + $timecmd $cp_cmd 2>&1 | tee -a $LF if [ ${PIPESTATUS[0]} -ne 0 ] ; then exit 1 ; fi fi fi diff --git a/recon_surf/recon-surf.sh b/recon_surf/recon-surf.sh index 8b17efba..1bfe2a6f 100755 --- a/recon_surf/recon-surf.sh +++ b/recon_surf/recon-surf.sh @@ -44,10 +44,10 @@ else fi -# check bash version > 4 +# check bash version > 3.1 (needed for printf %q) function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } -if [ $(version ${BASH_VERSION}) -lt $(version "4.0.0") ]; then - echo "bash ${BASH_VERSION} is too old. Should be newer than 4.0, please upgrade!" +if [ $(version ${BASH_VERSION}) -lt $(version "3.1.0") ]; then + echo "bash ${BASH_VERSION} is too old. Should be newer than 3.1, please upgrade!" exit 1 fi @@ -91,7 +91,7 @@ FLAGS: etiv estimates for 3T MR images, default: 1.5T atlas). --parallel Run both hemispheres in parallel --threads Set openMP and ITK threads to - --py Command for python, default ${python@Q} + --py Command for python, default ${python} --fs_license Path to FreeSurfer license key file. Register at https://surfer.nmr.mgh.harvard.edu/registration.html for free to obtain it if you do not have FreeSurfer @@ -389,37 +389,37 @@ 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 echo "Log file for recon-surf.sh" >> $LF -date |& tee -a $LF -echo "" |& tee -a $LF -echo "export SUBJECTS_DIR=$SUBJECTS_DIR" |& tee -a $LF -echo "cd `pwd`" |& tee -a $LF -echo $0 ${inputargs[*]} |& tee -a $LF -echo "" |& tee -a $LF -cat $FREESURFER_HOME/build-stamp.txt |& tee -a $LF -echo $VERSION |& tee -a $LF -uname -a |& tee -a $LF - -echo " " |& tee -a $LF -echo "================== Checking validity of inputs =================================" |& tee -a $LF -echo " " |& tee -a $LF +date 2>&1 | tee -a $LF +echo "" 2>&1 | tee -a $LF +echo "export SUBJECTS_DIR=$SUBJECTS_DIR" 2>&1 | tee -a $LF +echo "cd `pwd`" 2>&1 | tee -a $LF +echo $0 ${inputargs[*]} 2>&1 | tee -a $LF +echo "" 2>&1 | tee -a $LF +cat $FREESURFER_HOME/build-stamp.txt 2>&1 | tee -a $LF +echo $VERSION 2>&1 | tee -a $LF +uname -a 2>&1 | tee -a $LF + +echo " " 2>&1 | tee -a $LF +echo "================== Checking validity of inputs =================================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # Print parallelization parameters -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF if [ "$DoParallel" == "1" ] then - echo " RUNNING both hemis in PARALLEL " |& tee -a $LF + echo " RUNNING both hemis in PARALLEL " 2>&1 | tee -a $LF else - echo " RUNNING both hemis SEQUENTIALLY " |& tee -a $LF + echo " RUNNING both hemis SEQUENTIALLY " 2>&1 | tee -a $LF fi -echo " RUNNING $OMP_NUM_THREADS number of OMP THREADS " |& tee -a $LF -echo " RUNNING $ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS number of ITK THREADS " |& tee -a $LF -echo " " |& tee -a $LF +echo " RUNNING $OMP_NUM_THREADS number of OMP THREADS " 2>&1 | tee -a $LF +echo " RUNNING $ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS number of ITK THREADS " 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # Check input segmentation quality -echo "Checking Input Segmentation Quality ..." |& tee -a "$LF" +echo "Checking Input Segmentation Quality ..." 2>&1 | tee -a "$LF" cmd="$python $FASTSURFER_HOME/FastSurferCNN/quick_qc.py --asegdkt_segfile $asegdkt_segfile" RunIt "$cmd" "$LF" -echo "" |& tee -a "$LF" +echo "" 2>&1 | tee -a "$LF" @@ -427,9 +427,9 @@ echo "" |& tee -a "$LF" ########################################## START ######################################################## -echo " " |& tee -a $LF -echo "================== Creating orig and rawavg from input =========================" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "================== Creating orig and rawavg from input =========================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF CONFORM_LF=$SUBJECTS_DIR/$subject/scripts/conform.log if [ $CONFORM_LF != /dev/null ] ; then rm -f $CONFORM_LF ; fi @@ -450,12 +450,12 @@ RunIt "$cmd" $LF if (( $(echo "$vox_size < $hires_voxsize_threshold" | bc -l) )) then - echo "The voxel size $vox_size is less than $hires_voxsize_threshold, so we are proceeding with hires options." |& tee -a $LF + echo "The voxel size $vox_size is less than $hires_voxsize_threshold, so we are proceeding with hires options." 2>&1 | tee -a $LF hiresflag="-hires" noconform_if_hires=" -noconform" hires_surface_suffix=".predec" else - echo "The voxel size $vox_size is not less than $hires_voxsize_threshold, so we are proceeding with standard options." |& tee -a $LF + echo "The voxel size $vox_size is not less than $hires_voxsize_threshold, so we are proceeding with standard options." 2>&1 | tee -a $LF hiresflag="" noconform_if_hires="" hires_surface_suffix="" @@ -477,9 +477,9 @@ popd ### ---------- if [ ! -f "$mask" ] || [ ! -f "$mdir/aseg.auto_noCCseg.mgz" ] ; then # Mask or aseg.auto_noCCseg not found; create them - echo " " |& tee -a $LF - echo "============= Creating aseg.auto_noCCseg (map aparc labels back) ===============" |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo "============= Creating aseg.auto_noCCseg (map aparc labels back) ===============" 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF # reduce labels to aseg, then create mask (dilate 5, erode 4, largest component), also mask aseg to remove outliers # output will be uchar (else mri_cc will fail below) @@ -489,9 +489,9 @@ fi ### END SUPERSEDED BY SEGMENTATION PIPELINE, will be removed in the future ### ---------- -echo " " |& tee -a $LF -echo "============= Computing Talairach Transform and NU (bias corrected) ============" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "============= Computing Talairach Transform and NU (bias corrected) ============" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF ### START SUPERSEDED BY SEGMENTATION PIPELINE, will be removed in the future ### ---------- @@ -519,13 +519,13 @@ fi ### ---------- if [[ ! -f "$mdir/transforms/talairach.lta" ]] || [[ ! -f "$mdir/transforms/talairach_with_skull.lta" ]]; then - echo "\"$binpath/talairach-reg.sh\" \"$mdir\" \"$atlas3T\" \"$LF\"" |& tee -a "$LF" + echo "\"$binpath/talairach-reg.sh\" \"$mdir\" \"$atlas3T\" \"$LF\"" 2>&1 | tee -a "$LF" "$binpath/talairach-reg.sh" "$mdir" "$atlas3T" "$LF" fi -echo " " |& tee -a $LF -echo "============ Creating brainmask from aseg and norm, and update aseg ============" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "============ Creating brainmask from aseg and norm, and update aseg ============" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # create norm by masking nu cmd="mri_mask $mdir/nu.mgz $mdir/mask.mgz $mdir/norm.mgz" @@ -557,9 +557,9 @@ RunIt "$cmd" $LF 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" RunIt "$cmd" $LF -echo " " |& tee -a $LF -echo "========= Creating filled from brain (brainfinalsurfs, wm.asegedit, wm) =======" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "========= Creating filled from brain (brainfinalsurfs, wm.asegedit, wm) =======" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF cmd="recon-all -s $subject -asegmerge -normalization2 -maskbfs -segmentation -fill $hiresflag $fsthreads" RunIt "$cmd" $LF @@ -577,9 +577,9 @@ CMDFS="$CMDFS $CMDF" rm -rf $CMDF echo "#!/bin/bash" > $CMDF -echo "echo " |& tee -a $CMDF -echo "echo \"================== Creating surfaces $hemi - orig.nofix ==================\"" |& tee -a $CMDF -echo "echo " |& tee -a $CMDF +echo "echo " 2>&1 | tee -a $CMDF +echo "echo \"================== Creating surfaces $hemi - orig.nofix ==================\"" 2>&1 | tee -a $CMDF +echo "echo " 2>&1 | tee -a $CMDF if [ "$fstess" == "1" ] @@ -610,8 +610,8 @@ else # Check if the surfaceRAS was correctly set and exit otherwise (sanity check in case nibabel changes their default header behaviour) cmd="mris_info $outmesh | tr -s ' ' | grep -q 'vertex locs : surfaceRAS'" - echo "echo \"$cmd\" " |& tee -a $CMDF - echo "$timecmd $cmd " |& tee -a $CMDF + echo "echo \"$cmd\" " 2>&1 | tee -a $CMDF + echo "$timecmd $cmd " 2>&1 | tee -a $CMDF echo "if [ \${PIPESTATUS[1]} -ne 0 ] ; then echo \"Incorrect header information detected in $outmesh: vertex locs is not set to surfaceRAS. Exiting... \"; exit 1 ; fi" >> $CMDF # Reduce to largest component (usually there should only be one) @@ -637,9 +637,9 @@ fi -echo "echo " |& tee -a $CMDF -echo "echo \"=================== Creating surfaces $hemi - qsphere ====================\"" |& tee -a $CMDF -echo "echo " |& tee -a $CMDF +echo "echo " 2>&1 | tee -a $CMDF +echo "echo \"=================== Creating surfaces $hemi - qsphere ====================\"" 2>&1 | tee -a $CMDF +echo "echo " 2>&1 | tee -a $CMDF #surface inflation (54sec both hemis) (needed for qsphere and for topo-fixer) cmd="recon-all -subject $subject -hemi $hemi -inflate1 -no-isrunning $hiresflag $fsthreads" @@ -658,16 +658,17 @@ else # equivalent to -qsphere # (23sec) cmd="$python ${binpath}spherically_project_wrapper.py --hemi $hemi --sdir $sdir" - cmd="$cmd --subject $subject --threads=$threads --py ${python@Q} --binpath ${binpath}" + printf -v tmp %q "$python" + cmd="$cmd --subject $subject --threads=$threads --py ${tmp} --binpath ${binpath}" RunIt "$cmd" $LF $CMDF fi -echo "echo " |& tee -a $CMDF -echo "echo \"=================== Creating surfaces $hemi - fix ========================\"" |& tee -a $CMDF -echo "echo " |& tee -a $CMDF +echo "echo " 2>&1 | tee -a $CMDF +echo "echo \"=================== Creating surfaces $hemi - fix ========================\"" 2>&1 | tee -a $CMDF +echo "echo " 2>&1 | tee -a $CMDF ## -fix cmd="recon-all -subject $subject -hemi $hemi -fix -autodetgwstats -white-preaparc -cortex-label -no-isrunning $hiresflag $fsthreads" @@ -678,9 +679,9 @@ RunIt "$cmd" $LF $CMDF -echo "echo \" \"" |& tee -a $CMDF -echo "echo \"================== Creating surfaces $hemi - inflate2 ====================\"" |& tee -a $CMDF -echo "echo \" \"" |& tee -a $CMDF +echo "echo \" \"" 2>&1 | tee -a $CMDF +echo "echo \"================== Creating surfaces $hemi - inflate2 ====================\"" 2>&1 | tee -a $CMDF +echo "echo \" \"" 2>&1 | tee -a $CMDF # create nicer inflated surface from topo fixed (not needed, just later for visualization) @@ -688,9 +689,9 @@ cmd="recon-all -subject $subject -hemi $hemi -smooth2 -inflate2 -curvHK -no-isru RunIt "$cmd" $LF $CMDF -echo "echo \" \"" |& tee -a $CMDF -echo "echo \"=========== Creating surfaces $hemi - map input asegdkt_segfile to surf ===============\"" |& tee -a $CMDF -echo "echo \" \"" |& tee -a $CMDF +echo "echo \" \"" 2>&1 | tee -a $CMDF +echo "echo \"=========== Creating surfaces $hemi - map input asegdkt_segfile to surf ===============\"" 2>&1 | tee -a $CMDF +echo "echo \" \"" 2>&1 | tee -a $CMDF # sample input segmentation (aparc.DKTatlas+aseg orig) onto wm surface: # map input aparc to surface (requires thickness (and thus pail) to compute projfrac 0.5), here we do projmm which allows us to compute based only on white @@ -706,9 +707,9 @@ echo "echo \" \"" |& tee -a $CMDF # if we segment with FS or if surface registration is requested do it here: if [ "$fsaparc" == "1" ] || [ "$fssurfreg" == "1" ] ; then - echo "echo \" \"" |& tee -a $CMDF - echo "echo \"============ Creating surfaces $hemi - FS sphere, surfreg ===============\"" |& tee -a $CMDF - echo "echo \" \"" |& tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF + echo "echo \"============ Creating surfaces $hemi - FS sphere, surfreg ===============\"" 2>&1 | tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF # Surface registration for cross-subject correspondence (registration to fsaverage) cmd="recon-all -subject $subject -hemi $hemi -sphere $hiresflag -no-isrunning $fsthreads" @@ -746,9 +747,9 @@ fi if [ "$fsaparc" == "1" ] ; then - echo "echo \" \"" |& tee -a $CMDF - echo "echo \"============ Creating surfaces $hemi - FS asegdkt_segfile..pial ===============\"" |& tee -a $CMDF - echo "echo \" \"" |& tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF + echo "echo \"============ Creating surfaces $hemi - FS asegdkt_segfile..pial ===============\"" 2>&1 | tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF # 20-25 min for traditional surface segmentation (each hemi) # this creates aparc and creates pial using aparc, also computes jacobian @@ -759,9 +760,9 @@ if [ "$fsaparc" == "1" ] ; then else - echo "echo \" \"" |& tee -a $CMDF - echo "echo \"================ Creating surfaces $hemi - white and pial direct ===================\"" |& tee -a $CMDF - echo "echo \" \"" |& tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF + echo "echo \"================ Creating surfaces $hemi - white and pial direct ===================\"" 2>&1 | tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF # 4 min compute white : @@ -805,9 +806,9 @@ RunIt "$cmd" $LF "$CMDF" if [ "$DoParallel" == "0" ] ; then - echo " " |& tee -a $LF - echo " RUNNING $hemi sequentially ... " |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo " RUNNING $hemi sequentially ... " 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF chmod u+x $CMDF RunIt "$CMDF" $LF fi @@ -818,17 +819,17 @@ done # hemi loop ---------------------------------- if [ "$DoParallel" == 1 ] ; then - echo " " |& tee -a $LF - echo " RUNNING HEMIs in PARALLEL !!! " |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo " RUNNING HEMIs in PARALLEL !!! " 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF RunBatchJobs $LF $CMDFS fi -echo " " |& tee -a $LF -echo "============================ Creating surfaces - ribbon ===========================" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "============================ Creating surfaces - ribbon ===========================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # -cortribbon 4 minutes, ribbon is used in mris_anatomical stats to remove voxels from surface based volumes that should not be cortex # anatomical stats can run without ribon, but will omit some surface based measures then # wmparc needs ribbon, probably other stuff (aparc to aseg etc). @@ -840,9 +841,9 @@ echo " " |& tee -a $LF if [ "$fsaparc" == "1" ] ; then - echo " " |& tee -a $LF - echo "============= Creating surfaces - other FS asegdkt_segfile and stats =======================" |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo "============= Creating surfaces - other FS asegdkt_segfile and stats =======================" 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF cmd="recon-all -subject $subject -cortparc2 -cortparc3 -pctsurfcon -hyporelabel $hiresflag $fsthreads" RunIt "$cmd" $LF @@ -854,9 +855,9 @@ if [ "$fsaparc" == "1" ] ; then fi # (FS-APARC) -echo " " |& tee -a $LF -echo "===================== Creating surfaces - mapped stats =========================" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "===================== Creating surfaces - mapped stats =========================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # 2x18sec create stats from mapped aparc @@ -868,9 +869,9 @@ done if [ "$fsaparc" == "0" ] ; then - echo " " |& tee -a $LF - echo "============= Creating surfaces - pctsurfcon, hypo, segstats ====================" |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo "============= Creating surfaces - pctsurfcon, hypo, segstats ====================" 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF # pctsurfcon (has no way to specify which annot to use, so we need to link ours as aparc is not available) pushd $ldir @@ -911,9 +912,9 @@ fi -echo " " |& tee -a $LF -echo "===================== Creating wmparc from mapped =======================" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "===================== Creating wmparc from mapped =======================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # 1m 11sec also create stats for aseg.presurf.hypos (which is basically the aseg derived from the input with CC and hypos) # difference between this and the surface improved one above are probably tiny, so the surface improvement above can probably be skipped to save time @@ -962,9 +963,9 @@ fi -echo " " |& tee -a $LF -echo "================= DONE =========================================================" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "================= DONE =========================================================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # Collect info EndTime=`date` @@ -972,9 +973,9 @@ tSecEnd=`date '+%s'` tRunHours=`echo \($tSecEnd - $tSecStart\)/3600|bc -l` tRunHours=`printf %6.3f $tRunHours` -echo "Started at $StartTime " |& tee -a $LF -echo "Ended at $EndTime" |& tee -a $LF -echo "#@#%# recon-surf-run-time-hours $tRunHours" |& tee -a $LF +echo "Started at $StartTime " 2>&1 | tee -a $LF +echo "Ended at $EndTime" 2>&1 | tee -a $LF +echo "#@#%# recon-surf-run-time-hours $tRunHours" 2>&1 | tee -a $LF # Create the Done File echo "------------------------------" > $DoneFile @@ -991,7 +992,7 @@ echo "VERSION $VERSION" >> $DoneFile echo "CMDPATH $0" >> $DoneFile echo "CMDARGS ${inputargs[*]}" >> $DoneFile -echo "recon-surf.sh $subject finished without error at `date`" |& tee -a $LF +echo "recon-surf.sh $subject finished without error at `date`" 2>&1 | tee -a $LF cmd="$python ${binpath}utils/extract_recon_surf_time_info.py -i $LF -o $SUBJECTS_DIR/$subject/scripts/recon-surf_times.yaml" RunIt "$cmd" "/dev/null" diff --git a/recon_surf/recon-surfreg.sh b/recon_surf/recon-surfreg.sh index 546478e0..b3260165 100755 --- a/recon_surf/recon-surfreg.sh +++ b/recon_surf/recon-surfreg.sh @@ -109,12 +109,12 @@ function RunIt() if [[ $# -eq 3 ]] then CMDF=$3 - echo "echo \"$cmd\" " |& tee -a $CMDF - echo "$timecmd $cmd " |& tee -a $CMDF + echo "echo \"$cmd\" " 2>&1 | tee -a $CMDF + echo "$timecmd $cmd " 2>&1 | tee -a $CMDF echo "if [ \${PIPESTATUS[0]} -ne 0 ] ; then exit 1 ; fi" >> $CMDF else - echo $cmd |& tee -a $LF - $timecmd $cmd |& tee -a $LF + echo $cmd 2>&1 | tee -a $LF + $timecmd $cmd 2>&1 | tee -a $LF #if [ ${PIPESTATUS[0]} -ne 0 ] ; then exit 1 ; fi fi } @@ -356,28 +356,28 @@ if [ $DoneFile != /dev/null ] ; then rm -f $DoneFile ; fi LF=$SUBJECTS_DIR/$subject/scripts/recon-surfreg.log if [ $LF != /dev/null ] ; then rm -f $LF ; fi echo "Log file for recon-surfreg.sh" >> $LF -date |& tee -a $LF -echo "" |& tee -a $LF -echo "export SUBJECTS_DIR=$SUBJECTS_DIR" |& tee -a $LF -echo "cd `pwd`" |& tee -a $LF -echo $0 ${inputargs[*]} |& tee -a $LF -echo "" |& tee -a $LF -cat $FREESURFER_HOME/build-stamp.txt |& tee -a $LF -echo $VERSION |& tee -a $LF -uname -a |& tee -a $LF +date 2>&1 | tee -a $LF +echo "" 2>&1 | tee -a $LF +echo "export SUBJECTS_DIR=$SUBJECTS_DIR" 2>&1 | tee -a $LF +echo "cd `pwd`" 2>&1 | tee -a $LF +echo $0 ${inputargs[*]} 2>&1 | tee -a $LF +echo "" 2>&1 | tee -a $LF +cat $FREESURFER_HOME/build-stamp.txt 2>&1 | tee -a $LF +echo $VERSION 2>&1 | tee -a $LF +uname -a 2>&1 | tee -a $LF # Print parallelization parameters -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF if [ "$DoParallel" == "1" ] then - echo " RUNNING both hemis in PARALLEL " |& tee -a $LF + echo " RUNNING both hemis in PARALLEL " 2>&1 | tee -a $LF else - echo " RUNNING both hemis SEQUENTIALLY " |& tee -a $LF + echo " RUNNING both hemis SEQUENTIALLY " 2>&1 | tee -a $LF fi -echo " RUNNING $OMP_NUM_THREADS number of OMP THREADS " |& tee -a $LF -echo " RUNNING $ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS number of ITK THREADS " |& tee -a $LF -echo " " |& tee -a $LF +echo " RUNNING $OMP_NUM_THREADS number of OMP THREADS " 2>&1 | tee -a $LF +echo " RUNNING $ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS number of ITK THREADS " 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF #if false; then @@ -396,9 +396,9 @@ for hemi in lh rh; do CMDFS="$CMDFS $CMDF" rm -rf $CMDF - echo "echo \" \"" |& tee -a $CMDF - echo "echo \"============ Creating surfaces $hemi - FS sphere, surfreg ===============\"" |& tee -a $CMDF - echo "echo \" \"" |& tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF + echo "echo \"============ Creating surfaces $hemi - FS sphere, surfreg ===============\"" 2>&1 | tee -a $CMDF + echo "echo \" \"" 2>&1 | tee -a $CMDF # Surface registration for cross-subject correspondence (registration to fsaverage) cmd="recon-all -subject $subject -hemi $hemi -sphere -no-isrunning $fsthreads" @@ -432,9 +432,9 @@ for hemi in lh rh; do # $SUBJECTS_DIR/$subject/label/${hemi}.aparc.DKTatlas-guided.annot" if [ "$DoParallel" == "0" ] ; then - echo " " |& tee -a $LF - echo " RUNNING $hemi sequentially ... " |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo " RUNNING $hemi sequentially ... " 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF chmod u+x $CMDF RunIt "$CMDF" $LF fi @@ -444,16 +444,16 @@ done # hemi loop ---------------------------------- if [ "$DoParallel" == 1 ] ; then - echo " " |& tee -a $LF - echo " RUNNING HEMIs in PARALLEL !!! " |& tee -a $LF - echo " " |& tee -a $LF + echo " " 2>&1 | tee -a $LF + echo " RUNNING HEMIs in PARALLEL !!! " 2>&1 | tee -a $LF + echo " " 2>&1 | tee -a $LF RunBatchJobs $LF $CMDFS fi -echo " " |& tee -a $LF -echo "================= DONE =========================================================" |& tee -a $LF -echo " " |& tee -a $LF +echo " " 2>&1 | tee -a $LF +echo "================= DONE =========================================================" 2>&1 | tee -a $LF +echo " " 2>&1 | tee -a $LF # Collect info EndTime=`date` @@ -461,9 +461,9 @@ tSecEnd=`date '+%s'` tRunHours=`echo \($tSecEnd - $tSecStart\)/3600|bc -l` tRunHours=`printf %6.3f $tRunHours` -echo "Started at $StartTime " |& tee -a $LF -echo "Ended at $EndTime" |& tee -a $LF -echo "#@#%# recon-surfreg-run-time-hours $tRunHours" |& tee -a $LF +echo "Started at $StartTime " 2>&1 | tee -a $LF +echo "Ended at $EndTime" 2>&1 | tee -a $LF +echo "#@#%# recon-surfreg-run-time-hours $tRunHours" 2>&1 | tee -a $LF # Create the Done File echo "------------------------------" > $DoneFile @@ -480,7 +480,7 @@ echo "VERSION $VERSION" >> $DoneFile echo "CMDPATH $0" >> $DoneFile echo "CMDARGS ${inputargs[*]}" >> $DoneFile -echo "recon-surfreg.sh $subject finished without error at `date`" |& tee -a $LF +echo "recon-surfreg.sh $subject finished without error at `date`" 2>&1 | tee -a $LF cmd="$python ${binpath}utils/extract_recon_surf_time_info.py -i $LF -o $SUBJECTS_DIR/$subject/scripts/recon-surfreg_times.yaml" RunIt "$cmd" "/dev/null" diff --git a/requirements.mac.txt b/requirements.mac.txt index f1afd67f..95af69a7 100644 --- a/requirements.mac.txt +++ b/requirements.mac.txt @@ -1,185 +1,19 @@ -# -# This file is manually created from the autogenerated -# requirements.txt . It is experimental to support MAC -# (intel, apple silicon and gpus via mps). For this we -# currently need the nightly torch and torchvision. -# ---extra-index-url https://download.pytorch.org/whl/nightly/cpu +h5py>=3.7 +lapy>=1.0.1 +matplotlib>=3.7.1 +nibabel>=5.1.0 +numpy>=1.25,<2 +pandas>=1.5.3 +pyyaml>=6.0 +requests>=2.31.0 +scikit-image>=0.19.3 +scikit-learn>=1.2.2 +scipy>=1.10.1,!=1.13.0 +simpleitk>=2.2.1 +tensorboard>=2.12.1 +torch>=2.0.1 +torchio>=0.18.83 +torchvision>=0.15.2 +tqdm>=4.65 +yacs>=0.1.8 -absl-py==1.2.0 - # via tensorboard -cachetools==5.2.0 - # via google-auth -certifi==2022.6.15 - # via requests -charset-normalizer==2.1.0 - # via requests -click==8.1.3 - # via torchio -cycler==0.11.0 - # via matplotlib -deprecated==1.2.13 - # via torchio -fonttools==4.34.4 - # via matplotlib -google-auth==2.9.1 - # via - # google-auth-oauthlib - # tensorboard -google-auth-oauthlib==0.4.6 - # via tensorboard -grpcio==1.47.0 - # via tensorboard -h5py==3.7.0 - # via -r requirements.in -humanize==4.2.3 - # via torchio -idna==3.3 - # via requests -imageio==2.19.5 - # via scikit-image -importlib-metadata==4.12.0 - # via markdown -joblib==1.2.0 - # via scikit-learn -kiwisolver==1.4.4 - # via matplotlib -lapy==0.4.1 - # via -r requirements.in -markdown==3.4.1 - # via tensorboard -matplotlib==3.5.1 - # via -r requirements.in -networkx==2.8.5 - # via scikit-image -nibabel==3.2.2 - # via - # -r requirements.in - # torchio -numpy==1.23.5 - # via - # -r requirements.in - # h5py - # imageio - # lapy - # matplotlib - # nibabel - # pandas - # pywavelets - # scikit-image - # scikit-learn - # scipy - # tensorboard - # tifffile - # torchio - # torchvision -oauthlib==3.2.0 - # via requests-oauthlib -packaging==21.3 - # via - # matplotlib - # nibabel - # scikit-image -pandas==1.4.3 - # via -r requirements.in -pillow==9.2.0 - # via - # -r requirements.in - # imageio - # matplotlib - # scikit-image - # torchvision -plotly==5.9.0 - # via lapy -protobuf==3.19.4 - # via tensorboard -pyasn1==0.4.8 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 - # via google-auth -pyparsing==3.0.9 - # via - # matplotlib - # packaging -python-dateutil==2.8.2 - # via - # -r requirements.in - # matplotlib - # pandas -pytz==2022.1 - # via pandas -pywavelets==1.3.0 - # via scikit-image -pyyaml==6.0 - # via - # -r requirements.in - # yacs -requests==2.28.1 - # via - # requests-oauthlib - # tensorboard - # torchvision -requests-oauthlib==1.3.1 - # via google-auth-oauthlib -rsa==4.8 - # via google-auth -scikit-image==0.19.2 - # via -r requirements.in -scikit-learn==1.1.2 - # via -r requirements.in -scipy==1.8.0 - # via - # -r requirements.in - # lapy - # scikit-image - # scikit-learn - # torchio -simpleitk==2.1.1 - # via - # -r requirements.in - # torchio -six==1.16.0 - # via - # google-auth - # grpcio - # python-dateutil -tenacity==8.0.1 - # via plotly -tensorboard==2.9.1 - # via -r requirements.in -tensorboard-data-server==0.6.1 - # via tensorboard -tensorboard-plugin-wit==1.8.1 - # via tensorboard -threadpoolctl==3.1.0 - # via scikit-learn -tifffile==2022.5.4 - # via scikit-image -torch>=1.13.0.dev20220815 - # manually set nighly -torchio==0.18.83 - # via -r requirements.in -torchvision>=0.14.0.dev20220815 - # manually set nighly -tqdm==4.64 - # via - # -r requirements.in - # torchio -typing-extensions==4.3.0 - # via - # torch - # torchvision -urllib3==1.26.10 - # via requests -werkzeug==2.1.2 - # via tensorboard -wheel==0.37.1 - # via tensorboard -wrapt==1.14.1 - # via deprecated -yacs==0.1.8 - # via -r requirements.in -zipp==3.8.1 - # via importlib-metadata diff --git a/run_fastsurfer.sh b/run_fastsurfer.sh index 694ad669..1e1c51cf 100755 --- a/run_fastsurfer.sh +++ b/run_fastsurfer.sh @@ -713,7 +713,7 @@ else log_existed="false" fi VERSION=$($python "$FASTSURFER_HOME/FastSurferCNN/version.py" "${version_args[@]}") -echo "Version: $VERSION" |& tee -a "$seg_log" +echo "Version: $VERSION" 2>&1 | tee -a "$seg_log" ### IF THE SCRIPT GETS TERMINATED, ADD A MESSAGE trap "{ echo \"run_fastsurfer.sh terminated via signal at \$(date -R)!\" >> \"$seg_log\" ; }" SIGINT SIGTERM @@ -733,8 +733,8 @@ if [[ "$run_seg_pipeline" == "1" ]] # "============= Running FastSurferCNN (Creating Segmentation aparc.DKTatlas.aseg.mgz) ===============" # use FastSurferCNN to create cortical parcellation + anatomical segmentation into 95 classes. echo "Log file for segmentation FastSurferCNN/run_prediction.py" >> "$seg_log" - date |& tee -a "$seg_log" - echo "" |& tee -a "$seg_log" + date 2>&1 | tee -a "$seg_log" + echo "" 2>&1 | tee -a "$seg_log" if [[ "$run_asegdkt_module" == "1" ]] then @@ -745,7 +745,7 @@ if [[ "$run_seg_pipeline" == "1" ]] --viewagg_device "$viewagg" --device "$device" "${allow_root[@]}") # specify the subject dir $sd, if asegdkt_segfile explicitly starts with it if [[ "$sd" == "${asegdkt_segfile:0:${#sd}}" ]]; then cmd=("${cmd[@]}" --sd "$sd"); fi - echo "${cmd[@]}" |& tee -a "$seg_log" + echo "${cmd[@]}" 2>&1 | tee -a "$seg_log" "${cmd[@]}" exit_code="${PIPESTATUS[0]}" if [[ "${exit_code}" == 2 ]] @@ -766,7 +766,7 @@ if [[ "$run_seg_pipeline" == "1" ]] echo "INFO: Running N4 bias-field correction" | tee -a "$seg_log" cmd=($python "${reconsurfdir}/N4_bias_correct.py" "--in" "$conformed_name" --rescale "$norm_name" --aseg "$asegdkt_segfile" --threads "$threads") - echo "${cmd[@]}" |& tee -a "$seg_log" + echo "${cmd[@]}" 2>&1 | tee -a "$seg_log" "${cmd[@]}" if [[ "${PIPESTATUS[0]}" -ne 0 ]] then @@ -778,7 +778,7 @@ if [[ "$run_seg_pipeline" == "1" ]] then echo "INFO: Running talairach registration" | tee -a "$seg_log" cmd=("$reconsurfdir/talairach-reg.sh" "$sd/$subject/mri" "$atlas3T" "$seg_log") - echo "${cmd[@]}" |& tee -a "$seg_log" + echo "${cmd[@]}" 2>&1 | tee -a "$seg_log" "${cmd[@]}" if [[ "${PIPESTATUS[0]}" -ne 0 ]] then @@ -800,8 +800,8 @@ if [[ "$run_seg_pipeline" == "1" ]] 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2034 2035 --lut "$fastsurfercnndir/config/FreeSurferColorLUT.txt") - echo "${cmd[@]}" |& tee -a "$seg_log" - "${cmd[@]}" |& tee -a "$seg_log" + echo "${cmd[@]}" 2>&1 | tee -a "$seg_log" + "${cmd[@]}" 2>&1 | tee -a "$seg_log" if [[ "${PIPESTATUS[0]}" -ne 0 ]] then echo "ERROR: asegdkt statsfile generation failed" | tee -a "$seg_log" @@ -817,7 +817,7 @@ if [[ "$run_seg_pipeline" == "1" ]] cereb_flags=("${cereb_flags[@]}" --norm_name "$norm_name" --cereb_statsfile "$cereb_statsfile") else - echo "INFO: Running CerebNet without generating a statsfile, since biasfield correction deactivated '--no_biasfield'." |& tee -a $seg_log + echo "INFO: Running CerebNet without generating a statsfile, since biasfield correction deactivated '--no_biasfield'." 2>&1 | tee -a $seg_log fi cmd=($python "$cerebnetdir/run_prediction.py" --t1 "$t1" @@ -827,11 +827,11 @@ if [[ "$run_seg_pipeline" == "1" ]] --threads "$threads" "${cereb_flags[@]}" "${allow_root[@]}") # specify the subject dir $sd, if asegdkt_segfile explicitly starts with it if [[ "$sd" == "${cereb_segfile:0:${#sd}}" ]] ; then cmd=("${cmd[@]}" --sd "$sd"); fi - echo "${cmd[@]}" |& tee -a "$seg_log" + echo "${cmd[@]}" 2>&1 | tee -a "$seg_log" "${cmd[@]}" if [[ "${PIPESTATUS[0]}" -ne 0 ]] then - echo "ERROR: Cerebellum Segmentation failed" |& tee -a "$seg_log" + echo "ERROR: Cerebellum Segmentation failed" 2>&1 | tee -a "$seg_log" exit 1 fi fi @@ -850,7 +850,7 @@ if [[ "$run_surf_pipeline" == "1" ]] cmd=("./recon-surf.sh" --sid "$subject" --sd "$sd" --t1 "$conformed_name" --asegdkt_segfile "$asegdkt_segfile" --threads "$threads" --py "$python" "${surf_flags[@]}" "${allow_root[@]}") - echo "${cmd[@]}" |& tee -a "$seg_log" + echo "${cmd[@]}" 2>&1 | tee -a "$seg_log" "${cmd[@]}" if [[ "${PIPESTATUS[0]}" -ne 0 ]] ; then exit 1 ; fi popd || return