#/bin/tcsh
# initializations
set maskset = ""
set surfset = ""
set thickdir = "bb_thickdir"
set increment = ""
set maxthick = ""
set resample = "auto"
set surfsmooth = "6"
set smoothmm = ""
set depthsearch = "3"
set inval = ""
set outval = ""
set maskval = ""
set keep_temp_files = ""
set balls_only = ""
set heat_method = "HEAT_07"

if ($# < 1) then
HELP:
    echo "@measure_bb_thick - compute thickness of mask using ball and box method"
    echo "usage:"
    echo "@measure_bb_thick -maskset maskset -surfset surfacedset.gii -outdir thickdir"
    echo
    echo "where maskset is the dataset to find thickness"
    echo " using the largest non-zero value in the mask." 
    echo " If dataset has values -2,-1 and 1 for different regions, this script"
    echo " calculates the thickness only for voxels with a value of 1"
    echo "surfset is a surface to use to find normals into the volume"
    echo "output is in directory thickdir. If not specified, bb_thickdir is used"
    echo
    echo 'This script finds thickness by finding the largest sphere or cube that fits'
    echo 'within the mask at each voxel. The cube search has little effect on'
    echo 'surface mapping of thickness, affecting only some edges in the volume.'
    echo 'If one is primarily interested in the surface mapping, then consider'
    echo 'the -balls_only to skip the cube search.'
    echo
    echo "Because of limitations in the growth of the spheres used in this method,"
    echo "it is recommended to use oversampled data, particularly when using 1mm data"
    echo "See -resample option below"
    echo
    echo "Main options:"
    echo "  -maskset mydset      mask dataset for input"
    echo "  -surfset mydset.gii  surface dataset onto which to map thickness"
    echo "                       (probably a pial/gray matter surface)"
    echo "  -outdir thickdir     output directory"
    echo
    echo "Other options:"
    echo
    echo "  -resample mm   resample input to mm in millimeters (put a number here)"
    echo '                 set this to half a voxel or \"auto\".'
    echo "                 No resampling is done by default"
    echo "                 Resampling is highly recommended for most 1mm data"
    echo "  -increment mm  test thickness at increments of sub-voxel distance"
    echo "                 default is 1/4 voxel minimum distance (in-plane)"
    echo "  -surfsmooth mm smooth surface map of thickness by mm millimeters"
    echo "                 default is 6 mm"
    echo "  -smoothmm mm  smooth volume by mm FWHM in mask"
    echo "                 default is 2*voxelsize of mask or resampled mask"
    echo "  -maxthick mm   search for maximum thickness value of mm millimeters"
    echo "                 default is 6 mm"
    echo "  -depthsearch mm map to surface by looking for max along mm millimeter"
    echo "                 normal vectors. default is 3 mm"
    echo "  -keep_temp_files do not delete the intermediate files (for testing)"
    echo "  -balls_only    calculate only with spheres and skip boxes"
    echo "  -surfsmooth_method heattype heat method used for smoothing surfaces"
    echo "                 default is HEAT_07 but HEAT_05 is also useful for models"
    echo
    echo "Output:"
    echo "   maxfill.nii.gz - thickness/depth dataset"
    echo "   bb_thick.nii.gz - volumetric thickness dataset"
    echo "   bb_thick_smooth.nii.gz - smoothed volumetric thickness dataset"
    echo "   bb_thick.niml.dset - unsmoothed thickness mapped to surface nodes"
    echo "   bb_thick_smooth.niml.dset - smoothed thickness mapped to surface nodes"
    echo
    echo "   Other datasets included in output:"
    echo "   maskset.nii.gz, maskset_rs.nii.gz - mask and optional resampled mask"
    echo "   anat.gii - surface representation of mask volume"
    echo "   quick.spec - simple specification file for surface to use with suma commands"
    echo
    echo "See related scripts and programs for computing thickness:"
    echo "    @measure_in2out, @measure_erosion_thick and SurfMeasures"
    exit
endif

# process user options
set ac = 1
while ( $ac <= $#argv )
    # maybe just a desperate plea for help
    if ( ( "$argv[$ac]" == "-help" ) || ( "$argv[$ac]" == "-HELP" )  || \
       ( "$argv[$ac]" == "--help" ) || ( "$argv[$ac]" == "-h" ) ) then
       goto HELP
    # get the basics - input dataset, surface and output directory
    # only the input is really required, but the other two are nice to have
    else if ( "$argv[$ac]" == "-maskset" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-maskset'"
            exit 1
        endif
        set  maskset = $argv[$ac]
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-surfset" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-surfset'"
            exit 1
        endif
        set  surfset = $argv[$ac]
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-outdir" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-outdir'"
            exit 1
        endif
        set  thickdir = $argv[$ac]
        @ ac ++; continue;
        
    # now tweak the options    
    else if ( "$argv[$ac]" == "-resample" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-resample'"
            exit 1
        endif
        set resample = $argv[$ac]
        if ("$resample" == "auto") then
           echo "resampling set to auto"
        else
           set resample = `ccalc -expr "$resample"`
           if ("$status" != "0") then
              echo "resample set to $argv[$ac] is not valid"
              exit 1
           endif 
           echo "resample voxel size to $resample mm"       
        endif
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-increment" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-increment'"
            exit 1
        endif
        set  increment = $argv[$ac]
       set increment = `ccalc -expr "$increment"`
       if ("$status" != "0") then
         echo "increment set to $argv[$ac] is not valid"
         exit 1
       endif        
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-maxthick" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-maxthick'"
            exit 1
        endif
        set  maxthick = $argv[$ac]
       set maxthick = `ccalc -expr "$maxthick"`
       if ("$status" != "0") then
         echo "maxthick set to $argv[$ac] is not valid"
         exit 1
       endif
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-surfsmooth" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-surfsmooth'"
            exit 1
        endif
        set surfsmooth = $argv[$ac]
       set surfsmooth = `ccalc -expr "$surfsmooth"`
       if ("$status" != "0") then
         echo "surfsmooth set to $argv[$ac] is not valid"
         exit 1
       endif
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-surfsmooth_method" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-surfsmooth'"
            exit 1
        endif
        set heat_method = $argv[$ac]
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-smoothmm" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-smoothmm'"
            exit 1
        endif
        set smoothmm = $argv[$ac]
       set smoothmm = `ccalc -expr "$smoothmm"`
       if ("$status" != "0") then
         echo "smoothmm set to $argv[$ac] is not valid"
         exit 1
       endif
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-depthsearch" ) then
        @ ac ++
        if ( $ac > $#argv ) then
            echo "** missing parameter for option '-depthsearch'"
            exit 1
        endif
        set depthsearch = $argv[$ac]
       set depthsearch = `ccalc -expr "$depthsearch"`
       if ("$status" != "0") then
         echo "depthsearch set to $argv[$ac] is not valid"
         exit 1
       endif
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-keep_temp_files" ) then
        set keep_temp_files = "keep"
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-balls_only" ) then
        set balls_only = "true"
        @ ac ++; continue;
    else if ( "$argv[$ac]" == "-ignore_unknown_options" ) then
        set ignore_unknown_options = "yes"
        @ ac ++; continue;

    # unknown options - exit
    else 
        if ("$ignore_unknown_options" != "yes") then
          echo "** unknown option $argv[$ac]"
          exit 1
        endif
    endif
    @ ac ++
end

if ("$maskset" == "") then
   echo 'No input dataset. Please provide "-maskset mydset.nii" '
   exit 1
endif

# get voxel size - minimum of 2D square at least in-plane (should use cubic voxels though)
# but this could potentially work for 2D images too ("thick" 2D images)
set v3 = `3dinfo -d3 $maskset`
set voxsize = `ccalc "min(abs($v3[1]),abs($v3[2]))"`
# starting radius in mm
set inirad = $voxsize

# maximum radius in mm (not absolutely necessary for this method)
if ("$maxthick" == "") then
   set maxrad = 6
else
   set maxrad = `ccalc -expr "$maxthick/2+0.01"`
endif

# do everything in a new directory for simplicity
mkdir $thickdir

# put original dataset into output directory with specific name 
#  this is easier and allows for mask range selectors, sub-brick selectors, and NIFTI
3dTcat -prefix $thickdir/allmaskset.nii.gz -overwrite $maskset
if ($surfset != "") then
    ConvertSurface -i $surfset -o $thickdir/anat.gii
endif

cd $thickdir
# get rid of any previous results if directory already exists
rm fill_r*.nii.gz fill_c*.nii.gz
set maskset = maskset.nii.gz
# if there are multiple values in the mask dataset
# then assume the lower values are inside and outside masks for now
set vrange = `3dBrickStat -non-zero -min -max allmaskset.nii.gz`
if ($vrange[1] != $vrange[2]) then
   # can do in-out, but use a simpler mask for the other methods
   3dTcat -overwrite -prefix maskset.nii.gz allmaskset.nii.gz"<${vrange[2]}>" 
else
   # just a single value mask - can't do in-out calculation
   rm maskset.nii.gz
   3dcopy allmaskset.nii.gz maskset.nii.gz
endif

# check if resampling is desired
if ("$resample" != "") then
   # for the "auto" way, use 1/2 voxel size
   if ("$resample" == "auto") then
      set voxsize = `ccalc -expr "${voxsize}/2"`
   else
      set voxsize = $resample         
   endif
   3dresample -rmode NN -prefix maskset_rs.nii.gz -overwrite \
      -bound_type SLAB -dxyz $voxsize $voxsize $voxsize -input $maskset
   set maskset = maskset_rs.nii.gz
endif

# increment radial search in mm
#  if nothing set, use 1/4 voxels
if ("$increment" == "") then
   set radinc = $voxsize

   set r1 = `ccalc "0.25*$voxsize"`
   set r2 = `ccalc "0.5*$voxsize"`
   set r3 = `ccalc "0.75*$voxsize"`
   #set radoffs = ( 0.00 $edge_r $corner_r )
   set radoffs = ( 0.00 $r1 $r2 $r3 )
else
   set radinc = $increment
   set radoffs = ( 0.00 )
endif

set rad = $inirad
set radi = 0
set donext = 1
set radoff = 0
# try putting spheres down at different sizes to see what's the largest one that fits
while ($donext )
# took out radius offsets because thickness measurements are still
#  limited by voxel increments even if large neighborhoods could still be included
#  the smaller neighborhood still gets assigned the same thickness as the larger
#  one, and that voxel only gets assigned at the center voxel.
  foreach radoff ($radoffs)
     set rad = `ccalc -expr "$inirad + $radinc * $radi + $radoff"`
     set donext = `ccalc -int "step($maxrad -$rad)"`
     if ($donext != "0") then
        # thickness is double radius, add voxelsize to allow for full size past centers
        # may want to adjust for edge,corner too
        # set rthick = `ccalc "int($rad/$voxsize+0.001)*2*$voxsize+$voxsize"`
        set rthick = `ccalc "$rad*2+$voxsize"`
        3dLocalstat -nbhd "SPHERE($rad)" -stat filled -mask $maskset -fillvalue $rthick \
                    -prefix fill_r$rad.nii.gz -overwrite $maskset
        # all spheres MUST be filled. 
        # If the spheres are too big, then no spheres are found, and we can stop growing.
        set anytherer = `3dBrickStat -max fill_r$rad.nii.gz`
        set anythere = `ccalc -int "step($anytherer)"`
        if ($anythere == 0) then
           echo maximum thickness is $rthick
           set maxfound = $rad
           set donext = 0
           goto finish_sphere_search
        endif
     endif
  end
  @ radi ++
end
finish_sphere_search:

set rad = $inirad
set donext = 1
# skip cubes if balls only
if ("$balls_only" == "true") then
   set donext = 0
endif

# Now try putting cubes down instead at different sizes to see what's the largest one that fits
while ($donext )
  # calculate approx. thickness from radius - roughly equivalent to edge
  # consider the sqrt(2) or sqrt(3) as a factor here for diagonal
#  set rthick = `ccalc "int($rad/$voxsize+0.001)*2*$voxsize+$voxsize"`
  set rthick = `ccalc "$rad*2+$voxsize"`
  3dLocalstat -nbhd "RECT($rad,$rad,$rad)" -stat filled -mask $maskset -fillvalue $rthick \
     -prefix fill_c$rad.nii.gz -overwrite $maskset
  set anytherer = `3dBrickStat -max fill_c$rad.nii.gz`
  set anythere = `ccalc -int "step($anytherer)"`
  if ($anythere) then
     set rad = `ccalc  "$rad + $radinc"`
     set donext = `ccalc -int "step($maxrad -$rad)"`
  else
     echo maximum thickness of cubes is $rthick
     set maxfoundc = $rad
     set donext = 0
     goto finish_cube_search
  endif
end
finish_cube_search:

# find the biggest sphere that fits at any voxel
# max of spheres
if ("$balls_only" == "true") then
   3dMean -max -prefix tmaxfill.nii.gz -overwrite fill_r*.nii.gz
else
   3dMean -max -prefix _tt_maxfill_spheresonly.nii.gz -overwrite fill_r*.nii.gz
   3dcalc -a _tt_maxfill_spheresonly.nii.gz -b $maskset \
      -prefix maxfill_spheresonly.nii.gz -overwrite \
      -expr  "step(b)*max(a,$voxsize)"

   # max of cubes 
   3dMean -max -prefix _tt_maxfill_cubesonly.nii.gz -overwrite fill_c*.nii.gz
   3dcalc -a _tt_maxfill_cubesonly.nii.gz -b $maskset \
      -prefix maxfill_cubesonly.nii.gz -overwrite \
      -expr  "step(b)*max(a,$voxsize)"
   # max of spheres and cubes together
   3dMean -max -prefix tmaxfill.nii.gz -overwrite maxfill_spheresonly.nii.gz maxfill_cubesonly.nii.gz
endif

# all voxels should have at least a thickness of a voxel size 
#   (don't need a 3dLocalstat for that)
3dcalc -a tmaxfill.nii.gz -b $maskset -expr "step(b)*max(a,$voxsize)" \
 -prefix maxfill.nii.gz -overwrite


# now put spheres back down at each coordinate starting at the maximum radius
# write out all the coordinates in the mask with the current thickness
3dmaskdump -mask maxfill.nii.gz -nozero maxfill.nii.gz > sphere_centers_pre.1D

# sort coordinates from smallest to largest thickness
sort -n -k 4 sphere_centers_pre.1D > sphere_centers_sorted.1D
# calculate radius again - need this for 3dUndump 
1deval -a sphere_centers_sorted.1D'[3]' -expr "(a-${voxsize})/2" > radii.1D 
# 1D file is "i j k thickness radius" sorted by radius 
1dcat sphere_centers_sorted.1D radii.1D > sphere_centers.1D

if ("$balls_only" == "true") then
   set thickvol = bb_thick.nii.gz 
else
   set thickvol = bb_spheresonly_thick.nii.gz
endif

# fill spheres around centers with 1/2 thickness radii and values of the thickness
# minimum value in mask should be voxsize
3dUndump -master $maskset -overwrite -prefix $thickvol \
    -datum float -mask $maskset \
    -srad 1.0 -dval $voxsize -ijk sphere_centers.1D

# do similar process for boxes
if ("$balls_only" != "true") then
    # put boxes back down at each coordinate starting at the maximum radius
    # write out all the coordinates in the mask with the current thickness
    3dmaskdump -mask maxfill_cubesonly.nii.gz -nozero maxfill_cubesonly.nii.gz > cube_centers_pre.1D
    
    # sort coordinates from smallest to largest thickness
    sort -n -k 4 cube_centers_pre.1D > cube_centers_sorted.1D
    # calculate radius again - need this for 3dUndump 
    1deval -a cube_centers_sorted.1D'[3]' -expr "(a-${voxsize})/2" > radii.1D 
    # 1D file is "i j k thickness radius" sorted by radius 
    1dcat cube_centers_sorted.1D radii.1D > cube_centers.1D
    
    # fill cubes around centers with 1/2 thickness radii and values of the thickness
    # minimum value in mask should be voxsize
    3dUndump -master $maskset -overwrite -prefix bb_cubesonly_thick.nii.gz \
        -datum float -mask $maskset -cubes \
        -srad 1.0 -dval $voxsize -ijk cube_centers.1D

    # big boxes beat out little balls to fill corners!
    3dMean -max -overwrite -prefix bb_thick.nii.gz bb_cubesonly_thick.nii.gz bb_spheresonly_thick.nii.gz
endif


# smooth these out in the volume (mean, median, mode all work, but mean gives smoothest result here)
#3dLocalstat -stat mean -nbhd "SPHERE(1)" -mask $maskset \
#    -prefix bb_thick_smooth.nii.gz -overwrite bb_thick.nii.gz

# smooth volume in mask
if ("$smoothmm" == "") then
   set smoothmm = `ccalc "$voxsize*2"`
endif
3dBlurInMask -overwrite -FWHM $smoothmm -prefix  bb_thick_smooth.nii.gz -mask $maskset -input bb_thick.nii.gz

# do some surface stuff if we have a surface to work with
#  could make one with IsoSurface, but if it's already done, we can use that instead
if($surfset != "") then
   quickspec -tn gii anat.gii -spec quick.spec
   # try some surface-volume interaction methods also to extract the cortical thickness
   # one way is to look for the mean just a little on the interior
   # because it has already been filled by spheres, do no have to look too deep
# the surface representation from the thickness volume is blotchier than from the
# thickness/depth volume, so I've commented out these representations to
# avoid user confusion with too many output datasets
# Could do this optionally in the future
   #3dVol2Surf -spec quick.spec -surf_A anat.gii -sv $maskset \
     #-grid_parent bb_thick_smooth.nii.gz -gp_index 0 -map_func max \
     #-f_steps 10 -f_index nodes \
     #-cmask "-a $maskset<$maskval> -expr astep(a,0.000000)" \
     #-oob_value $voxsize -oom_value $voxsize \
     #-use_norms -norm_len -$depthsearch \
     #-dnode -1 -out_niml bb_thick_max.niml.dset -overwrite

   ## smooth data on surface 
   ## (HEAT_05 method is more robust from quick tests than HEAT_07 method
   ##  recommended in help for SurfSmooth)
   #SurfSmooth -spec quick.spec -surf_A anat.gii -met HEAT_05  \
     #-input bb_thick_max.niml.dset -fwhm $surfsmooth \
     #-output bb_thick_max_smooth_${surfsmooth}_mm.niml.dset -overwrite

# as long as we don't go too deep into the volume, this works well
   # another way is to use the max radii dset and search for the max going along a normal
   #  from the surface. This requires looking a little deeper, and maybe too deep

   # how many steps along normal vector for surface mapping
   set norm_step_size = `ccalc -int "1.5*$depthsearch/$voxsize"`

   3dVol2Surf -spec quick.spec -surf_A anat.gii -sv $maskset \
     -grid_parent maxfill.nii.gz -gp_index 0 -map_func max \
     -f_steps $norm_step_size -f_index nodes \
     -cmask "-a maxfill.nii.gz -expr astep(a,0.000000)" \
     -oob_value $voxsize -oom_value $voxsize \
     -use_norms -norm_len -$depthsearch \
     -dnode -1 -out_niml bb_thick.niml.dset -overwrite
   # smooth data on surface 
   # (HEAT_05 method is more robust from quick tests than HEAT_07 method
   #  recommended in help for SurfSmooth but HEAT_07 gives smoother
   #  results that look reasonable)
   SurfSmooth -spec quick.spec -surf_A anat.gii -met $heat_method  \
     -input bb_thick.niml.dset -fwhm $surfsmooth \
     -output bb_thick_smooth.niml.dset -overwrite
endif 

# unless the user is a hoarder, trash the intermediate files
if ("$keep_temp_files" != "keep") then
   rm fill_r*.nii.gz fill_c*.nii.gz sphere_centers*.1D allmaskset.nii.gz \
      radii.1D tmaxfill*.nii.gz maxfill_*.nii.gz *.smrec \
      bb_cubesonly_thick.nii.gz bb_spheresonly_thick.nii.gz _tt*.nii.gz
endif

echo "#\!/bin/tcsh" > @show_thick
echo "setenv SUMA_Sym_I_Range NO" >> @show_thick
echo "suma -i anat.gii -drive_com " '"-com surf_cont -load_dset bb_thick_smooth.niml.dset \' >> @show_thick
echo '     -com surf_cont -Dim 0.4 -com surf_cont -I_range 0 5 \' >> @show_thick
echo '     -com viewer_cont -key b" &' >> @show_thick
echo "To show surface with thickness map, use this command:"
echo "   cd $thickdir; tcsh @show_thick"
echo "To see volume datasets of thickness, view bb_thick_smooth.nii.gz in the afni GUI"
