PRO xrt_dark_sub, index_in, image_in, index_out, image_out, $
                  dark_file=dark_file, force=force,         $
                  verbose=verbose,     quiet=quiet,         $
                  qabort=qabort,       qstop=qstop

; ==========================================================================
;+
; PROJECT:
;       Solar-B / XRT
;
; NAME: 
;       XRT_DARK_SUB
;
; CATEGORY:
;       Data calibration
;
; PURPOSE:
;       Subtract a dark current image from an XRT image. The dark image 
;       should be in FITS file format. The "/force" switch may be used 
;       to manipulate the dark to better match the data.
;
; CALLING SEQUENCE:
;       XRT_DARK_SUB, index_in, image_in, index_out, image_out,
;                     dark_image=dark_image [,/force] [,/verbose]
;                     [,/quiet] [,qabort=qabort] [,/qstop]
;
; INPUTS:
;       INDEX_IN  - [Mandatory] (structure scalar) The index structure
;                   for the input image (e.g., "index" from output of 
;                   "read_xrt.pro".)
;       IMAGE_IN  - [Mandatory] (number array, [Nx,Ny]) The input image
;                   data array.
;       DARK_FILE - [Mandatory] (string scalar) The dark image (FITS file) 
;                   that will be subtracted from the image.
;
; KEYWORDS:
;       /FORCE    - [Optional] (Boolean) If set, the program will try
;                   to change the dark to match the image. The following
;                   steps are performed, as necessary.
;                   a) Change summing to match.
;                   b) Use lower-left of dark if cannot match the actual
;                      CCD pixels of the image.
;                   c) Normalize to image exposure duration.
;       /VERBOSE  - [Optional] (Boolean) Print out extra information.
;                   Overrides "/quiet" (see Note #2).
;       /QUIET    - [Optional] (Boolean) Suppress messages (see Note #2).
;       /QSTOP    - [Optional] (Boolean) For debugging.
;
; OUTPUTS:
;       INDEX_OUT - [Mandatory] (structure scalar)
;                   The updated index structure of the output image.
;       IMAGE_OUT - [Mandatory] (float array, [Nx,Ny])
;                   The dark-subtracted output image data array.
;       QABORT    - [Optional] (Boolean) Indicates that the program
;                   exited gracefully without completing. (Might be
;                   useful for calling programs.)
;                   0: Program ran to completion.
;                   1: Program aborted before completion.
;
; EXAMPLES:
;
;       Straightforward usage:
;       IDL> xrt_dark_sub, index_in, image_in, index_out, image_out, $
;       IDL>               dark_file='XRT_dark.fits'
;
;       If you want to use a dark that doesn't perfectly match the image:
;       IDL> xrt_dark_sub, index_in, image_in, index_out, image_out, $
;       IDL>               dark_file='XRT_dark.fits', /force
;
;       Same as previous, but shut it up:
;       IDL> xrt_dark_sub, index_in, image_in, index_out, image_out, $
;       IDL>               dark_file='XRT_dark.fits', /force, /quiet
;
;
; COMMON BLOCKS:
;       None.
;
;
; NOTES:
;
;      1) The "v2006-Nov-30" version does not allow CAL darks to be used.
;
;      2) There are three levels of verbosity.
;          a) "verbose" = highest priority. All errors and messages are
;                         displayed. ("if q_vb")
;          b) "quiet" = lower priority. No errors or messages are
;                       displayed. ("if q_qt")
;          c) neither = lowest priority. All errors and some messages are
;                       displayed. ("if not q_qt")
;
;      3) The "index_out.HISTORY" is updated to reflect the 
;         dark subtraction.
;
;      4) The "v2006-Nov-30" version of this program does not make any
;         adjustments for the CCD temperature, although it will reject
;         a dark subtraction if the difference is too large.
;
;      5) The "v2006-Nov-30" version assumes that "image_in" has already 
;         had its bias (pedestal) subtracted.
;
;      6) As of 2007-Feb-14, the "v2006-Nov-30" version has been deprecated
;         in the sense that it is not currently used by <xrt_prep.pro>. It
;         anticipated that this code may become useful for calibration
;         analyses.
;
;
; CONTACT:
;
;       Comments, feedback, and bug reports regarding this routine may be
;       directed to this email address:
;                xrt_manager ~at~ head.cfa.harvard.edu
;
;
; MODIFICATION HISTORY:
;
progver = 'v2006-May-17' ;--- (P.R. Jibben) Written.
progver = 'v2006-Nov-30' ;--- (MW) Systemic changes.
;
;
;-
;===========================================================================


;=== Initial setup ====================================================


;===== Track program runtime.
  t0 = systime(1)


;===== Initialize program constants.
  prognam='XRT_DARK_SUB'
  ;=== Image and dark must differ in temperature by less than this amount:
  temp_diff_threshold = 8. ;Degrees Kelvin
  ;=== Image and dark must differ in time by less than this amount:
  time_diff_threshold = 60d ;days
  ccd_bias = 35 ; [DN] for full-resolution pixels


;===== Set Booleans which control print statements.
;===== Keyword "verbose" overrules "quiet".
  q_vb = keyword_set(verbose)
  q_qt = keyword_set(quiet) and (not q_vb)


;===== Announce version of <xrt_dark_sub.pro>.
  if q_vb then box_message, 'XRT_DARK_SUB: Running ' + prognam + $
                            ' ' + progver + '.'


;===== Set some keyword Booleans.
  q_dkfil = keyword_set(dark_file)
  q_force = keyword_set(force)
  qstop   = keyword_set(qstop)
  qabort  = 0B


;===== Other useful stuff.
  ;=== First element is just a placeholder.
  hist_entry = ''


;===== Are there 4 parameters?
  if (n_params() ne 4) then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: Requires 4 ' $
                                    + 'parameters. Aborting...'
    qabort = 1B
    return
  endif


;=== Check "index_in" and "image_in" ==================================

;===== This block checks "index_in".
  if q_vb then print, 'XRT_DARK_SUB: Checking input "index_in"...'

  ;=== Check "index_in".
  if (datatype(index_in) ne 'STC') then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: Input "index_in" ' $
       + 'needs to be a structure from an XRT FITS header. Aborting...'
    qabort = 1B
    return
  endif
  if (n_elements(index_in) ne 1) then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: Input "index_in" ' $
                                  + 'needs to be a scalar. Aborting...'
    qabort = 1B
    return
  endif

  ;=== Check for necessary tags.
  if not required_tags(index_in, 'CAL_INFO,EC_IMTYP,CCD_TEMP,CHIP_SUM,' $
         + 'EXPTIME,CCD_READ,DATE_OBS,RPOS_COL,RPOS_ROW,RSIZ_COL,'      $
         + 'RSIZ_ROW,CTIME,HISTORY', missing=missing) then begin 
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Input "index_in" ' $
          + 'does not have these required tags:',                      $
          '              ' + missing, '              Aborting...']
    qabort = 1B
    return
  endif else begin
    img_cal = gt_tagval(index_in, /cal_info)  ;Cal image? 0=no 1=yes
    img_typ = gt_tagval(index_in, /ec_imtyp)  ;Type? 0=normal 1=dark
    img_tmp = gt_tagval(index_in, /ccd_tmpc)  ;CCD temperature
    img_sum = gt_tagval(index_in, /platescl)  ;CCD summing factor
    img_edi = gt_tagval(index_in, /ec_einde)  ;Exposure duration index
    img_exp = gt_tagval(index_in, /exptime)   ;Actual exposure duration
    img_prt = gt_tagval(index_in, /ccd_read)  ;Read port 0=right 1=left
    img_tim = gt_tagval(index_in, /date_obs)  ;Image time
    img_ctm = gt_tagval(index_in, /ctime)     ;Image time readable
    img_x0  = gt_tagval(index_in, /rpos_col)  ;ROI x-corner
    img_y0  = gt_tagval(index_in, /rpos_row)  ;ROI y-corner
    img_nx0 = gt_tagval(index_in, /rsiz_col)  ;ROI x-size
    img_ny0 = gt_tagval(index_in, /rsiz_row)  ;ROI y-size
  endelse

  ;=== Check whether this image has already been dark-subtracted.
  previous = get_history(index_in, 'XRT_DARK_SUB', found=found)
  if found then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: This image has ' $
            + 'already been dark-subtracted. Aborting...'
    qabort = 1B
    return
  endif

  if q_vb then print, '                                       ...OK'


;===== This block checks "image_in".
  if q_vb then print, 'XRT_DARK_SUB: Checking input "image_in"...'
  ;=== Ensure "image_in" is of a number type.
  num_check = total(is_number(image_in)) eq n_elements(image_in)
  ;=== Ensure "image_in" has 2 dimensions.
  dim_check = (size(image_in,/n_dim) eq 2)
  if not (num_check and dim_check) then begin
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Input "image_in" ' $
                        + 'needs to be a single XRT image.', $
                        '              Aborting...']
    qabort = 1B
    return
  endif
  if q_vb then print, '                                       ...OK'


;=== Check dark input =================================================

;===== This block checks the "dark_file" keyword input.
  if q_vb then print, 'XRT_DARK_SUB: Checking "dark_file" keyword...'

   ;== Current version requires dark file to be supplied.
  if not q_dkfil then begin
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Requires that ' $
                       + 'dark_file be supplied as an input.',      $
                       '              Aborting...']
    qabort = 1B
    return
  endif 

  ;=== Keyword needs to be a string filename.
  if (datatype(dark_file) ne 'STR') then begin
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Keyword ' $
                + '"dark_file" needs to be a string naming ', $
                '              a dark FITS file. Aborting...']
    qabort = 1B
    return
  endif

  ;=== Keyword needs to be a single filename.
  if (n_elements(dark_file) ne 1) then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: Keyword "dark_file" ' $
                                    + 'needs to be a scalar. Aborting...'
    qabort = 1B
    return
  endif
  if q_vb then print, '                                          ...OK'


;===== This block reads in "dark_file".
  if q_vb then print, 'XRT_DARK_SUB: Reading "dark_file"...'
  read_xrt, dark_file, index_dark, image_dark,      $
            verbose=verbose, quiet=quiet, qstop=qstop
  if q_vb then print, '                                 ...OK'


;===== This block checks "index_dark".
  if q_vb then print, 'XRT_DARK_SUB: Checking the dark index...'

  ;=== Check "index_dark".
  if (datatype(index_dark) ne 'STC') then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: Dark index needs ' $
             + 'to be a structure from an XRT FITS header. Aborting...'
    qabort = 1B
    return
  endif
  if (n_elements(index_dark) ne 1) then begin
    if (not q_qt) then box_message, 'XRT_DARK_SUB: Dark index needs ' $
                                    + 'to be a scalar. Aborting...'
    qabort = 1B
    return
  endif

  ;=== Check for necessary tags.
  if not required_tags(index_dark, 'CAL_INFO,EC_IMTYP,CCD_TEMP,CHIP_SUM,' $
           + 'EXPTIME,CCD_READ,DATE_OBS,RPOS_COL,RPOS_ROW,RSIZ_COL,'      $
           + 'RSIZ_ROW,CTIME,HISTORY,COMMENT', missing=missing) then begin 
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Dark index does ' $
             + 'not have these required tags:',                       $
             '              ' + missing, '              Aborting...']
    qabort = 1B
    return
  endif else begin
    drk_cal = gt_tagval(index_dark, /cal_info)  ;Cal image? 0=no 1=yes
    drk_typ = gt_tagval(index_dark, /ec_imtyp)  ;Type? 0=normal 1=dark
    drk_tmp = gt_tagval(index_dark, /ccd_tmpc)  ;CCD temperature
    drk_sum = gt_tagval(index_dark, /platescl)  ;CCD summing factor
    drk_edi = gt_tagval(index_dark, /ec_einde)  ;Exposure duration index
    drk_exp = gt_tagval(index_dark, /exptime)   ;Actual exposure duration
    drk_prt = gt_tagval(index_dark, /ccd_read)  ;Read port 0=right 1=left
    drk_tim = gt_tagval(index_dark, /date_obs)  ;Image time
    drk_ctm = gt_tagval(index_dark, /ctime)     ;Image time readable
    drk_x0  = gt_tagval(index_dark, /rpos_col)  ;ROI x-corner
    drk_y0  = gt_tagval(index_dark, /rpos_row)  ;ROI y-corner
    drk_nx0 = gt_tagval(index_dark, /rsiz_col)  ;ROI x-size
    drk_ny0 = gt_tagval(index_dark, /rsiz_row)  ;ROI y-size
  endelse

  ;=== Is actually a dark?
  if (drk_typ ne 1) then begin
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Input "dark_file" ' $
                       + 'does not actually contain ',                  $
                       '              dark image data. Aborting...']
    qabort = 1B
    return
  endif 

  if q_vb then print, '                                     ...OK'


;===== This block checks "image_dark".
  if q_vb then print, 'XRT_DARK_SUB: Checking dark image...'

  ;=== Ensure "image_dark" is of a number type.
  num_check = total(is_number(image_dark)) eq n_elements(image_dark)
  ;=== Ensure "image_dark" has 2 dimensions.
  dim_check = (size(image_dark,/n_dim) eq 2)
  if not (num_check and dim_check) then begin
    if (not q_qt) then box_message, ['XRT_DARK_SUB: Input "image_dark" ' $
                        + 'needs to be a single XRT image.', $
                        '              Aborting...']
    qabort = 1B
    return
  endif
  if q_vb then print, '                                 ...OK'


;===== Currently not supporting CAL darks as darks.
  if ((drk_cal eq 1) or (n_elements(image_dark[*,0]) gt 2048) $
            or (n_elements(image_dark[0,*]) gt 2048)) then begin
    if (not q_qt) then box_message, ['XRT_DARK_SUB: CAL darks not ' $
                       + 'currently accepted for regular',          $
                       '              dark subtraction. Aborting...']
    qabort = 1B
    return
  endif


;=== Match dark to data ===============================================

;===== This block checks whether image and dark cover the same ROI.
  if q_vb then print, 'XRT_DARK_SUB: Checking whether the image and ' $
                      + 'dark can be matched...'

  ;=== Has "image_in" been rescaled?
  if (((n_elements(image_in[*,0])*img_sum) ne img_nx0) or        $
      ((n_elements(image_in[0,*])*img_sum) ne img_ny0)) then begin
    if (not q_qt) then box_message,                                     $
      ['XRT_DARK_SUB: Input "image_in" has been rescaled or otherwise', $
       '              altered such that the pixel ranges cannot be ',   $
       '              ascertained from the header. Aborting...']
    qabort = 1B
    return
  endif


;===== This block checks several matches between the image and dark.
;===== A mismatch causes an abort unless the "force" keyword has
;===== been invoked.

  ;=== Does dark span image's pixels?
  if ((drk_x0 gt img_x0) or ((drk_x0+drk_nx0) lt (img_x0+img_nx0)) or $
      (drk_y0 gt img_y0) or ((drk_y0+drk_ny0) lt (img_y0+img_ny0)))   $
                                                             then begin
    if not q_force then begin
      if (not q_qt) then box_message, 'XRT_DARK_SUB: Dark ROI does ' $
                              + 'not match the image ROI. Aborting...'
      qabort = 1B
      return
    endif else begin
      if q_vb then print, 'XRT_DARK_SUB: Dark ROI does not match the ' $
                          + 'image ROI.'
      if q_vb then print, '              Forced to proceed.'
      mismatch_span = 1B
    endelse
  endif else begin
    mismatch_span = 0B
  endelse

  ;=== Does dark summing match image's?
  if (img_sum ne drk_sum) then begin
    if not q_force then begin
      if (not q_qt) then box_message,                       $
        ['XRT_DARK_SUB: Dark chip-summing does not match ', $
         '              the image chip-summing. Aborting...']
      qabort = 1B
      return
    endif else begin
      if q_vb then print, 'XRT_DARK_SUB: Dark chip-summing does not ' $
                          + 'match the image ROI.'
      if q_vb then print, '              Forced to proceed.'
      mismatch_sum = 1B
    endelse
  endif else begin
    mismatch_sum = 0B
  endelse

  ;=== Does dark exposure duration match image's?
  if (img_edi ne drk_edi) then begin
    if not q_force then begin
      if (not q_qt) then box_message,                            $
        ['XRT_DARK_SUB: Dark exposure duration does not match ', $
         '              the image exposure duration. Aborting...']
      qabort = 1B
      return
    endif else begin
      if q_vb then print, 'XRT_DARK_SUB: Dark exposure duration does ' $
                          + 'not match the image'
      if q_vb then print, '              exposure duration. Forced '   $
                          + 'to proceed.'
    endelse
  endif

  ;=== Does dark read-port match image's?
  if (img_prt ne drk_prt) then begin
    if not q_force then begin
      if (not q_qt) then box_message,                                $
        ['XRT_DARK_SUB: CCD read-port for the dark does not match ', $
         '              that for the image. Aborting...']
      qabort = 1B
      return
    endif else begin
      if q_vb then print, 'XRT_DARK_SUB: CCD read-port for the dark ' $
                          + 'does not match'
      if q_vb then print, '              that for the image. Forced ' $
                          + 'to proceed.'
    endelse
  endif

  ;=== Is dark's CCD temperature close enough to image's?
  temp_diff = abs(img_tmp - drk_tmp)
  aa0 = string(temp_diff,           format='(F6.2)')
  aa1 = string(temp_diff_threshold, format='(F6.2)')
  if (temp_diff gt temp_diff_threshold) then begin
    if not q_force then begin
      if (not q_qt) then box_message,                                $
        ['XRT_DARK_SUB: CCD temperature for the dark differs from ', $
         '              that for the image by ' + aa0 + ' degrees.', $
         '              Limit is ' + aa1 + '. Aborting...']
      qabort = 1B
      return
    endif else begin
      if q_vb then print, 'XRT_DARK_SUB: CCD temperature for the '      $
                  + 'dark differs from that'
      if q_vb then print, '              for the image by ' + aa0 + $
                          ' degrees. Limit is ' + aa1 + ' degrees.'
      if q_vb then print, '              Forced to proceed.'
    endelse
  endif

  ;=== Is dark's exposure date close enough to image's?
  time_diff = abs(anytim(img_tim,/utime) - anytim(drk_tim,/utime))
  time_diff = time_diff / 3600d / 24d
  aa0 = string(time_diff,           format='(F7.3)')
  aa1 = string(time_diff_threshold, format='(F7.3)')
  if (time_diff gt time_diff_threshold) then begin
    if not q_force then begin
      if (not q_qt) then box_message,                              $
        ['XRT_DARK_SUB: Exposure date for the dark differs from ', $
         '              that for the image by ' + aa0 + ' days.',  $
         '              Limit is ' + aa1 + '. Aborting...']
      qabort = 1B
      return
    endif else begin
      if q_vb then print, 'XRT_DARK_SUB: Exposure date for the dark '   $
                          + 'differs from that'
      if q_vb then print, '              for the image by ' + aa0 + $
                          ' days. Limit is ' + aa1 + ' days.'
      if q_vb then print, '               Forced to proceed.'
    endelse
  endif

  if q_vb then print, '                                          ' $
                      + '                      ...OK'


  if q_vb then print, 'XRT_DARK_SUB: Matching dark to image...'
;===== This block extracts the part of the dark that matches to the
;===== image. By this point in the program, either mismatches have
;===== already aborted the routine, or the "force" keyword was invoked.

  ;=== Make dark summing match that of image.
  if mismatch_sum then begin
    image_dark = xrt_resum_img(temporary(image_dark), drk_sum,        $
                               img_sum, quiet=quiet, verbose=verbose, $
                               qabort=qabort, qstop=qstop)
    if qabort then begin
      if (not q_qt) then box_message, 'XRT_DARK_SUB: Cannot match ' $
                         + 'dark summing level to image. Aborting...'
      return
    endif
    aa0 = strcompress(drk_sum,/rem)
    aa1 = strcompress(img_sum,/rem)
    hist_entry0 = 'Dark summing level does not match image. Forced ' + $
                  'to re-sum dark to match image: ' + aa0 + 'x --> ' + $
                  aa1 + 'x.'
    hist_entry  = [hist_entry, hist_entry0]
  endif else begin
    hist_entry0 = 'Dark summing level matches image: ' + $
                  strcompress(img_sum,/rem) + 'x.'
  endelse

  ;=== These next coords are "absolute" wrt the CCD corner.
  ;=== The "1" subscript means they have units of "summed pixels".
  ;=== (Eg: img_x0 = 80 in CCD pixels, img_sum = 8 ==> img_x1 = 10.)
  img_nx1 = long(img_nx0/img_sum)
  img_ny1 = long(img_ny0/img_sum)
  img_x1 = long(img_x0/img_sum)
  img_y1 = long(img_y0/img_sum)
  drk_nx1 = long(drk_nx0/img_sum)
  drk_ny1 = long(drk_ny0/img_sum)
  drk_x1 = long(drk_x0/img_sum)
  drk_y1 = long(drk_y0/img_sum)


  ;=== If dark ROI doesn't completely span the image ROI.
  if mismatch_span then begin

    ;=== If dark ROI is smaller than the image ROI?
    if ((drk_nx0 lt img_nx0) or (drk_ny0 lt img_ny0)) then begin
      if (not q_qt) then box_message,                         $
        ['XRT_DARK_SUB: Dark ROI is smaller than image ROI.', $
         '              Cannot subtract. Aborting...']
      qabort = 1B
      return

    ;=== Else if dark ROI is big enough, but doesn't span image.
    endif else begin
      if (not q_qt) then box_message,                            $
        ['XRT_DARK_SUB: Dark ROI does not match image ROI.',     $
         '              Forced to use lower-left corner of dark.']
      image_dark = temporary(image_dark[0:img_nx1-1, 0:img_ny1-1])
      aa0 = strcompress(img_nx0-1,/rem)
      aa1 = strcompress(img_ny0-1,/rem)
      aa2 = strcompress(img_x0,/rem)
      aa3 = strcompress(img_x0+img_nx0-1,/rem)
      aa4 = strcompress(img_y0,/rem)
      aa5 = strcompress(img_y0+img_ny0-1,/rem)
      hist_entry0 = 'Dark ROI does not match image. Forced to use ' +   $
                    'dark corner. Dark ROI = [0:' + aa0 + ',0:' + aa1 + $
                    '] --> Image ROI = [' + aa2 + ':' + aa3 + ',' + aa4 $
                    + ':' + aa5 + '].'
      hist_entry  = [hist_entry, hist_entry0]
    endelse

  ;=== Else if dark ROI *does* span the image ROI, then get that span.
  endif else begin
    ;=== These next coords are "relative" to the corner of the dark's ROI.
    ;=== The "1" subscript still means they have units of "summed pixels".
    x1 = img_x1 - drk_x1
    x2 = x1     + img_nx1 - 1
    y1 = img_y1 - drk_y1
    y2 = y1     + img_ny1 - 1
    image_dark = temporary(image_dark[x1:x2, y1:y2])
    aa0 = strcompress(img_x0,/rem)
    aa1 = strcompress(img_x0+img_nx0-1,/rem)
    aa2 = strcompress(img_y0,/rem)
    aa3 = strcompress(img_y0+img_ny0-1,/rem)
    hist_entry0 = 'Dark ROI matches image. ROI = [' + aa0 + ':' + aa1 + $
                  ',' + aa2 + ':' + aa3 + '].'
    hist_entry  = [hist_entry, hist_entry0]
  endelse

stop

;===== This block subtracts the CCD bias (pedestal) from the dark.

  ;=== This needs to happen before the normalization.
  ;=== An earlier block matched the dark's summing level to that
  ;=== of the image, but did not update drk_sum, so use img_sum.
  ;=== This program assumes that the image has already had its
  ;==== bias subtracted.
  image_dark = (temporary(image_dark) - (ccd_bias * (img_sum^2.))) > 0


stop
;===== Normalize the exposure.
  if (drk_edi ne img_edi) then begin
    image_dark = temporary(image_dark) * img_exp / drk_exp
    aa0 = string(drk_exp, format='(F6.3)')
    aa1 = string(img_exp, format='(F6.3)')
    hist_entry0 = 'Dark exposure duration does not match image. ' + $
                  'Forced to normalize to image. Dark exp (s) = ' + $
                  aa0 + ' --> Image exp (s) = ' + aa1 + '.'
    hist_entry  = [hist_entry, hist_entry0]
  endif else begin
    aa0 = string(img_exp, format='(F6.3)')
    hist_entry0 = 'Dark exposure duration matches image. Exp (s) = ' $
                  + aa0 + '.'
    hist_entry  = [hist_entry, hist_entry0]
  endelse


;===== Adjust for temperature.
  ;=== (This part not implemented yet.)
  aa0 = string(drk_tmp, format='(F6.2)')
  aa1 = string(img_tmp, format='(F6.2)')
  hist_entry0 = 'No calibration for temperature. Dark temp (C) = ' $
                + aa0 + '. Image temp (C) = ' + aa1 + '.'
  hist_entry  = [hist_entry, hist_entry0]
  if q_vb then print, '                                    ...OK'


;=== Subtract dark from image =========================================

stop
  ;=== Do the deed.
  if q_vb then print, 'XRT_DARK_SUB: Subtracting dark from image...'
  image_out = (image_in - image_dark) > 0
  index_out = index_in
  if q_vb then print, '                                         ...OK'

  ;=== Now is the time to update the HISTORY tag.
  if q_vb then print, 'XRT_DARK_SUB: Updating HISTORY...'
  hist_prefix = prognam + ' ' + progver + ': (' + systim() + ') '
  hist_entry0 = 'Subtracted dark. Dark file: ' + $
                strip_dirname(dark_file)
  ;=== Remove placeholder.
  hist_entry = hist_entry[1:*]
  hist_entry  = hist_prefix + [hist_entry0, hist_entry]
  update_history, index_out, hist_entry, /noroutine
  if q_vb then print, '                              ...OK'


;=== Print logs =======================================================


  if (not q_qt) then begin
    td = abs(anytim(img_tim,/utime) - anytim(drk_tim,/utime))
    dd = strcompress(long(td/86400),      /rem)
    hh = strcompress(long(td/3600) mod 24,/rem)
    mm = strcompress(long(td/60)   mod 60,/rem)
    ss = strcompress(long(td)      mod 60,/rem)
    box_message, ['XRT_DARK_SUB: Image time = ' + img_ctm + '.',  $
                  '              Dark time  = ' + drk_ctm + '.',  $
                  '              Difference = ' + dd + ' dys, '   $
                  + hh + ' hrs, ' + mm + ' min, ' + ss + ' sec.', $
                  '              Processing time = ' + $
                  strcompress(systime(1)-t0,/rem) + ' sec.'        ]
  endif


  if q_vb then begin
    print, 'XRT_DARK_SUB: Adding verbose information...'

    aa0 = string(img_sum, format='(I1)')
    aa1 = string(drk_sum, format='(I1)')
    print, '              Image sum = ' + aa0 + $
           'x.                Dark sum = ' + aa1 + 'x.'
    aa0 = string(img_exp, format='(F6.3)')
    aa1 = string(drk_exp, format='(F6.3)')
    print, '              Image exposure (s) = ' + aa1 + $
           '.   Dark exposure (s) = ' + aa1 + '.'
    aa0 = string(img_tmp, format='(F6.2)')
    aa1 = string(drk_tmp, format='(F6.2)')
    print, '              Image CCD temp (deg) = ' + aa0 + $
           '. Dark CCD temp (deg) = ' + aa1 + '.'

    img_sdv = stdev(image_in,   img_men)
    drk_sdv = stdev(image_dark, drk_men)
    aa0 = string(img_men, format='(F8.3)')
    aa1 = string(drk_men, format='(F8.3)')
    aa2 = string(img_sdv, format='(F8.3)')
    aa3 = string(drk_sdv, format='(F8.3)')
    print, '              Image mean = ' + aa0   $
           + '.         Dark mean = ' + aa1 + '.'
    print, '              Image std-dev = ' + aa2 $
           + '.      Dark std-dev = ' + aa3 + '.'

    img_min = min(image_in)
    img_max = max(image_in)
    drk_min = min(image_dark)
    drk_max = max(image_dark)
    aa0 = string(img_min, format='(F8.3)')
    aa1 = string(drk_min, format='(F8.3)')
    aa2 = string(img_max, format='(F8.3)')
    aa3 = string(drk_max, format='(F8.3)')
    print, '              Image min = ' + aa0 $
           + '.          Dark min = ' + aa1 + '.'
    print, '              Image max = ' + aa2 $
           + '.          Dark max = ' + aa3 + '.'

    drk_hst = gt_tagval(index_dark, /history)
    drk_com = gt_tagval(index_dark, /comment)
    print, '              Dark history    = '
    ss = where(drk_hst ne '', count)
    if (count gt 0) then print, transpose(drk_hst)
    print, '              Dark commentary = '
    ss = where(drk_com ne '', count)
    if (count gt 0) then print, transpose(drk_com)

  endif

if qstop then stop

END ;=======================================================================
