#!/usr/bin/env tcsh

@global_parse `basename $0` "$*" ; if ($status) exit 0

#set version   = "0.0";  set rev_dat   = "June 3, 2022"
# [PT]  creation of this script, which (at present) is a useful
#       precursor for EPI-anatomical alignment.
#
#set version   = "1.0";  set rev_dat   = "June 3, 2022"
# [PT]  add help file
#
#set version   = "1.1";  set rev_dat   = "July 4, 2022"
# [PT]  change how masking is turned off
#
#set version   = "1.2";  set rev_dat   = "Jan 29, 2024"
# [PT]  change how local_mask is parsed, so '-mask DSET' is 
#       properly handled with an initial dset copy to the wdir
#
set version   = "1.3";  set rev_dat   = "May 11, 2025"
# [PT]  add -sharpen_with_phi, to possibly insert 3dSharpen into
#       the processing stream
#
set version   = "1.4";  set rev_dat   = "July 2, 2025"
# [PT]  add -automask_opts and -automask_tool options, to be able
#       to be able to control automasking (which now also happens 
#       as its own separate step) with more refinement, if 
#       necessary
#
# ----------------------------------------------------------------

set abbrev = "LocalUni"

# ----------------------- set defaults --------------------------

set input     = ""
set opref     = ""
set odir      = "."

set pppp      = "`3dnewid -fun11`"
set wdir_base = "__wdir_${abbrev}"
set wdir_name = "${wdir_base}_${pppp}"

set local_perc = 50
set local_rad  = -3
set local_mask = "-automask"
set sharp_phi  = ""

set amask_opts = ""
set amask_tool = ""

set filt_thr   = 1.5

set overwrite  = ""

set DO_CLEAN  = 1                       # default: keep working dir

# ------------------- process options, a la rr ----------------------

if ( $#argv == 0 ) goto SHOW_HELP

set ac = 1
while ( $ac <= $#argv )
    # terminal options
    if ( ("$argv[$ac]" == "-h" ) || ("$argv[$ac]" == "-help" )) then
        goto SHOW_HELP
    endif
    if ( "$argv[$ac]" == "-ver" ) then
        goto SHOW_VERSION
    endif

    if ( "$argv[$ac]" == "-echo" ) then
        set echo

    # ----------- required
    else if ( "$argv[$ac]" == "-input" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set input = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-prefix" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set opref = `basename "$argv[$ac]"`
        set odir  = `dirname  "$argv[$ac]"`

    # ----------- 3dLocalStat opts

    else if ( "$argv[$ac]" == "-local_perc" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set local_perc = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-local_rad" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set local_rad = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-sharpen_with_phi" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set sharp_phi = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-local_mask" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set local_mask = "$argv[$ac]"
        # special case to turn off masking
        if ( "${local_mask}" == "None" ) then
            set local_mask = ""
        endif

    else if ( "$argv[$ac]" == "-automask_opts" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set amask_opts = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-automask_tool" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set amask_tool = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-wdir_name" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set wdir_name = "$argv[$ac]"

        set check     = `echo "${wdir_name}" | grep '/'`
        if ( "${check}" != "" ) then
            echo "\n\n** ERROR: -wdir_name arg must not have '/':"
            echo "   '${wdir_name}'\n\n"
            goto BAD_EXIT
        endif

    else if ( "$argv[$ac]" == "-filter_thr" ) then
        if ( $ac >= $#argv ) goto FAIL_MISSING_ARG
        @ ac += 1
        set filt_thr = "$argv[$ac]"

    else if ( "$argv[$ac]" == "-overwrite" ) then
        set overwrite = "-overwrite"

    else if ( "$argv[$ac]" == "-no_clean" ) then
        set DO_CLEAN = 0
        
    else
        echo "\n\n** ERROR: unexpected option #$ac = '$argv[$ac]'\n\n"
        goto BAD_EXIT
        
    endif
    @ ac += 1
end

# =======================================================================
# ============================ ** SETUP ** ==============================
# =======================================================================

if ( "${input}" == "" ) then
    echo "** ERROR: missing input file. Use '-input ..'"
    goto BAD_EXIT
endif

if ( "${opref}" == "" ) then
    echo "** ERROR: missing output prefix. Use '-prefix ..'"
    goto BAD_EXIT
endif

if ( "${sharp_phi}" != "" ) then
    set is_ok = `ccalc -i -eval "within(${sharp_phi},0.1,0.9)"`
    if ( ! ${is_ok} ) then
        echo "** ERROR: Sharpen phi must be in range [0.1, 0.9], not: ${sharp_phi}"
        echo "          Please fix your input to '-sharpen_with_phi ..'"
        goto BAD_EXIT
    endif
endif

# ===================== output dir + wdir =======================
 
echo "++ Start 3dLocalUnifize work"

# check output directory, use input one if nothing given

if ( ! -e "${odir}" ) then
    echo "++ Making new output directory: ${odir}"
    \mkdir -p "${odir}"
endif

set wdir = "${odir}/${wdir_name}"

# make the working directory
if ( ! -e "${wdir}" ) then
    echo "++ Making working directory: ${wdir}"
    \mkdir -p "${wdir}"
else
    echo "+* WARNING: Somehow found a premade working directory (?):"
    echo "      ${wdir}"
endif

# check about whether local_mask opt uses a mask dset; if so, copy
# that mask to wdir and change var to use that local copy
set nlm = `python -c "print(len('${local_mask}'.split()))"`
if ( "${nlm}" == "2" ) then
    # does local_mask start with '-mask'?
    set lll = `echo "${local_mask}" | awk '{print $1}'`
    if ( "${lll}" == "-mask" ) then
        # if here, we have a mask dset input

        # get name of mask; careful of subbrick selectors
        set mmm = `echo "${local_mask}" | awk '{print $2}'`

        # copy the dset
        3dTcat ${overwrite}                                         \
            -prefix   "${wdir}/dset_00_mask.nii.gz"                 \
            "${mmm}"

        if ( $status ) then
            echo "** ERROR when processing local_mask opt: '${local_mask}'"
            goto BAD_EXIT
        endif
            
        # edit the local_mask opt to use the local wdir mask name
        set local_mask = "-mask dset_00_mask.nii.gz"
    endif
endif


# ======================= main program work ===========================

# Copy original image in

set idset    = "${input}"
set odset_cp = dset_00_cp.nii.gz

3dTcat ${overwrite}                                                 \
    -prefix   "${wdir}/${odset_cp}"                                 \
    "${idset}"

# move into working dir
cd "${wdir}"

# if automasking, calculate separately first
if ( "${local_mask}" == "-automask" ) then

    3dAutomask ${overwrite}                                         \
        ${amask_opts}                                               \
        -prefix  "dset_00_mask.nii.gz"                              \
        "${odset_cp}"

    if ( $status ) then
        echo "** ERROR when automasking"
        goto BAD_EXIT
    endif
        
    # edit the local_mask opt to use the local wdir mask name
    set local_mask = "-mask dset_00_mask.nii.gz"

    if ( "${amask_tool}" != "" ) then
        3dmask_tool ${overwrite}                                  \
            ${amask_tool}                                         \
            -input     dset_00_mask.nii.gz                        \
            -prefix    dset_00_maskB.nii.gz 

        if ( $status ) then
            echo "** ERROR when running 3dmask_tool on the automask"
            goto BAD_EXIT
        endif

        # edit the local_mask opt to use the local wdir mask name
        set local_mask = "-mask dset_00_maskB.nii.gz"
    endif
endif


# Calc the smooth/median image

set idset    = "${odset_cp}"
set odset_lu = dset_01_lu.nii.gz

3dLocalstat ${overwrite}                                            \
    ${local_mask}                                                   \
    -nbhd     "SPHERE(${local_rad})"                                \
    -stat     "perc:${local_perc}:${local_perc}:1"                  \
    -prefix   "${odset_lu}"                                         \
    "${idset}"

# Calc the ratio of original to this

set idset     = "${odset_lu}"
set jdset     = "${odset_cp}"
set odset_rat = dset_02_rat.nii.gz

3dcalc ${overwrite}                                                \
    -a       "${idset}"                                            \
    -b       "${jdset}"                                            \
    -expr    "b/a"                                                 \
    -prefix  "${odset_rat}"

set dset_out = "${odset_rat}"

# (opt) Sharpen

if ( "${sharp_phi}" != "" ) then
    echo "++ Apply sharpening to image with phi: ${sharp_phi}"

    set idset       = "${odset_rat}"
    set odset_sharp = dset_03_sharp.nii.gz

    if ( "${overwrite}" != "" && -f ${odset_sharp} ) then
        \rm ${odset_sharp}
    endif

    3dSharpen                                                      \
        -phi     ${sharp_phi}                                      \
        -input   "${idset}"                                        \
        -prefix  "${odset_sharp}"

    set dset_out = "${odset_sharp}"
endif

# (opt) Filter possible outlier ratio values

if ( `echo "${filt_thr} > 0 " | bc` ) then
    echo "++ Apply filter threshold to scaled image: ${filt_thr}"

    set idset      = "${dset_out}"
    set odset_filt = dset_04_filt.nii.gz

    3dcalc ${overwrite}                                            \
        -a       "${idset}"                                        \
        -expr    "maxbelow(${filt_thr},a)"                         \
        -prefix  "${odset_filt}"

    set dset_out = "${odset_filt}"
else
    echo "++ NO filter threshold will be applied to the scaled image"

endif

# ----------- finish

3dcopy ${overwrite}                                               \
    "${dset_out}"                                                 \
    "../${opref}"

# ------------------------- wrap up ----------------------------

# move out of wdir to the odir
cd ..
set whereout = "$PWD"

if ( "$DO_CLEAN" == "1" ) then
    echo "\n+* Removing the working dir: '${wdir_name}'\n"
    \rm -rf "${wdir_name}"
else
    echo "\n++ NOT removing the working dir: '${wdir_name}'\n"
endif

echo ""
echo "++ DONE! View the finished, locally-unifized product:"
echo "     ${whereout}/${opref}"
echo ""


goto GOOD_EXIT

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

SHOW_HELP:
cat << EOF
-------------------------------------------------------------------------

OVERVIEW ~1~

This program takes an input and generates a simple "unifized" output
volume.  It estimates the median in the local neighborhood of each
voxel, and uses that to scale each voxel's brightness.  The result is
a new dataset of brightness of order 1, which still has the
interesting structure(s) present in the original.

This program's output looks very useful to help with dataset alignment
(esp. EPI-to-anatomical) in a wide array of cases.

ver   : ${version}
date  : ${rev_dat}
auth  : PA Taylor (SSCC, NIMH, NIH)


USAGE ~1~

This program is generally run as:

    3dLocalUnifize [options] -prefix DSET_OUT -input DSET_IN

where the following options exist:

  -input     DSET_IN  :(req) input dataset
  
  -prefix   DSET_OUT  :(req) output dataset name, including path

  -wdir_name WD       :name of temporary working directory, which 
                       should not contain any path information---it will be
                       created in the same directory as the final dataset
                       is created
                       (def: ${wdir_base}_, plus a random alphanumeric str)

  -echo               :run this program very verbosely (def: don't do so)

  -no_clean           :do not remove the working directory (def: remove it)

 ... and the following are 'tinkering' options, likely not needed in
     most cases:

  -local_rad LR       :the spherical neighborhood's radius for the 
                       3dLocalStat step (def: ${local_rad})

  -local_perc LP      :the percentile used in the 3dLocalStat step,
                       generating the scaling volume 
                       (def: ${local_perc})

  -local_mask LM      :provide the masking option to be used in the
                       3dLocalStat step, which should be enclosed in
                       quotes for passing along to the internal
                       program call.  So, to use a pre-existing mask,
                       you might call this option like:
                         -local_mask "-mask my_mask.nii.gz"
                       To remove any masking, put the special keyword
                       "None" as the option value.
                       (def: "${local_mask}")

  -automask_opts 'AO ...' :when automasking is applied ('-local_mask -automask' 
                       or, since that is the default, not adding any masking 
                       opt), users can use this option to provide one or more
                       options to 3dAutomask when it is run 
                       (def: "${amask_opts}").

  -automask_tool 'AT ...' :when automasking is applied ('-local_mask -automask' 
                       or, since that is the default, not adding any masking 
                       opt), users can use this option to run 3dmask_tool on that
                       initial automask, providing one or more options to 
                       3dmask_tool when it is run (def: "${amask_tool}").

  -sharpen_with_phi PHI :before possible filtering and output, apply 3dSharpen
                       to sharpen the locally unifized result.  The value of
                       PHI must be in range [0.1, 0.9]; this sets the 
                       value of the '-phi ..' opt in 3dSharpen---see that 
                       program's help for details (def: no sharpening applied)

  -filter_thr FT      :put a ceiling on values in the final, scaled dataset,
                       whose values should be of order 1; setting FT to be a
                       value <=0 turns off this final filtering
                       (def: ${filt_thr})


NOTES ~1~

  This program is designed to not need a lot of tinkering with
  options, such as the '-local_* ..' ones.  In most cases, the default
  scaling will be useful.


EXAMPLES ~1~

  1. Basic local unifizing:
     3dLocalUnifize                                                  \\
        -prefix  vr_base_LU                                          \\
        -input   vr_base_min_outlier+orig.HEAD

  2. Same as above, without masking:
     3dLocalUnifize                                                  \\
        -prefix      vr_base_LU_FOV                                  \\
        -input       vr_base_min_outlier+orig.HEAD                   \\
        -local_mask  None

  3. Same as Ex. 1, but adding sharpening:
     3dLocalUnifize                                                  \\
        -prefix            vr_base_LU_SHARP.nii.gz                   \\
        -input             vr_base_min_outlier+orig.HEAD             \\
        -sharpen_with_phi  0.5

EOF

# ----------------------------------------------------------------------

    goto GOOD_EXIT

SHOW_VERSION:
   echo "version  $version (${rev_dat})"
   goto GOOD_EXIT

FAIL_MISSING_ARG:
    echo "** ERROR! Missing an argument after option flag: '$argv[$ac]'"
    goto BAD_EXIT

BAD_EXIT:
    exit 1

GOOD_EXIT:
    exit 0
