#
# This TCL script processes a single raw Mark IV CCD images
#
#      - reads a few values from an input field=value file
#      - subtracts a scaled "master_dark" frame
#      - divides by a "master_flat" frame
#      - places comments into the FITS headers, describing the work
#              which was done
#              
# MWR 3/4/2000
#
# Modified to work on only a SINGLE image, rather than a whole bunch.
#   MWR 5/22/2000
#  
#
# Added "xvista_dir" to all calls to XVista programs
#   --  MWR 2/9/2001
# 
# We now use the binned exposure time to figure out the master dark name.
#   --  MWR 4/7/2001
#     
# Added some variables to the Ccdproc namespace so that trimming an image
#   can be done during the flatfielding process as well as in the usual
#   place.  Also, moved the trimming code to a proc of its own.
#   --  MWR 5/28/2001
#

if { [catch { set debug }] == 1 } {
  set debug 2
}

# contains source for "get_param" procedure
source param.tcl

# contains source for "sub_dark" procedure
source sub_dark.tcl


##############################################################################
# ccdproc
# 
#   - read input values from a data file
#   - perform dark-subtraction on the given image
#   - perform flatfielding on the given image
#   - write comments to FITS headers
#   - write the corrected image to disk
#   
# returns
#     0            if all goes well
#     1            if there's a problem
#
proc ccdproc { param_file raw_file } {
  global debug

  if { $debug > 0 } {
    puts "entering ccdproc"
  }

  if { $debug > 1 } {
    puts [format "   args %s %s \n" $param_file $raw_file]
  }

  if { [file exists $param_file] != 1 } {
    puts "ccdproc: can't open param file $param_file"
    return 1
  }


  # get the parameter values we need
  set raw_dir        [get_param $param_file "input_dir"]
  set output_dir     [get_param $param_file "output_dir"]
  set box_start      [get_param $param_file "box_start"]
  set dark_cols      [get_param $param_file "dark_cols"]
  set trim_start_row [get_param $param_file "trim_start_row"]
  set trim_end_row   [get_param $param_file "trim_end_row"]
  set trim_start_col [get_param $param_file "trim_start_col"]
  set trim_end_col   [get_param $param_file "trim_end_col"]

  # get information about the image
  if { [single_image_info $raw_file image_info_list] != 0 } {
    puts stderr "ccdproc: single_image_info fails for image $raw_file"
    return 1
  }

  # if this is not an "object" image, don't process it!  Just return
  #   with code 0, but do print a warning message
  set type [get_image_value $image_info_list "type"]
  if { [string compare $type "object"] != 0 } {
    if { $debug > 0 } {
      puts "ccdproc: image $raw_file is not 'object', so skip it"
    }
    return 0
  }

  # we need the filter, (binned) exposure time  to pick proper "master" files
  set filter [get_image_value $image_info_list "filter"]
  set exposure [get_image_value $image_info_list "exposure"]
  set exposure [get_binned_exptime $exposure]
  if { $debug > 1 } {
    puts "   filter is $filter,  (binned) exposure is $exposure"
  }

  # get the "master" dark and flat images which we will apply
  #   to this raw image
  if { [get_master_names $filter $exposure master_dark master_flat] != 0 } {
    puts stderr "ccdproc: get_master_names fails for image $raw_file"
    return 1
  }

  # must prepare by calculating the mean value of the flatfield frame
  if { [calc_master_flat_mean $master_flat] != 0 } {
    puts stderr "calc_master_flat_mean fails -- quitting "
    return 1
  }


   if { [file exists $raw_dir/$raw_file] != 1 } {
     puts stderr "ccdproc: can't find input raw file $raw_dir/$raw_file -- skipping it"
     return 0
   }

   # we will give the 'corrected' image the same name as $raw_file,
   #   but place the 'corrected' version in the output directory,
   #   instead of the $raw_dir
   if { $debug > 1 } { 
     puts "ccdproc: about to copy $raw_file from $raw_dir "
   }
   set corrected_file $raw_file
   exec cp $raw_dir/$raw_file $output_dir/$corrected_file
   exec chmod 644 $output_dir/$corrected_file
   

   # subtract the dark frame
   if { $debug > 0 } {
     puts "about to call sub_dark with dir $output_dir file $corrected_file"
   }
   if { [sub_dark $output_dir $corrected_file $master_dark \
                                      $box_start $dark_cols \
                                      $trim_start_row $trim_end_row] != 0 } {
     puts stderr "ccdproc: sub_dark fails for file $corrected_file"
     return 1
   }


   # divide by the flatfield
   if { $debug > 0 } {
     puts "about to call do_flatfield for dir $output_dir file $corrected_file"
   }
   if { [do_flatfield $output_dir $corrected_file $master_flat ] != 0 } {
     puts stderr "ccdproc: do_flatfield fails for file $corrected_file"
     return 1
   }

   # trim the nasty edges away from the image
   if { [trim_image $output_dir/$corrected_file] != 0 } {
     puts stderr "ccdproc: trim_image fails for file $corrected_file"
     return 1
   }


   return 0
}



#############################################################################
# PROCEDURE: trim_image
#
# DESCRIPTION: given the name of an image file on disk, trim the edge
#              rows and cols from the image and replace the original
#              with the trimmed version.  
#
# RETURNS:
#    0            if all goes well
#    1            if there's a problem
#
proc trim_image { image_file } {
   global debug

   if { $debug > 0 } {
     puts "entering trim_image "
   }

   # the XVista programs live in this directory
   set xvista_dir [get_directory "xvista_dir"]
   if { $xvista_dir == {} } {
     puts stderr "trim_image: get_directory fails for xvista_dir"
     return 1
   }

   if { [get_trim_info trim_box trim_start_row trim_end_row \
                                 trim_start_col trim_end_col ] != 0 } {
     puts stderr "trim_image: get_trim_info fails"
     return 1
   }

   # trim the corrected image to get rid of pre-scan and post-scan columns,
   #   and garbage rows at top and bottom
   set sr $trim_start_row
   set nr [expr 1+($trim_end_row - $trim_start_row)]
   set sc $trim_start_col
   set nc [expr 1+($trim_end_col - $trim_start_col)]
   if { $debug > 1 } {
     puts "  exec $xvista_dir/box $trim_box sr=$sr nr=$nr sc=$sc nc=$nc"
   }
   exec $xvista_dir/box $trim_box sr=$sr nr=$nr sc=$sc nc=$nc
   exec $xvista_dir/window $image_file box=$trim_box
   exec $xvista_dir/comment $image_file \
                            "trimmed sr=$sr nr=$nr sc=$sc nc=$nc"
   set datestr [exec date]
   exec $xvista_dir/comment $image_file \
                            "   by trim_image on $datestr"

   return 0
}




#############################################################################
# calculate the mean value of the master flatfield frame.
#   This will be stored in the XVista symbol table for later use
#
# Returns
#   0               if all goes well
#   1               if not
#   
proc calc_master_flat_mean { master_flat } {
  global debug

  # the XVista programs live in this directory
  set xvista_dir [get_directory "xvista_dir"]
  if { $xvista_dir == {} } {
    puts stderr "ccdproc: get_directory fails for xvista_dir"
    return 1
  }

  if { [file exists $master_flat] != 1 } {
    puts "calc_master_flat_mean: can't find file $master_flat"
    return 1
  }

  exec $xvista_dir/mn $master_flat

  return 0
}


#############################################################################
# Given a single dark-subtracted file, and a master flatfield file
#   (of which we have already calculated the mean)
#   divide the dark-subtracted file by the master flatfield, 
#   renormalizing so that the mean value of the input file remains the same.
#   
# Place comments in the FITS header of the input file to explain the
#   changes we've made
#   
# Returns
#    0              if all goes well
#    1              if not
#
proc do_flatfield { dir file master_flat } {
  global debug

  # the XVista programs live in this directory
  set xvista_dir [get_directory "xvista_dir"]
  if { $xvista_dir == {} } {
    puts stderr "ccdproc: get_directory fails for xvista_dir"
    return 1
  }

  if { [file exists $dir/$file] != 1 } {
    puts "do_flatfield: can't find file $dir/$file"
    return 1
  }

  if { [file exists $master_flat] != 1 } {
    puts "do_flatfield: can't find master flatfield file $master_flat"
    return 1
  }

  # call the XVista "div" program to perform the division
  if { $debug > 1 } {
    puts "about to call exec $xvista_dir/div $dir/$file $master_flat flat"
  }
  exec $xvista_dir/div $dir/$file $master_flat flat

  # now put some comments into the FITS header to explain what we've done
  set date [exec date]
  exec $xvista_dir/comment $dir/$file \
                    "do_flatfield: div by master flat $master_flat"
  exec $xvista_dir/comment $dir/$file \
                    "do_flatfield: finished $date"

  return 0

}


#############################################################################
# figure out the names of the 
# 
#      master dark image            which we'll subtract from the current
#                                               raw image
#                                               
#      master flat image            by which we'll divide the current
#                                                raw image
#                                                
# Place the names of these two images into the final two arguments.
# 
# Returns
#    0                 if all goes well
#    1                 if there's an error
#
proc get_master_names { filter exposure master_dark master_flat } {
  upvar $master_dark m_dark $master_flat m_flat
  global debug

  if { $debug > 0 } {
    puts "entering get_master_names"
  }

  set m_dark [master_dark_name $filter $exposure]

  set m_flat [master_flat_name $filter]

  return 0
}
  



#######################
# Here, we start the namespace "Ccdproc", which we use to 
#   store 
#         1. information needed to subtract dark frames from target frames
#         2. information needed to trim edges off target frames
#   
# 
namespace eval Ccdproc {
  namespace export init_sub_dark_info get_sub_dark_info get_trim_info
  variable Ccdproc_Box_Start 
  variable Ccdproc_Dark_Cols 
  variable Ccdproc_Trim_Start_Row 
  variable Ccdproc_Trim_End_Row
  variable Ccdproc_Trim_Start_Col
  variable Ccdproc_Trim_End_Col


#############################################################################
# Initialize some "static" variables by reading
#   their value from the given param file
# 
#
proc init_sub_dark_info { param_file } {
  global debug
  variable Ccdproc_Box_Start 
  variable Ccdproc_Dark_Cols 
  variable Ccdproc_Trim_Box
  variable Ccdproc_Trim_Start_Row 
  variable Ccdproc_Trim_End_Row
  variable Ccdproc_Trim_Start_Col
  variable Ccdproc_Trim_End_Col
  
  if { $debug > 0 } {
    puts "entering init_sub_dark_info"
  } 
  
  if { [file exists $param_file] == 0 } {
    puts stderr "init_sub_dark_info: can't find param file $param_file"
    return 1
  } 
  set Ccdproc_Box_Start [get_param $param_file "box_start"]
  set Ccdproc_Dark_Cols [get_param $param_file "dark_cols"]
  set Ccdproc_Trim_Box [get_param $param_file "trim_box"]
  set Ccdproc_Trim_Start_Row [get_param $param_file "trim_start_row"]
  set Ccdproc_Trim_End_Row [get_param $param_file "trim_end_row"]
  set Ccdproc_Trim_Start_Col [get_param $param_file "trim_start_col"]
  set Ccdproc_Trim_End_Col [get_param $param_file "trim_end_col"]
  
  return 0
} 


#############################################################################
# Return in the output args some information needed to call the
#   'sub_dark' procedure 
#   
# usage:   get_sub_dark_info     box_start dark_cols \
#                                       trim_start_row trim_end_row
# 
# All args will be filled on output:
# 
#              box_start         start of boxes to use for defining dark cols
#              dark_cols         a list of the dark columns to use for
#                                    scaling target frame to match dark frame
#              trim_start_row    starting row to use in dark columns
#              trim_end_row      ending row to use in dark columns
#              
#              
# Returns
#   0              if all goes well
#   1              if an error occurs
#   
proc get_sub_dark_info { box_start dark_cols trim_start_row trim_end_row } {
  upvar $box_start b_start $dark_cols d_cols
  upvar $trim_start_row t_start_row $trim_end_row t_end_row
  global debug
  variable Ccdproc_Box_Start 
  variable Ccdproc_Dark_Cols 
  variable Ccdproc_Trim_Start_Row 
  variable Ccdproc_Trim_End_Row

  if { $debug > 0 } {
    puts "entering get_sub_dark_info"
  }

  set b_start $Ccdproc_Box_Start
  set d_cols  $Ccdproc_Dark_Cols
  set t_start_row $Ccdproc_Trim_Start_Row
  set t_end_row   $Ccdproc_Trim_End_Row
  
  return 0
}


#############################################################################
# Return in the output args some information needed to trim an image;
#   this will be done both to target images, and to copies of the flatfields
#   (in order to create a mask of bad regions).
#   
# usage:   get_trim_info     trim_box trim_start_row trim_end_row \ 
#                                      trim_start_col trim_end_col
# 
# All args will be filled on output:
# 
#              trim_box          the XVista "box" number to use for trimming
#              trim_start_row    starting row to use when trimming
#              trim_end_row      ending row to use when trimming
#              trim_start_col    starting col to use when trimming
#              trim_end_col      ending col to use when trimming
#              
#              
# Returns
#   0              if all goes well
#   1              if an error occurs
#   
proc get_trim_info { trim_box trim_start_row trim_end_row \
                                 trim_start_col trim_end_col } {
  upvar $trim_box t_box
  upvar $trim_start_row t_start_row $trim_end_row t_end_row
  upvar $trim_start_col t_start_col $trim_end_col t_end_col
  global debug
  variable Ccdproc_Trim_Box
  variable Ccdproc_Trim_Start_Row 
  variable Ccdproc_Trim_End_Row
  variable Ccdproc_Trim_Start_Col
  variable Ccdproc_Trim_End_Col

  if { $debug > 0 } {
    puts "entering get_trim_info"
  }

  set t_box       $Ccdproc_Trim_Box
  set t_start_row $Ccdproc_Trim_Start_Row
  set t_end_row   $Ccdproc_Trim_End_Row
  set t_start_col $Ccdproc_Trim_Start_Col
  set t_end_col   $Ccdproc_Trim_End_Col
  
  return 0
}



}
# end of the namespace Ccdproc
