FUNCTION fg_dark_index
get_utc, utc, /ccsds
index_dark = {SIMPLE:0, BIXPIX:16, NAXIS:2, NAXIS1:4096, NAXIS2:2048 $
         , DATE:utc, TELESCOP:'HINODE', TIMESYS:'UTC', INSTRUME:'SOT/WB' $
         , OBS_TYPE:'FG DARK MODEL DATA', OBS_ID:1, GEN_ID:1, FRM_ID:1 $
         , DARKFLAG:1, CAMAMP:0, CAMDACA:8, CAMDACB:8 $
         , CAMPSUM:1, CAMSSUM:1, ROISTART:0, ROISTOP:1025 $
         , FGCCDIX0:0, FGCCDIX1:4095, FGCCDIY0:0, FGCCDIY1:2047 $
         , FGBINX:1, FGBINY:1, DATE_OBS:utc $
         , COMMENT:strarr(1), HISTORY:strarr(1)}
RETURN, index_dark
end

;-----------------------------------------------------------------------------
PRO fg_dark_sub,   index, data, index_out, data_out, mask,  	            $
                   darkdir=darkdir,                                         $
                   user_mask=user_mask,       user_file=user_file,          $
                   user_dark=user_dark,       user_dindex=user_dindex,      $
                   dark_image=dark_image,     dark_index=dark_index,        $
                   no_float=no_float,                                       $
                   dc_right=dc_right,         dc_left=dc_left,              $
                   retrn=retrn,               run_time=run_time,            $
                   verbose=verbose,           loud=loud,                    $
                   version=progver,           name=prognam,                 $
                   qstop=qstop,               update=update 
;+
; NAME:
;	FG_DARK_SUB_BETA1
;
; PURPOSE:
;       Subtract ADC offset and dark currents from SOT/FG images on
;       each half of CCD. ADC offset is temperature dependent and
;       different for left and right halves of CCD.  
;
; CATEGORY:
;	FITS processing
;
; SAMPLE CALLING SEQUENCE:
;	fg_dark_sub, index, data, index_out, data_out, mask $
;                 [, user_mask=user_mask, darkdir=darkdir            $
;                  , user_dark=user_dark, user_dindex=user_dindex      $
;                  , dc_left=dc_left, dc_right=dc_right, /no_float   $
;		   , dark_image=dark_image, dark_index=dark_index    $
;                  , version=version, name=name, /update ]
;
; INPUTS:
;	INDEX	  - the SSW index structure for each image
;	DATA	  - a 2- or 3-D data array (NX x NY [x N])
;
; OUTPUTS:
;       INDEX_OUT - the SSW header index structure for the corrected
;                   data. Currently this is just INDEX, unmodified.
;       DATA_OUT - The original array with the dark image subtracted,
;                   converted to floating point unless /NO_FLOAT is set.
;
; OUTPUTS (Optional):
;       MASK      - a 2-D image showing valid and invalid pixels. This array
;                   is useful when filling the bad pixels in fg_bad_pix.
;	
; OPTIONAL INPUT KEYWORD PARAMETERS: 
;       USER_MASK  - A user-supplied mask of dead and/or hot pixels. 
;                    The MASK array returned by FG_SHIFT_PIX can be used.
;	DARKDIR    - Alternate directory for processed dark current fits images,
;		     default =  concat_dir('$SOT_SSWDB','darks') for dark images
;       USER_DARK  - A user supplied dark image can be input in this keyword.
;       USER_DINDEX- The index structure associated with user_dark.
;                    Mandatory if user_dark is supplied.
;	DC_LEFT    - If set, then do not select or read in a dark current file,
;                    just use a scalar value for the backround level for the left half of the CCD.
;       DC_RIGHT   - Ditto for right half of CCD. 
;       NO_FLOAT   - If set, the dark subtracted image is not converted to floating point. 
;	VERBOSE    - Set for a few more messages; overrides /loud
;	LOUD	   - Not set to minimize the messages
;	QSTOP	   - Set to stop in this procedure for debuging purposes
;       UPDATE     - If set, update history record in INDEX_OUT
;
; OUTPUT KEYWORD PARAMETERS: 
;	DARK_IMAGE - The dark current image before binning or extraction
;	DARK_INDEX - The dark current index for the raw dark image
;	VERSION    - The current version number of the program
;       NAME       - The name of this routine
;	RETRN      - Flag for branching on unusual return
;
; PROCEDURE:
;	1. Determine CEB temp and CCD temp for input image.
;       2. Create artificial dark using a regression of ADC offset 
;          on CEB and CCD temp as well as dark currents. 
;          Okamoto san determined a regression on CEB and CCD temp:
;            T = T_CCD/69.7 + T_CEB/18.0
;       3. Subtract dark image from data image.
;          Perform subtraction in floating point arithmetic. Return
;          image in floating point for subsequent processing unless
;          keyword no_float is set.
;
; MODIFICATION HISTORY:
;V0.1  Created 01-Oct-2006. T.E. Berger, LMSAL.
;V1.0  Pixel shift correction is addded.  16-Apr-2007, YK
;V1.1  Minor typing corrections. TEB 24-Apr-2007.
;v1.2  Update to handle multi-image NFI data arrays. TEB 24-Oct-2007.
;v1.21 Remove fg_shift_pix from modeled dark image section. TEB 25-Oct-2007.
;v1.22 Modify dark_index to allow fg_shift_pix. TEB 6-Nov-2007.
;v1.3  Use fg_get_dark to find an appropriate dark image. YK 21-Nov-2007
;v1.31 Added V/I and dopplergram checks in image_type switch. TEB 19-Apr-2008.
;v1.32 Changed "user_index" to "user_dindex" to fix user dark bug. TEB30-Jun-2008.
;v1.4  Added binned image capability. TEB 28-Jul-2008.
;v1.41 Fix a bug in extraction of ROI. YK 25-Sep-2008.
;v1.42 2020-07-28 - GLS - Make sure that candidate dark is same size as image.
;-
;-----------------------------------------------------------------------------
;
; Program name and version:
prognam = 'FG_DARK_SUB.PRO'
progver = 'V1.42'
prog_nam_ver = 'FG_DARK_SUB.PRO V1.42'

;Constants
xmax = 4095
ymax = 2047
x0 = 0 
y0 = 0

time0 = SYSTIME(1)
retrn = 0
loud = 0
loud = KEYWORD_SET(loud)
verbose = KEYWORD_SET(verbose)
if (verbose eq 1) then loud = 1
if (loud) then MESSAGE,/INFO, 'Start  '+prognam+' '+progver
if KEYWORD_SET(no_float) then nofloat=1 else nofloat=0

; for input image:
img_dateobs = gt_tagval(index,/date_obs) ; image date/time                             
img_observable = fg_genid(gt_tagval(index,/gen_id)) ; GEN_ID has a 1:1 mapping to obs_type        
img_wave = fg_waveid(gt_tagval(index,/waveid)) ; wavelength of observable                    
img_exptime = gt_tagval(index,/exptime) ; exposure time                               
img_nx0 = gt_tagval(index[0],/naxis1) ; image size nx0
img_ny0 = gt_tagval(index[0],/naxis2) ; image size ny0
img_ix0 = gt_tagval(index[0],/fgccdix0) ; image x0 in CCD coordinates
img_ix1 = gt_tagval(index[0],/fgccdix1) ; image x1 in CCD coordinates
img_iy0 = gt_tagval(index[0],/fgccdiy0) ; image y0 in CCD coordinates
img_iy1 = gt_tagval(index[0],/fgccdiy1) ; image y1 in CCD coordinates
img_gain = gt_tagval(index[0],/camgain) ; camera gain                                 
img_amp = gt_tagval(index[0],/camamp) ; ccd amplifier used                          
img_daca = gt_tagval(index[0],/camdaca) ; ccd DAC A                                   
img_dacb = gt_tagval(index[0],/camdacb) ; ccd DAC B                                   
img_psum = gt_tagval(index[0],/campsum) ; Parallel summing (summing in x direction)    
img_ssum = gt_tagval(index[0],/camssum) ; Serial summing (in y-direction)              
img_tccd = gt_tagval(index[0],/t_fgccd) ; Temperature of the FG CCD                    
img_tceb = gt_tagval(index[0],/t_fgceb) ; Temperature of the FG camera electronics box 
img_binx = gt_tagval(index[0],/fgbinx) ; Binning in x-direction                       
img_biny = gt_tagval(index[0],/fgbiny) ; Binning in y-direction                       
img_roi0 = gt_tagval(index[0],/roistart) ; CCD readout codewords
img_roi1 = gt_tagval(index[0],/roistop)

if (verbose) then begin
   MESSAGE,/info,'Input image information.'
   MESSAGE,/info,'      Date/Time: '+img_dateobs
   MESSAGE,/info,'     Wavelength: '+img_wave+' Angstrom'
   MESSAGE,/info,'     Observable: '+img_observable
   MESSAGE,/info,'  Exposure time: '+STRTRIM(img_exptime,2)+' seconds'
   MESSAGE,/info,'       CCD gain: '+STRTRIM(img_gain,2)
   MESSAGE,/info,'        CCD amp: '+STRTRIM(img_amp,2)
   MESSAGE,/info,'    CCD summing: '+STRTRIM(img_psum,2)+' x '+STRTRIM(img_ssum,2)+' (parallel x serial)'
   MESSAGE,/info,'    CCD binning: '+STRTRIM(img_binx,2)+' x '+STRTRIM(img_biny,2)+' (X x Y)'
   MESSAGE,/info,'       CEB temp: '+STRTRIM(img_tceb,2)+' deg C'
   MESSAGE,/info,'       CCD temp: '+STRTRIM(img_tccd,2)+' deg C'
end

dc_scalar = 0
if KEYWORD_SET(dc_right) then begin
   dc_scalar = 1
   dcr = dc_right
   if not KEYWORD_SET(dc_left) then dcl = dcr
end
if KEYWORD_SET(dc_left) then begin
   dc_scalar = 1
   dcl = dc_left
   if not KEYWORD_SET(dc_right) then dcr = dcl
end

if (dc_scalar) then begin                          ;Simple subtraction. Mostly for testing purposes. 
    if (verbose) then begin
        MESSAGE, /info, 'Scalar bias is used. '
        MESSAGE, /info, '       Left: '+string(dcl, form='(I4)')
        MESSAGE, /info, '      Right: '+string(dcr, form='(I4)')
    endif
    dark_image = FLTARR(4096/img_psum,2048/img_ssum)
    dark_image[0:2048/img_psum-1,*] = dcl
    dark_image[2048/img_psum:4096/img_psum-1,*] = dcr

    ;Convert ROI to image/solar orientation
    x0 = (4095-img_ix1)/img_psum
    x1 = x0+img_nx0-1
    y0 = (2047-img_iy1)/img_ssum
    y1 = y0+img_ny0-1
    ; extract ROI
    dark_image = dark_image[x0:x1, y0:y1]

    ; make output index structure
    dark_index = fg_dark_index()
    dark_index.naxis1 = img_nx0
    dark_index.naxis2 = img_ny0
    dark_index.fgccdix0 = img_ix0
    dark_index.fgccdix1 = img_ix1
    dark_index.fgccdiy0 = img_iy0
    dark_index.fgccdiy1 = img_iy1

end else if KEYWORD_SET(user_dark) then begin                  ;Dark image and index supplied by keywords...

   if datatype(user_dindex) ne 'STC' then begin
        MESSAGE,'Need to supply SSW index structure with dark image...'
        RETURN
   end

   dark_nx0 = gt_tagval(user_dindex,/naxis1)  ; dark image x-size
   dark_ny0 = gt_tagval(user_dindex,/naxis2)  ; dark image y-size
   dark_psum = gt_tagval(user_dindex,/campsum)  ; dark image ccd parallel summing factor 
   dark_ssum = gt_tagval(user_dindex,/camssum)  ; dark image ccd serial summing factor   
   dark_exptime = gt_tagval(user_dindex,/exptime) ; dark image exposure time               
   dark_amp = gt_tagval(user_dindex,/camamp)      ; dark image amp    
   dark_gain = gt_tagval(user_dindex,/camgain)    ; dark image gain       
   dark_tceb = gt_tagval(user_dindex,/t_fgceb)    ; CEB temp
   dark_tccd = gt_tagval(user_dindex,/t_fgccd)    ; CCD temp
   if (verbose) then begin
      MESSAGE,/info,'User-supplied dark information.'
      MESSAGE,/info,'  Exposure time: '+STRTRIM(dark_exptime,2)+' seconds'
      MESSAGE,/info,'       CCD gain: '+STRTRIM(dark_gain,2)
      MESSAGE,/info,'        CCD amp: '+STRTRIM(dark_amp,2)
      MESSAGE,/info,'    CCD summing: '+STRTRIM(dark_psum,2)+' x '+STRTRIM(dark_ssum,2)+' (parallel x serial)'
      MESSAGE,/info,'       CEB temp: '+STRTRIM(dark_tceb)+' deg C'
      MESSAGE,/info,'       CCD temp: '+STRTRIM(dark_tccd)+' deg C'
   endif
   
    ;Checks on user-supplied dark image:
   if (dark_psum gt img_psum) or (dark_ssum gt img_ssum) then begin
      MESSAGE,/INFO, 'User supplied dark image is summed more than image. Returning...' 
      index_out = -1
      data_out = -1
      retrn = 1
      RETURN
   end
   
   if (dark_nx0 lt img_nx0) or (dark_ny0 lt img_ny0) then begin
      MESSAGE,/INFO,'Dark too small for image. Returning...'
      index_out = -1
      data_out = -1
      retrn=1
      RETURN
   end
   
                                ; check FG_SHIFT_PIX has been applied to the data
                                ; dark image also should be shifted
    previous = get_history(index,caller='FG_SHIFT_PIX',found=found)
    if found then begin
;STOP, ' Stopping before call to fg_shift_pix.'
       fg_shift_pix, dark_index, dark_image, dark_index2, dark_image2, $
                     dark_mask, /update
       dark_index = TEMPORARY(dark_index2)
       dark_image = TEMPORARY(dark_image2)
    endif

    if (ABS(dark_tceb - img_tceb) gt 5.0) then begin
                                ; No return 
       MESSAGE,/INFO,'CEB temperature differs from image CEB temp by more than 5 degrees.'
    end
    
    if (ABS(dark_tccd - img_tccd) gt 5.0) then begin
                                ; No return
       MESSAGE,/INFO,'CCD temperature differs from image CCD temp by more than 5 degrees. '
    end 
    dark_image = user_dark
    dark_index = user_dindex
    
end else begin                 ;Use modeled dark image based on CEB and CCD temperatures

   if N_ELEMENTS(darkdir) eq 0 then darkdir = concat_dir(getenv('SOT_SSWDB_FG_CAL'),'darks')
   fg_get_dark, index, dark_index, dark_image, $
                      darkdir=darkdir, deltaday=deltaday, retrn=retrn, darkfile=darkfile
   
   if deltaday eq -1 then unitarr=1 else unitarr=0
   if (retrn eq 1) then begin
      MESSAGE, /CONT, 'No dark file found. Returning uncalibrated image.'
      data_out  = data
      index_out = index
      retrn = 1
      RETURN
   end
   if (loud) and (N_ELEMENTS(dark_image) gt 0) and (deltaday ne -1) then begin
      MESSAGE, /info, 'Dark file found in the database. '
      MESSAGE, /info, '      File name: '+darkfile
      MESSAGE, /info, 'Time difference: '+STRTRIM(deltaday,2)+ ' days'
   endif 
   
    ; check FG_SHIFT_PIX has been applied to the data
    ; dark image also should be shifted
    previous = get_history(index,caller='FG_SHIFT_PIX',found=found)
    if found then begin
       fg_shift_pix, dark_index, dark_image, dark_index2, dark_image2, $
                           dark_mask, /update
       dark_index = TEMPORARY(dark_index2)
       dark_image = TEMPORARY(dark_image2)
    endif
    dark_nx0  = gt_tagval(dark_index[0],/naxis1)                   ; dark image x-size
    dark_ny0  = gt_tagval(dark_index[0],/naxis2)                   ; dark image y-size
    dark_img_dateobs = gt_tagval(dark_index[0],/date_obs)		; dark date and time in YYYY-MM-DDThh:mm:ss.mmm format
    dark_img_date = STRMID(dark_img_dateobs,0,10)               ; dark image date
    dark_img_time = STRMID(dark_img_dateobs,12,12)              ; dark image time
    dark_amp  = gt_tagval(dark_index[0],/camamp)			; dark ccd amplifier used
    dark_gain = gt_tagval(dark_index[0],/camgain)                  ; dark ccd gain
    dark_daca = gt_tagval(dark_index[0],/camdaca)                  ; dark DAC A 
    dark_dacb = gt_tagval(dark_index[0],/camdacb)                  ; dark DAC B
    dark_psum = gt_tagval(dark_index[0],/campsum)                  ; dark parallel summing factor
    dark_ssum = gt_tagval(dark_index[0],/camssum)                  ; dark serial summing factor
    dark_t_fgccd = gt_tagval(dark_index[0],/t_fgccd)               ; dark ccd temperature				
    dark_x0   = gt_tagval(dark_index[0],/fgccdix0)                 ; dark x0
    dark_y0   = gt_tagval(dark_index[0],/fgccdiy0)                 ; dark y0
    dark_binx = gt_tagval(dark_index[0],/fgbinx)                   ; dark binning in x
    dark_biny = gt_tagval(dark_index[0],/fgbiny)                   ; dark binning in y
    
    ;Convert ROI to image/solar orientation
    x0 = ( (4095-img_ix1)>0<4095 )
    x1 = ( (4095-img_ix0)>0<4095 )
    y0 = ( (2047-img_iy1)>0<2047 )
    y1 = ( (2047-img_iy0)>0<2047 )
  
                                ;handle camera summing
    if img_psum ge 2 then begin
       if x0 mod dark_psum eq 0 then x0 = x0/dark_psum $
       else x0 = float(x0)/dark_psum
       if x1-1 mod dark_psum eq 0 then x1 = (x1-1)/dark_psum $
       else x1 = float(x1-1)/dark_psum
    endif
    
    if img_ssum ge 2 then begin
       if y0 mod dark_ssum eq 0 then y0 = y0/dark_ssum $
       else y0 = float(y0)/dark_ssum
       if y1-1 mod dark_ssum eq 0 then y1 = (y1-1)/dark_ssum $
       else y1 = float(y1-1)/dark_ssum
    endif

    if (dark_nx0 ne img_nx0) or (dark_ny0 ne img_ny0) then begin
       dark_image = dark_image[x0:x1, y0:y1] ;if statement guards against unity array from fg_get_dark
       dark_mask = dark_mask[x0:x1, y0:y1]
    end

    szxy = SIZE(dark_image,/dimen)
    dsx = szxy[0]
    dsy = szxy[1]
        
    if (dark_binx ne img_binx) then begin
       dark_image = REBIN(dark_image, dsx/img_binx, dsy/img_biny)
       dark_mask = REBIN(dark_mask, dsx/img_binx, dsy/img_biny)
       if (loud) then MESSAGE,/info,'WARNING: dark image is being rebinned to match image binning condition.'
    endif

 end


;Determine number of images in DATA array and effective exposures for
;each 
 nexp = fg_num_eff_exp(index, quiet=(1-loud)) 
 nimage = N_ELEMENTS(nexp)
; GLS - Added 2022-06-01 to handle 3D data arrays:
 ndim_data = (size(data))[0]
 if ndim_data eq 2 then nimage = 1
 if ndim_data eq 3 then nimage = n_elements(data[0,0,*])
;if n_elements(data[0,0,*]) gt 1 then nimage = n_elements(data[0,0,*])
;HELP, nexp, nimage & STOP, ' Stopping after call to fg_num_eff_exp
 if N_ELEMENTS(dark_mask) le 0 then dark_mask = BYTE(data)*0b+1b
 if N_ELEMENTS(user_mask) le 0 then user_mask = BYTE(data)*0b+1b
 if n_elements(dark_mask) ne n_elements(user_mask) then dark_mask = BYTE(data)*0b+1b
 mask = user_mask
 for i=0,nimage-1 do mask[*,*,i] = dark_mask*user_mask[*,*,i]
 
; Now convert to floating point (unless keyword no_float is set) and
; subtract the dark for FG and SI

 if (nofloat) then data_out = data else data_out = data*0.0
;Loop through images in data array
 for i=0,nimage-1 do begin
    
    image = data[*,*,i]
    imask = mask[*,*,i]
    image_type = fg_image_type(index,i)
    
    switch image_type of 

      'FG':
      'SI':begin
         if n_elements(dark_image) eq n_elements(image) then $
            image = (FLOAT(image) - dark_image*nexp[i])*imask
         image >= 0
         if (nofloat) then image = FIX(image)
         BREAK
      end

      'SQ':
      'SU':
      'SV':
      'VI':
      'DN':
      'DD':
      'DG':begin
         if (loud) then MESSAGE,/info,'Stokes Q, U, and V images and Dopplergrams are not dark corrected.'
         image *= imask
      end
      
    end
;
    data_out[*,*,i] = image
;HELP, i, image, data_out & pmm, image & pmm, data_out[*,*,i] & STOP, ' Stopping in fg_dark_sub nimage loop.'
 endfor

 index_out = index
; Update history record in the index if requested
 if KEYWORD_SET(update) then begin
    tagval = 'Subtracted dark pedestal and current.'
    update_history,index_out, tagval, version=progver
    if (loud) then MESSAGE, /INFO, 'HISTORY record updated :'+tagval
 endif
 
 run_time = SYSTIME(1) - time0
 if (loud) then MESSAGE,/INFO,'execution time: '+STRTRIM(run_time,2)+' seconds for 1 image'
 
 if (KEYWORD_SET(qstop)) then STOP
 
 RETURN
END


