#!/usr/bin/tcsh -f
#
# seg2recon - create and populate a subjects dir given the seg and
# input vol in a way that recon-all can be run on it. This is similar
# to samseg2recon but provides for bias field correction (which is
# already done by samseg). 

set VERSION = 'seg2recon mockbuild-local';

if(-e $FREESURFER_HOME/sources.csh) then
  source $FREESURFER_HOME/sources.csh
endif

set segvol = ();
set subject = ();
set input = ();
set mask = ()
set headmask = ()
set ctab = ();
set ndilate = 2;
set XOptsFiles = ();
set ctabdefault = $FREESURFER_HOME/FreeSurferColorLUT.txt
set DoCC = 1;    # Seg Corpus callosum
set DoRCA = 0;
set tmpdir = ()
set LF = ()
set cleanup = 1
set ForceUpdate = 0; 
set thresh = 1.2;
set DoBiasFieldCor = 1
# Extracerebral segs (samseg and charm)
set xcersegs = (165 258 259 85 501 502 506 507 508 509 511 512 514 404 516 517 530)

set inputargs = ($argv);
set PrintHelp = 0;
if($#argv == 0) goto usage_exit;
set n = `echo $argv | grep -e -help | wc -l` 
if($n != 0) then
  set PrintHelp = 1;
  goto usage_exit;
endif
set n = `echo $argv | grep -e -version | wc -l` 
if($n != 0) then
  echo $VERSION
  exit 0;
endif
goto parse_args;
parse_args_return:
goto check_params;
check_params_return:

set StartTime = `date`;
set tSecStart = `date '+%s'`;
set year  = `date +%Y`
set month = `date +%m`
set day   = `date +%d`
set hour   = `date +%H`
set min    = `date +%M`

mkdir -p $SUBJECTS_DIR/$subject/scripts 
mkdir -p $SUBJECTS_DIR/$subject/mri/transforms
mkdir -p $SUBJECTS_DIR/$subject/mri/tmp

if($#tmpdir == 0) then
  if(-dw /scratch)   set tmpdir = /scratch/tmpdir.seg2recon.$$
  if(! -dw /scratch) set tmpdir = $SUBJECTS_DIR/$subject/mri/tmp/tmpdir.seg2recon.$$
endif
mkdir -p $tmpdir

# Set up log file
if($#LF == 0) set LF = $SUBJECTS_DIR/$subject/scripts/seg2recon.log
if($LF != /dev/null) rm -f $LF
echo "Log file for seg2recon" >> $LF
date  | tee -a $LF
echo "" | tee -a $LF
echo "setenv 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
if($?PBS_JOBID) then
  echo "pbsjob $PBS_JOBID"  >> $LF
endif

#========================================================

set mdir = $SUBJECTS_DIR/$subject/mri

# If input volume is not isotropic, then there will be downstream failures.
# Need to resolve the failures or conform-to-min from the start.
# Copy the input. Eventually will probably want to have arbitrary
# inputs and will have to conform it.
set ud = `UpdateNeeded $mdir/orig.mgz $input`
if($ud || $ForceUpdate) then
  set cmd = (mri_convert $input $mdir/orig.mgz)
  echo $cmd | tee -a $LF
  $cmd | tee -a $LF
  if($status) exit 1;
else
  echo "$mdir/orig.mgz does not need updating"  | tee -a $LF
endif

# create a link from the orig.mgz to rawavg.mgz, needed for pctsurfcon
if(! -e $mdir/rawavg.mgz) then
  pushd $mdir
  set cmd = (ln -sf orig.mgz rawavg.mgz)
  echo $cmd | tee -a $LF
  $cmd | tee -a $LF
  if($status) goto error_exit;
  popd
endif

# Copy seg into aseg.auto_noCCseg.mgz 
set ud = `UpdateNeeded $mdir/aseg.auto_noCCseg.mgz $segvol`
if($ud || $ForceUpdate) then
  # odt must be int because extracerebral seg ids are > 255
  set cmd = (mri_convert $segvol $mdir/aseg.auto_noCCseg.mgz -odt int --no_scale 1)
  if($#ctab) set cmd = ($cmd --ctab $ctab)
  echo $cmd | tee -a $LF
  fs_time $cmd | tee -a $LF
  if($status) goto error_exit;
else
  echo "$mdir/aseg.auto_noCCseg.mgz does not need updating" | tee -a $LF
endif

# Create a head mask to speed bias fitting. Need whole head for
# talairach reg which may later be used for ICV
if($#headmask == 0) then
  set headmask = $mdir/head.mgz
  set ud = `UpdateNeeded $headmask $segvol $input`
  if($ud || $ForceUpdate) then
    set xopts = `fsr-getxopts mri_seghead $XOptsFiles`
    set cmd = (mri_seghead --invol $input --outvol $headmask \
      --fill 1 --thresh1 20 --thresh2 20 --nhitsmin 2 --rescale \
      --no-fill-holes-islands --or-mask $segvol)
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
  else
    echo "$headmask does not need updating" | tee -a $LF
  endif
endif

# Fit the bias field and apply bias correction. This is one of the
# main functions of this script since many of the segmentation
# techniques (eg, synthseg, fastsurfer) do not provide bias field
# correction. mri_fit_bias takes the log of the input, then fits a GLM
# with DCT as regressors.
set nu = $mdir/nu.mgz
if($DoBiasFieldCor) then
  set biasfield = $mdir/biasfield.mgz
  set ud = `UpdateNeeded $nu $segvol $input $headmask`
  if($ud || $ForceUpdate) then
    set xopts = `fsr-getxopts mri_fit_bias $XOptsFiles`
    set cmd = (mri_fit_bias --i $input --seg $segvol --mask $headmask \
      --bias $biasfield --threads $threads --o $nu --thresh $thresh $xopts)
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
  else
    echo "$nu does not need updating" | tee -a $LF
  endif
else
  echo "Not applying bias field correction" | tee -a $LF
  pushd $mdir
  ln -s orig.mgz nu.mgz
  popd
endif

# Run tal registration. This could be done in recon-all, but recon-all
# tal will run N4 to create orig_nu.mgz. Since we already have a
# bf-corrected volume, we do it here. May need to refactor for edits.
# Must use whole head here for ICV purposes.
set xfma = $mdir/transforms/talairach.xfm
set ud = `UpdateNeeded $xfma $nu`
if($ud || $ForceUpdate) then
  set xopts = `fsr-getxopts talairach_avi $XOptsFiles`;
  # note: have to be in the same folder so just use nu.mgz, etc
  pushd $mdir
  set cmd = (talairach_avi --i nu.mgz --xfm transforms/talairach.xfm $xopts)
  echo $cmd | tee -a $LF
  $cmd |& tee -a $LF
  if($status) goto error_exit;
  popd
  # Not sure I should create this here in case of edits
  set lta = $mdir/transforms/talairach.xfm.lta
  set mni305 = $FREESURFER_HOME/average/mni305.cor.mgz
  set cmd = (lta_convert --src $nu --trg $mni305 --inxfm $xfma \
      --outlta $lta --subject fsaverage --ltavox2vox)
  echo $cmd |& tee -a $LF
  $cmd |& tee -a $LF
  if($status) goto error_exit;
  pushd $mdir/transforms
  if(! -e talairach.lta) ln -sf talairach.xfm.lta talairach.lta
  popd
endif

if($#mask == 0) then # for brain masking
  set mask = $mdir/seg.bin.dil$ndilate.mgz
  #set cmd = (mri_binarize --i $segvol --min 0.5 --dilate $ndilate --o $mask) 
  #Create mask by binarizing everything outside of the brain then
  # inverting rather than simply thresholding what is in the
  # seg. This allows for segmentations with extracerebral segs (like
  # samseg and charm). But the extracerebral segs need to be within
  # the xcercegs list. Need to make this configurable.
  set cmd = (mri_binarize --i $segvol --match 0 $xcersegs --inv --dilate $ndilate --o $mask)
  echo $cmd | tee -a $LF
  set ud = `UpdateNeeded $mask $segvol`
  if($ud || $ForceUpdate) then
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
  else
    echo "$mask does not need updating" | tee -a $LF
  endif
endif

# Create the norm simply by masking the bias field corrected nu. 
# This works for samseg, but maybe not so great for synthseg.
set norm = $mdir/norm.mgz
set ud = `UpdateNeeded $norm $nu $mask`
if($ud || $ForceUpdate) then
  set cmd = (mri_mask $nu $mask $norm)
  fs_time $cmd | tee -a $LF
  if($status) goto error_exit;
else
  echo "$nu does not need updating" | tee -a $LF
endif

# link norm to brainmask; brain will be created by recon-all
pushd $mdir
if(! -e T1.mgz) then
  set cmd = (ln -sf nu.mgz T1.mgz)
  echo $cmd | tee -a $LF
  $cmd | tee -a $LF
  if($status) goto error_exit;
endif
foreach v (brainmask.mgz)
  # This is redundant when synthstrip is used in recon-all
  if(-e $v) continue
  set cmd = (ln -sf norm.mgz $v)
  echo $cmd | tee -a $LF
  $cmd | tee -a $LF
  if($status) goto error_exit;
end
popd

# Create aseg.auto.mgz (which has corpus callosum). This needs to be
# run outside of recon-all because in recon-all it is done as part of
# ca_label (should be refactored)
pushd $mdir
set ud = `UpdateNeeded aseg.auto.mgz aseg.auto_noCCseg.mgz`
if($DoCC && $ud) then
  set tmpfile = $tmpdir/isconformed
  mri_info --o $tmpfile --conformed-to-min aseg.auto_noCCseg.mgz > /dev/null
  set isconf = `cat $tmpfile | head -n 1`
  echo IsConformed $isconf | tee -a $LF
  if($isconf == yes) then
    set cmd = (mri_cc -aseg aseg.auto_noCCseg.mgz -o aseg.auto.mgz -lta transforms/cc_up.lta $subject)
    date | tee -a $LF
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
    date | tee -a $LF
  else
    # If it is not conformed, have to go through some gymnastics because mri_cc
    # needs things to be conformed
    # Conform the norm and aseg
    set cmd = (mri_convert norm.mgz norm.conf.mgz --conform_min)
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
    set cmd = (mri_label2vol --seg aseg.auto_noCCseg.mgz --temp norm.conf.mgz --regheader --o aseg.auto_noCCseg.conf.mgz)
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
    # Now run mri_cc. Not sure if cc_up.lta will be valid (or needed)
    set cmd = (mri_cc -norm norm.conf.mgz -aseg aseg.auto_noCCseg.conf.mgz \
      -o aseg.auto.conf.mgz -lta transforms/cc_up.lta $subject)
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
    # Now map it back to the non-conformed space
    set cmd = (mri_label2vol --seg aseg.auto.conf.mgz --temp norm.mgz --regheader --o aseg.auto.noconf.mgz)
    echo $cmd | tee -a $LF
    fs_time $cmd | tee -a $LF
    if($status) goto error_exit;
    # Merge the CC labels back into the original aseg. Do not use the non-conf volume above because
    # moving into and out of 1mm space may create a problem
    foreach segid (251 252 253 254 255)
      set cmd = (mri_binarize --i aseg.auto.noconf.mgz --match $segid --o $tmpdir/ccbin.mgh)
      echo $cmd | tee -a $LF
      fs_time $cmd | tee -a $LF
      if($status) goto error_exit;
      if($segid == 251) then
        set src = aseg.auto_noCCseg.mgz
      else
        set src = aseg.auto.mgz
      endif
      set cmd = (mergeseg --src $src --merge $tmpdir/ccbin.mgh --o aseg.auto.mgz --segid $segid)
      set cmd = ($cmd --tmpdir $tmpdir/tmp.mergeseg --ctab $ctabdefault)
      if($cleanup) set cmd = ($cmd --cleanup)
      echo $cmd | tee -a $LF
      fs_time $cmd | tee -a $LF
      if($status) goto error_exit;
    end
  endif
else
  echo "Not segmenting CC" | tee -a $LF
  # will the cc_up.lta be needed at some point?
  set cmd = (cp aseg.auto_noCCseg.mgz aseg.auto.mgz)
  date | tee -a $LF
  echo $cmd | tee -a $LF
  fs_time $cmd | tee -a $LF
  if($status) goto error_exit;
  date | tee -a $LF
endif
popd

# Cleanup
if($cleanup) rm -rf $tmpdir

if($DoRCA) then
  # added this for convenience in testing
  date | tee -a $LF
  echo "Running recon-all" | tee -a $LF
  set cmd = (recon-all -s $subject -autorecon2-samseg -autorecon3 -threads $threads)
  echo $cmd | tee -a $LF
  fs_time $cmd | tee -a $LF
  if($status) goto error_exit;
endif

# Done
echo " " |& tee -a $LF
set tSecEnd = `date '+%s'`;
@ tSecRun = $tSecEnd - $tSecStart;
set tRunHours = `echo $tSecRun/3600|bc -l`
set tRunHours = `printf %5.2f $tRunHours`
echo "Started at $StartTime " |& tee -a $LF
echo "Ended   at `date`" |& tee -a $LF
echo "Seg2recon-Run-Time-Sec $tSecRun" |& tee -a $LF
echo "Seg2recon-Run-Time-Hours $tRunHours" |& tee -a $LF
echo " " |& tee -a $LF
echo "seg2recon Done" |& tee -a $LF
exit 0

###############################################

############--------------##################
error_exit:
echo "ERROR: $cmd"
exit 1;
###############################################

############--------------##################
parse_args:
set cmdline = ($argv);
while( $#argv != 0 )

  set flag = $argv[1]; shift;
  
  switch($flag)

    case "--seg":
      if($#argv < 1) goto arg1err;
      set segvol = $argv[1]; shift;
      breaksw

    case "--s":
      if($#argv < 1) goto arg1err;
      set subject = $argv[1]; shift;
      breaksw

    case "--i":
      if($#argv < 1) goto arg1err;
      set input = $argv[1]; shift;
      breaksw

    case "--ctab":
      if($#argv < 1) goto arg1err;
      set ctab = $argv[1]; shift;
      breaksw

    case "--threads":
      if($#argv < 1) goto arg1err;
      set threads = $argv[1]; shift;
      breaksw

    case "--ndilate":
      if($#argv < 1) goto arg1err;
      set ndilate = $argv[1]; shift;
      breaksw

    case "--no-bias-field-cor":
    case "--no-bfc":
      set DoBiasFieldCor = 0
      breaksw
    case "--bias-field-cor":
    case "--bfc":
      set DoBiasFieldCor = 1
      breaksw

    case "--m":
      if($#argv < 1) goto arg1err;
      set mask = $argv[1]; shift;
      if(! -e $mask) then
        echo "ERROR: cannot find $mask"
        exit 1;
      endif
      set mask = `getfullpath $mask`
      breaksw

    case "--h":
      if($#argv < 1) goto arg1err;
      set headmask = $argv[1]; shift;
      if(! -e $headmask) then
        echo "ERROR: cannot find $headmask"
        exit 1;
      endif
      set headmask = `getfullpath $headmask`
      breaksw

    case "--thresh":
      if($#argv < 1) goto arg1err;
      set thresh = $argv[1]; shift;
      breaksw

    case "--expert":
      if( $#argv < 1) goto arg1err;
      set XOptsFile = $argv[1]; shift;
      fsr-checkxopts $XOptsFile
      if($status) goto error_exit;
      set XOptsFiles = ($XOptsFiles `getfullpath $XOptsFile`)
      breaksw

    case "--cc":
      set DoCC = 1;
      breaksw
    case "--no-cc":
      set DoCC = 0;
      breaksw

    case "--rca":
      set DoRCA = 1;
      breaksw
    case "--no-rca":
      set DoRCA = 0;
      breaksw

    case "--log":
      if($#argv < 1) goto arg1err;
      set LF = $argv[1]; shift;
      breaksw

    case "--nolog":
    case "--no-log":
      set LF = /dev/null
      breaksw

    case "--no-force-update":
      set ForceUpdate = 0;
      breaksw
    case "--force-update":
      set ForceUpdate = 1;
      breaksw

    case "--tmp":
    case "--tmpdir":
      if($#argv < 1) goto arg1err;
      set tmpdir = $argv[1]; shift;
      set cleanup = 0;
      breaksw

    case "--nocleanup":
      set cleanup = 0;
      breaksw

    case "--cleanup":
      set cleanup = 1;
      breaksw

    case "--debug":
      set verbose = 1;
      set echo = 1;
      breaksw

    default:
      echo ERROR: Flag $flag unrecognized. 
      echo $cmdline
      exit 1
      breaksw
  endsw

end

goto parse_args_return;
############--------------##################

############--------------##################
check_params:

if($#subject == 0) then
  echo "ERROR: must supply subject"
  exit 1;
endif
if($#segvol == 0) then
  echo "ERROR: must supply seg"
  exit 1;
endif
if($#input == 0) then
  echo "ERROR: must supply input"
  exit 1;
endif
if($#ctab == 0) then
  set cmd = (mri_info --ctab --o /tmp/seg2recon.ctab.$$ $segvol)
  $cmd
  if($status) exit 1
  set n = `wc -l /tmp/seg2recon.ctab.$$`
  if($n[1] == 0) then
    echo "ctab not specified and no ctab in seg, so using $ctabdefault"
    set ctab = $ctabdefault
  endif
  rm -f /tmp/seg2recon.ctab.$$
endif
foreach f ($input $segvol $ctab $mask)
  if(! -e $f) then
    echo "ERROR: cannot find $f"
    exit 1;
  endif
end

set input  = `getfullpath $input`
set segvol = `getfullpath $segvol`
if($#ctab) set ctab   = `getfullpath $ctab`
if($#mask) set mask = `getfullpath $mask`

goto check_params_return;
############--------------##################

############--------------##################
arg1err:
  echo "ERROR: flag $flag requires one argument"
  exit 1
############--------------##################
arg2err:
  echo "ERROR: flag $flag requires two arguments"
  exit 1
############--------------##################

############--------------##################
usage_exit:
  echo ""
  echo "seg2recon"
  echo "  --s subject : output"
  echo "  --seg segvol : aseg-type volume, eg, from synthseg, fastsurfer, psacnn, samseg, or aseg"
  echo "  --i inputvol : what you would pass to recon-all"
  echo "  --ctab ctab  : ctab for the seg (will use embedded if there or FreeSurferColorLUT.txt if not spec)"
  echo "  --ndilate  : dilate binarization of seg when creating brainmask ($ndilate)"
  echo "  --threads nthreads"
  echo "  --force-update : force regeneration of files whether needed or not (default is --no-force-update)"
  echo "  --no-cc : do not seg corpus callosum (default is --cc)"
  echo "  --m mask : use mask as brainmask instead of computing from seg"
  echo "  --h headmask : use headmask instead of running mri_seghead"
  echo "  --thresh thresh : threshold for bias field estimation"
  echo "  --expert xoptsfile <--expert xoptsfile>"
  echo "  --rca : run recon-all on the output (good for testing)"
  echo "  --no-bias-field-cor (--no-bfc) : do not compute or apply bias field correction" 
  echo ""

  if(! $PrintHelp) exit 1;
  echo $VERSION
  cat $0 | awk 'BEGIN{prt=0}{if(prt) print $0; if($1 == "BEGINHELP") prt = 1 }'
exit 1;

#---- Everything below here is printed out as part of help -----#
BEGINHELP

Creates and populates a subjects dir from an input image and seg in a
way that recon-all can be run on it. It will propogate the seg to
aseg.auto_noCCseg.mgz and will run mri_cc to add the corpus callosum
to it to create aseg.auto.mgz as output. It fits and removes the bias
field to create the nu.mgz (to which T1.mgz is linked). It creates a
brain mask by binarizing and dilating the segmentation. The norm.mgz
and brainmask.mgz are the nu.mgz masked by the brain mask. The bias is
fit inside of a head mask.  Computes talairach.xfm from the nu.mgz
using talairach_avi. No talairach.m3z is created.

After completion, recon-all can be run something like
recon-all -s subject -autorecon2-samseg -autorecon3 







