#! /bin/csh -f

#
# ghostdet
#
# Original Author: Doug Greve
# CVS Revision Info:
#    $Author: nicks $
#    $Date: 2007/01/09 22:41:17 $
#    $Revision: 1.7 $
#
# Copyright (C) 2002-2007,
# The General Hospital Corporation (Boston, MA). 
# All rights reserved.
#
# Distribution, usage and copying of this software is covered under the
# terms found in the License Agreement file named 'COPYING' found in the
# FreeSurfer source code root directory, and duplicated here:
# https://surfer.nmr.mgh.harvard.edu/fswiki/FreeSurferOpenSourceLicense
#
# General inquiries: freesurfer@nmr.mgh.harvard.edu
# Bug reports: analysis-bugs@nmr.mgh.harvard.edu
#


set VERSION = '$Id: ghostdet,v 1.7 2007/01/09 22:41:17 nicks Exp $';
set inputargs = ($argv);

setenv FSLOUTPUTTYPE NIFTI
set instem    = ();
set tmpdir    = ();
set sumfile   = ();
set cleanup   = 1;
set monly     = 0;
set MLF       = ();

set headthresh   = 0.05;
set nheaddilate  = 3;

set brainthresh  = 0.5;
set nbrainerode  = 3;

set nairerode = 3;

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:

##### Create a log file ######
set LF = $sumfile.log
echo "--------------------------------------------------------------"
echo "ghostdet logfile is $LF"
echo "--------------------------------------------------------------"

echo "ghostdet log file" >> $LF
date          | tee -a $LF
echo $VERSION | tee -a $LF
pwd           | tee -a $LF
echo $0       | tee -a $LF
echo $inputargs | tee -a $LF
uname -a      >> $LF
date          >> $LF

set StartTime = `date`;

# Compute the mean functional image and use as template
set fmnstem = $tmpdir/fmn.nii
set cmd = (mri_concat $instem --o $fmnstem --mean)
pwd | tee -a $LF
echo $cmd | tee -a $LF
$cmd |& tee -a $LF
if($status) exit 1;

# Make the head mask
set cmd = (mkbrainmask -i $fmnstem -o $headstem \
           -thresh $headthresh -ndil $nheaddilate)
pwd | tee -a $LF
echo $cmd | tee -a $LF
$cmd |& tee -a $LF
if($status) exit 1;

# Make the brain mask
set cmd = (mkbrainmask -i $fmnstem -o $brainstem \
           -thresh $brainthresh -nerode $nbrainerode)
pwd | tee -a $LF
echo $cmd | tee -a $LF
$cmd |& tee -a $LF
if($status) exit 1;

set headghoststem = $tmpdir/headghost.nii

# Go into matlab to create the initial air mask
set MLF = $tmpdir/mkairmasktmp.m
rm -f $MLF
#---------#
tee $MLF > /dev/null <<EOF
headstem = '$headstem';
fmnstem  = '$fmnstem';
airstem  = '$airstem';
headghoststem  = '$headghoststem';

head = MRIread(headstem);
if(isempty(head)) return; end

fmn = MRIread(fmnstem);
if(isempty(fmn)) return; end

% For this, compute the ghost of the head
headghost = head;
headghost.vol = fast_ghostmask(head.vol);
air = head;
air.vol = ~(head.vol | headghost.vol);
indair = find(air.vol);

% Exclude air voxels that are identical to 0
indnz = find(fmn.vol(indair) ~=0 );
indair = indair(indnz);

air.vol = zeros(fmn.volsize);
air.vol(indair) = 1;

airstem

MRIwrite(air,airstem);
MRIwrite(headghost,headghoststem);

fprintf('matlab: finished creating init air mask\n');
EOF
#---------#
cat $MLF >> $LF
cat $MLF | matlab -display iconic |& tee -a $LF

# Erode the air mask
echo "Eroding the air mask $nairerode" | tee -a $LF
date | tee -a $LF
set cmd = (avwmaths $airstem -ero $airstem)
pwd | tee -a $LF
echo $cmd | tee -a $LF
@ n = 1;
while($n < $nairerode)
  echo "------- $n ------------" |& tee -a $LF
  ls $tmpdir |& tee -a $LF
  echo $cmd |& tee -a $LF
  $cmd |& tee -a $LF
  if($status) exit 1;
  @ n = $n + 1;
end
echo "Done eroding the air mask" | tee -a $LF
date | tee -a $LF

# Go back into matlab to do the final processing
set MLF = $tmpdir/ghostdettmp.m
rm -f $MLF
#---------#
tee $MLF > /dev/null <<EOF
headstem   = '$headstem';
brainstem  = '$brainstem';
airstem    = '$airstem';
fmnstem    = '$fmnstem';
ghoststem  = '$ghoststem';
sumfile    = '$sumfile';

%[head mristruct] = fast_ldbslice(headstem);
head = MRIread(headstem);
if(isempty(head)) return; end
head = head.vol;

%[brain mristruct] = fast_ldbslice(brainstem);
brain = MRIread(brainstem);
if(isempty(brain)) return; end
brain = brain.vol;

%[air mristruct] = fast_ldbslice(airstem);
air = MRIread(airstem);
if(isempty(air)) return; end
air = air.vol;

%fmn = fast_ldbslice(fmnstem);
fmn = MRIread(fmnstem);
if(isempty(fmn)) return; end
fmn = fmn.vol;

indair = find(air);
indbrain = find(brain);

% For this, compute the ghost of the brain
indghost = indbrain2ghost(size(brain),indbrain);

% Exclude ghost (and corresp brain) voxels that have fmn=0
indnz    = find(fmn(indghost) ~= 0);
indghost = indghost(indnz);
indbrain = indbrain2ghost(size(brain),indghost);

ghost = zeros(size(fmn));
ghost(indghost) = 1;

% Exclude overlapping ghost and head voxels from both ghost and brain
ghost = ghost & ~head;
indghost = find(ghost);
indbrain = indbrain2ghost(size(brain),indghost);

% Remake the ghost and brain masks
ghost = zeros(size(fmn));
ghost(indghost) = 1;
brain = zeros(size(fmn));
brain(indbrain) = 1;

nbrain = length(indbrain); % Equals number in ghost too
nair   = length(indair);

brainmn = mean(fmn(indbrain));
ghostmn = mean(fmn(indghost));
airmn   = mean(fmn(indair));

brain_ghost_ratio = brainmn/ghostmn;
brain_air_ratio   = brainmn/airmn;
ghost_air_ratio   = ghostmn/airmn;
structural_snr    = brain_air_ratio/1.54;

fp = fopen(sumfile,'w');
if(fp == -1)
  fprintf('ERROR: opening %s\n',sumfile);
  return;
end
fprintf(fp,'brain_mean %g\n',brainmn);
fprintf(fp,'ghost_mean %g\n',ghostmn);
fprintf(fp,'air_mean   %g\n',airmn);
fprintf(fp,'brain_ghost_ratio %g\n',brain_ghost_ratio);
fprintf(fp,'brain_air_ratio   %g\n',brain_air_ratio);
fprintf(fp,'ghost_air_ratio   %g\n',ghost_air_ratio);
%fprintf(fp,'structural_snr    %g\n',structural_snr);
fprintf(fp,'n_air   %7d\n',nair);
fprintf(fp,'n_brain %7d\n',nbrain);
fprintf(fp,'head_thresh   %g\n',$headthresh);
fprintf(fp,'n_head_dilate %d\n',$nheaddilate);
fprintf(fp,'brain_thresh  %g\n',$brainthresh);
fprintf(fp,'n_brain_erode %d\n',$nbrainerode);
fprintf(fp,'n_air_erode   %d\n',$nairerode);

fclose(fp);

%fast_svbslice(brain,brainstem,-1,'bshort',mristruct);
%fast_svbslice(ghost,ghoststem,-1,'bshort',mristruct);

fprintf('matlab: finished computing ghost measures\n');
EOF
#---------#
cat $MLF >> $LF
cat $MLF |& matlab -display iconic |& tee -a $LF

echo "Started at $StartTime" |& tee -a $LF
echo "Ended   at `date`"     |& tee -a $LF

echo "ghostdet done"


exit 0

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

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

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

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

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

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

    case "--monly":
      echo "ERROR: cannot do monly"
      exit 1;
      if ( $#argv == 0) goto arg1err;
      set MLF = $argv[1]; shift;
      breaksw

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

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

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

    case "--debug":
      set verbose = 1;
      set echo = 1; # turns on terminal echoing
      breaksw

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

end

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

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

  if($#instem == 0) then
    echo "ERROR: no input specified"    
    exit 1;
  endif

  if(! -e $instem) then
    echo "ERROR: cannot find $instem"
    exit 1;
  endif

  if($#tmpdir == 0) set tmpdir = /tmp/ghostdet.tmp.$$
  mkdir -p $tmpdir
  set maskstem  = $tmpdir/mask.nii
  set headstem  = $tmpdir/head.nii
  set brainstem = $tmpdir/brain.nii
  set ghoststem = $tmpdir/ghost.nii
  set airstem   = $tmpdir/air.nii

  if($#sumfile == 0) then
    set sumfile = $instem.ghost.sum
  else
    set outdir = `dirname $sumfile`;
    mkdir -p $outdir
  endif

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

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

############--------------##################
usage_exit:
  echo ""
  echo "USAGE: ghostdet"
  echo ""
  echo "  --i invol "
  echo ""
  echo "  --s sumfile  : default is instem.ghost.sum"
  echo "  --tmp tmpdir : turns cleanup off "
  echo "  --nocleanup  : do not delete temporary files"
  echo "  --cleanup    : force deleting fo temporary files"
#  echo "  --pedim dim  : 2=col (default) 1=row "
#  echo "  --synth "
  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

Measures ghost suppression for EPI functional scans.


REQUIRED ARGUMENTS

--i instem

Stem of functional data from which the ghosting measure is computed. This 
can have one or more frames. This is the only compulsary argument.

OPTIONAL ARUGMENTS

--s sumfile

Save summary into sumfile. Default is instem.ghost.sum. See SUMMARY
MEASURES below. Note: log file is saved as sumfile.log

--tmp tmpdir

Save intermediate results in tmpdir. By default the temporary directory
is /tmp/ghostdet.tmp.procid is deleted by default. When explicitly 
specified with --tmp, tmpdir will not be deleted (ie, implies --nocleanup). 
If you want tmpdir to be deleted, specify --cleanup after --tmp.

--nocleanup

Do not delete temporary directory and files.

--cleanup

Delete temporary directory and files. Does this auotmatically unless --tmp.

SUMMARY MEASURES

The summary file is a report of the ghost detection analysis. It contains 
both output results and the input parameters. See ALGORITHM below for
an explanation of how these parameters are used. It looks like this:

brain_mean 6398.47
ghost_mean 418.601
air_mean   92.8722
brain_ghost_ratio 15.2854
brain_air_ratio   68.8955
ghost_air_ratio   4.50729
n_air     27509
n_brain    2192
head_thresh   0.05
n_head_dilate 3
brain_thresh  0.5
n_brain_erode 3
n_air_erode   3

brain_mean - mean of the subset of voxels inside the brain voxels
  chosen for the analysis. Each voxel in the brain has a corresponding
  voxel in the ghost.

ghost_mean - mean of the voxels inside the ghost voxels chosen for the
  analysis. Each voxel in the ghost has a corresponding voxel in the
  brain.

air_mean - mean of the voxels outside both the head and the ghost of 
  the head.

brain_ghost_ratio - ratio of the mean of the brain to the mean of the
  ghost.  This is the primary measure of ghost suppression.

brain_air_ratio - ratio of the mean of the brain to the mean of the
  air.  This is the ideal value of upper limit to the
  brain_ghost_ratio.

ghost_air_ratio - ratio of the mean of the ghost to the mean of the
  air.  This ratio should be 1 ideally.

n_air - number of voxels in the air.

n_brain - number of voxels in the subset of the brain used for the
  analysis.  Note: this should NOT be interpreted as brain volume. See
  below for how this is computed. Note: each brain voxels has a
  corresponding ghost voxel, so the number of ghost voxels is equal to
  the number of brain voxels.

head_thresh - fraction of grand mean to use to initially segment the
  head.  This threshold is passed to FSL BET. Default is 0.05.

n_head_dilate - the head mask created by BET is dilated (grown) by
  this number of voxels. Default is 3.

brain_thresh - fraction of grand mean to use to initially segment the
  brain.  This threshold is passed to FSL BET. Default is 0.5.

n_brain_erode - the brain mask created by BET is eroded (shrunk) by
  this number of voxels. Default is 3.

n_air_erode - the air mask is eroded (shrunk) by this number of
  voxels.  Default is 3.  

ALGORITHM

The basic measure of ghosting is to compare the average intensity in
the head with that in the ghost. For an N/2 ghost, a voxel in the head
will map to another voxel in the slice. This ghost voxel will be in
the same column as the original voxel. Its row will be that of the
original plus N/2. If this puts it out of the slice, then it is
wrapped around. There are voxels where the ghost and the head will
overlap, and we do not want to consider these voxels because it would
skew the results. We want head-ghost voxel pairs for which the ghost
voxel is absolutely not in any tissue. A simple intensity-based
segmentation of the head often fails because there is some tissue (eg
the skull) that can have an intensity comparable to the ghost, and
these areas cannot be eliminated purely based on intensity without
eliminating the ghost itself.  In addition to determining which voxels
are head and ghost, it is nice to know which voxels are "air". The
average in the air will give a lower limit that the ghost can
achieve. Note that the average air intensity will always be non-zero
because of the nature of k-space reconstruction.

Here is the algorthm this program uses to choose the subset of voxels
for brain, ghost, and air.

1. Average the functional volume into a single volume. All the masks
are created based on this volume. In the tmpdir, this will be the fmn
volume.

2. Create a head mask. The first stage is to run BET with a low
threshold of 0.05 (head_thresh) to make sure that it gets most of the
head. BET has constraints that force the mask to have a smooth surface
and be connected. This usually does not capture the skull or non-brain
tissue, so the initial head mask is grown (dilated) by 3 voxels
(n_head_dilate) so as to expand out to the skull. In the tmpdir, this
will be the head volume.

3. Create a mask of the ghost of the head. This is done by mapping the
the head mask by N/2 rows. In the tmpdir, this will be the headghost
volume.

4. Create a mask of the air. These are all the voxels that do not fall
into either the head or the headghost. In addition, the initial air
mask is eroded (shrunk) by 3 voxels (n_air_erode) to make sure that
it does not contain any tissue. In the tmpdir, this will be the air
volume.

5. Create an initial brain mask. The first stage is to run BET with a
high threshold of 0.5 (brain_thresh) to make sure that it gets only
brain.  This brain mask is then eroded (shrunk) by 3 voxels
(n_brain_erode) to assure that it contains no low-intensity areas.

6. Create the brain ghost and final brain mask. The brain ghost is
created by wrapping the intial brain mask by N/2. Voxels that are in
both the brain and ghost are eliminated from both masks. The resulting
brain mask is saved as brain in tmpdir. The resulting ghost mask is
saved as ghost in tmpdir.
