;+
; NAME:
;	S;+
; NAME:
;	SNIRAC
;
; PURPOSE:
;	Calculates S/N for IRAC, including the Poisson noise
;       contribution of the target.
;
; CATEGORY:
;       Spitzer Space Telescope
;
; CALLING SEQUENCE:
;	SNIRAC
;
; OUTPUTS:
;	None.
;
; COMMON BLOCKS:
;	block1.
;
; RESTRICTIONS:
;	None.
;
; MODIFICATION HISTORY:
;       V1.0 - written by DWH, Spitzer SUST, March 2007
;       V1.1 - fixes for bugs found by Seppo (f_ch should not be changed 
;              in snirac_calc) and Sean (resolve_all calls obsolete program 
;              setlog under IDL 6.2-Mac).
;       V1.2 - as requested by Sean, removed 1.5x scaling of total noise 
;              (sigma), replaced with info in Help file telling users that 
;              S/N is an optimistic estimate and should be scaled by 
;              0.9-0.5x; also note this in the results window when a S/N 
;              value is returned.
;       V1.3 - warm IRAC version, added the 6 second, 200 second and 400
;              second frame times and appropriate 15% increase in read
;              noise 
;
;-
;***********************************************
;*** DON'T FORGET TO UPDATE THE VERSION NUMBER
;***********************************************

pro snirac_event, ev

 forward_function snirac_calc
 resolve_routine, 'snirac_calc', /is_function, /no_recompile
 resolve_routine, ['snirac_warm', 'snirac_event', 'snirac_help', 'snirac_help_event'], /no_recompile

 common block1, VERSION, ch, frametime, nexp, fd, background_sb, rin, rout, snout, nback, sigma_rn, e_s, e_bg, sigma_s, sigma_bg, sigma

 widget_control, ev.id, get_uvalue=uval
 if (n_elements(uval) eq 0) then uval = ''
 name = strmid(tag_names(ev, /structure_name), 7, 4)
 case (name) of
  'BUTT' : begin
            if (uval eq 'DONE') then begin
             widget_control, /destroy, ev.top
             return
            endif
            if (uval eq 'HELP') then begin
             snirac_help
             return
            endif
            if (uval eq 'CALC') then begin
             if (fd lt 0.0 or background_sb lt 0.0) then begin
              out_text=' '
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              out_text='Enter values for at least Target Flux Density and Background Surface Brightness'
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              out_text=' '
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              return
             endif else begin
              ;***DEBUG print, ch, frametime, nexp, fd, background_sb, rin, rout
              sn_out = snirac_calc(ch, frametime, nexp, fd, background_sb, rin, rout)
              out_text=' '
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              out_text='**********************************************************************'
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              out_text='S/N = '+sn_out+' '
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              out_text='**********************************************************************'
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              out_text=' '
              widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
              return
             endelse
            endif
           endcase
  'TEXT' : begin
            widget_control, ev.id, get_value=val
            case uval of
             'w_nexp' : begin
                         nexp = float(val)
                         out_text = 'Selected Total Number of Exposures: '+val
                        endcase
             'w_fd'   : begin
                         fd = float(val)
                         out_text = 'Selected Target Flux Density: '+val+' micro-Jy'
                        endcase
             'w_background_sb' : begin
                                  background_sb = float(val)
                                  out_text = 'Selected Background Surface Brightness: '+val+' (MJy/sr)'
                                 endcase
             'w_rin'  : begin
                          rin = float(val)
                          out_text = 'Selected Inner Radius of Background Annulus: '+val+' pixels'
                         endcase
             'w_rout' : begin
                          rout = float(val)
                          out_text = 'Selected Outer Radius of Background Annulus: '+val+' pixels'
                         endcase
            endcase
           endcase
  'LIST' : begin
            uval=uval[ev.index]
            if (ch lt 0.0) then ch=1
            if (frametime lt 0.0) then frametime=30.0 
            idx = where(uval eq [ '0.1 s', '0.4 s (Full array)', '2 s', '6 s', '12 s', '30 s', '100 s', '200 s', '400 s'], count)
            ;*** DEBUG print, ch, frametime, '   ', uval, count
            if (count le 0) then begin
            case uval of 
             '1 (3.6 microns)' : begin
                                  ch=1
                                  out_text = 'Selected Channel: '+uval
                                 end
             '2 (4.5 microns)' : begin
                                  ch=2
                                  out_text = 'Selected Channel: '+uval
                                 end
             '3 (5.8 microns)' : begin
                                  ch=3
                                  out_text = 'Selected Channel: '+uval
                                 end
             '4 (8.0 microns)' : begin
                                  ch=4
                                  out_text = 'Selected Channel: '+uval
                                 end
            endcase
            endif else begin
             case uval of
             '0.1 s'           : begin
                                  frametime=0.1
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '0.4 s (Full array)': begin
                                  frametime=0.4
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '2 s'             : begin
                                  frametime=2.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '6 s'             : begin
                                  frametime=6.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '12 s'            : begin
                                  frametime=12.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '30 s'            : begin
                                  frametime=30.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '100 s'           : begin
                                  frametime=100.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '200 s'           : begin
                                  frametime=200.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
             '400 s'           : begin
                                  frametime=400.0
                                  out_text = 'Selected Frame Time: '+uval
                                 end
            endcase
           endelse
           ;*** DEBUG print, ch, frametime, '   ', uval, count
           endcase
 else:
 endcase

 widget_control, widget_info(ev.top, find_by_uname='info_window'), set_value=out_text, /append
end


function snirac_calc, f_ch, f_frametime, f_nexp, f_fd, f_background_sb, f_rin, f_rout

; calculate number of pixels in background annulus
 f_nback = !pi * (f_rout^2.0 - f_rin^2.0)

; adjust channel number to array subscript
 f_ch_int = fix(f_ch) - 1

; determine frametime index
 case 1 of
  f_frametime eq   0.1 : fr = 0
  f_frametime eq   0.4 : fr = 1
  f_frametime eq   2.  : fr = 2
  f_frametime eq   6.  : fr = 3
  f_frametime eq  12.  : fr = 4
  f_frametime eq  30.  : fr = 5
  f_frametime eq 100.  : fr = 6
  f_frametime eq 200.  : fr = 7
  f_frametime eq 400.  : fr = 8
 endcase

; noise pixels (table 6.1 in the SOM, v9.1)
; selected via npix[ch]
 npix = [7.0, 7.2, 10.8, 13.4]

; read noise in electrons for the desired frametime (table 6.4 in the SOM, v9.1)
; selected via sigma_readnoise[fr,ch]
; estimates of readnoise for warm mission (pre IWIC)
; sigma_readnoise = [[11.8, 12.1, 9.1, 7.1], $
;                    [22.4, 23.7, 14.4, 12.0], $
;                    [11.8, 12.1, 9.1, 7.1], $
;                    [ 9.4,  9.4, 8.8, 6.7], $
;                    [ 9.4,  9.4, 8.8, 6.7], $
;                    [ 7.8,  7.5, 10.7, 6.9], $
;                    [ 8.4,  7.9, 13.1, 6.8], $
;                    [ 8.1,  6.8, 13., 6.6], $
;                    [ 8.1,  6.8, 13., 6.6]]

 sigma_readnoise = [[11.8, 22.4, 11.8, 9.4, 9.4, 7.8, 8.4, 8.1, 8.1], $
                    [12.1, 23.7, 12.1, 9.4, 9.4, 7.5, 7.9, 6.8, 6.8], $
                    [12.1, 23.7, 12.1, 9.4, 9.4, 7.5, 7.9, 6.8, 6.8], $
                    [12.1, 23.7, 12.1, 9.4, 9.4, 7.5, 7.9, 6.8, 6.8]]

;round upwards by 15% (warm mission estimate)
  sigma_readnoise = sigma_readnoise * 1.15
; cryo values
; sigma_readnoise = [[16.9, 11.8,  9.4,  7.8,  8.4], $
;                    [16.8, 12.1,  9.4,  7.5,  7.9], $
;                    [ 9.0,  9.1,  8.8, 10.7, 13.1], $
;                    [ 8.4,  7.1,  6.7,  6.9,  6.8]]

; detector read noise, electrons
 sigma_rn = sqrt( npix[f_ch_int] * sigma_readnoise[fr,f_ch_int]^2. )

;***DEBUG print, 'Detector read noise in electrons, sigma_rn = ', strtrim(string(sigma_rn),2)

; convert micro-Jy to equivalent MJy/sr
 scale = 34.98

; detector gain, electrons/DN (Table 6.6 in the SOM, v9.1)
; selected via GAIN[f_ch_int]
 GAIN = [3.3, 3.7, 3.8, 3.8]

; convert from MJy/sr to DN/s (Table 5.1 in the IRAC Data Handbook, v3.0)
; selected via FLUXCONV[f_ch_int]
 FLUXCONV = [0.1088, 0.1388, 0.5952, 0.2021]

; signal of the source in electrons
 e_s = f_fd / scale * GAIN[f_ch_int] * f_frametime / FLUXCONV[f_ch_int]

;***DEBUG print, 'source signal in electrons, e_s = ', strtrim(string(e_s),2)

; ...but ths can be simplified to...

; convert flux density to electrons, electrons/micro-Jy*sec
; selected via fd_to_e[f_ch_int]
 fd_to_e = [0.867, 0.764, 0.182, 0.537]

; signal of the source in electrons
 e_s = f_fd * f_frametime * fd_to_e[f_ch_int]

 if (f_ch_int eq 2 or f_ch_int eq 3) then f_background_sb = f_background_sb * 1.5

; scaling between surface brightness and electrons, electrons/(MJy/sr * sec)
; selected via sb_to_e[f_ch_int]
 sb_to_e = [30.331, 26.729, 6.384, 18.802]

; number of electrons contributed by the background
 e_bg = f_background_sb * f_frametime * sb_to_e[f_ch_int]

;***DEBUG print, 'background signal in electrons, e_bg = ', strtrim(string(e_bg),2)

; Poisson noise of the source + background
 sigma_s = sqrt( e_s + npix[f_ch_int] * e_bg )

;***DEBUG print, 'Poisson noise of the source + background in electrons, sigma_s = ', strtrim(string(sigma_s),2)

; Poisson noise of the subtracted background alone
 sigma_bg = sqrt( e_bg / f_nback )

;***DEBUG print, 'Poisson noise of the background alone in electrons, sigma_bg = ', strtrim(string(sigma_bg),2)

; scaling factor accounting for the noise in the annulus used to estimate the background to subtract
 ap_fac = npix[f_ch_int]

; any systematic/confusion error
 sigma_sys = 0.0

 sigma = 1.5*sqrt(sigma_rn^2. + sigma_s^2. + ap_fac*sigma_bg^2. + sigma_sys^2.)

;***DEBUG print, 'sigma = ', strtrim(string(sigma),2)

 sigma = 1.5*sqrt(sigma_rn^2. + f_frametime*(f_fd*fd_to_e[f_ch_int] + npix[f_ch_int]*f_background_sb*sb_to_e[f_ch_int] + npix[f_ch_int]*f_background_sb*sb_to_e[f_ch_int] / f_nback))

;***DEBUG print, 'sigma = ', strtrim(string(sigma),2)

 sn = sqrt(f_nexp) * f_fd * fd_to_e[f_ch_int] * f_frametime / sigma

;***DEBUG print, ' '
;***DEBUG print, 'S/N = ', strtrim(string(sn,format='(f12.2)'),2)
;***DEBUG print, ' '

 return, strtrim(string(sn,format='(f12.2)'),2)

end


pro snirac_help_event, ev

  widget_control, ev.id, get_uvalue=uval
  if (n_elements(uval) eq 0) then uval = ''
  name = strmid(tag_names(ev, /structure_name), 7, 4)
  case (name) of
   'BUTT' : begin
             if (uval eq 'DONE') then begin
              widget_control, /destroy, ev.top
              return
             endif
            endcase
  endcase
end


pro snirac_help

 common block1, VERSION, ch, frametime, nexp, fd, background_sb, rin, rout, snout, nback, sigma_rn, e_s, e_bg, sigma_s, sigma_bg, sigma

 helpbase = widget_base(title='Help Window - IRAC S/N Calculator v'+VERSION, row=2)
 widget_control, /managed, helpbase

 hb1 = widget_base(helpbase, /frame, /row)
 ht1 = widget_text(hb1, xsize=80, ysize=40, /scroll, /wrap, $
        uname='help_window', $
        value=[ 'IRAC S/N Calculator v'+VERSION, $
	        '', $
	        '', $
'This tool calculates IRAC S/N based on the memo "Estimating Signal-To-Noise Ratio of a Point Source Measurement for IRAC" (issued by the SSC on 12 Feb 2007 and updated on 18 Sep 2008; see https://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/snirac_memo.txt - the memo is also reproduced below).', $
'', $
'The following assumptions (based on those in the memo) were made during the calculation:', $
'', $
'1) Systematic and/or confusion error noise (sigma_sys) = 0.0.', $
'', $
'2) Photometry aperture has area larger than the number of noise pixels (see npix below).', $
'', $
'Note that the returned S/N value is a fairly conservative estimate (the noise has been scaled up by a factor of 1.5)', $
'', $
'', $
'-----------------------------------------------------------------------', $
'Estimating Signal-To-Noise Ratio of a Point Source Measurement for IRAC', $
'-----------------------------------------------------------------------', $
'',$ 
'When planning observations that need to measure the flux density or variations in the flux density of a point source to a certain precision, it is important to include the Poisson noise of the source in the estimation of the signal-to-noise ratio.  The online sensitivity estimator, SENS-PET, that determines the detection signal-to-noise ratio (how significant a source is compared to the background) only includes the read noise of the instrument and the Poisson noise of the background in the calculation of the uncertainty.  This memo discusses in detail how to estimate the uncertainty in a measurement of the source flux density for the benefit of observers who are interested in variations in source brightness (as in observations of extrasolar planet transits, etc).', $
'',$  
'The uncertainty in the measured flux density of a point source in a single exposure is', $
'',$  
'sigma^2 = sigma_rn^2 + sigma_s^2 + ap_fac * sigma_bg^2 + sigma_sys^2',$
'',$  
'where sigma_rn is the read noise of the detector, sigma_s is the Poisson noise of the source + background, sigma_bg is the Poisson noise of the subtracted background alone, ap_fac is a scaling factor accounting for the noise in the annulus used to estimate the background to subtract, and sigma_sys is any systematic/confusion error.  In this memo, sigma is calculated for a single exposure and in the limit where sigma_sys = 0; therefore, sigma decreases as the square-root of the number of exposures.', $
'', $  
'For an optimal source extraction, the number of pixels contributing to the source is the noise pixels (npix) for that particular array (see table 6.1 in the IRAC chapter of the Spitzer Observer"s Manual, SOM).  Then the read noise for the source is', $
'', $  
'sigma_rn^2 = npix * sigma_readnoise^2,', $
'', $      
'where sigma_readnoise is the read noise in electrons for the desired frametime (see Table 6.4 of the SOM).  If a smaller aperture is used, then npix is the number of pixels in the aperture, but the flux density of the source should be scaled by the fraction of the source flux density enclosed by the aperture.', $
'', $  
'The term sigma_s^2 is the Poisson noise of the source plus the background in the aperture used.  Then, sigma_s^2 = e_s + npix * e_bg, where e_s is the signal of the source in electrons and e_bg is the signal of the background per pixel in electrons.', $
'', $  
'Typically, the source flux density (fd) is given in micro-Jy.  To convert to electrons detected, use', $
'', $  
'e_s = fd(micro-Jy) / scale * GAIN * FRAMETIME / FLUXCONV', $
'', $  
'where scale (34.98) converts from micro-Jy to equivalent MJy/sr, so that the correct number of electrons are determined when using the FLUXCONV and GAIN values supplied; FLUXCONV converts from MJy/sr to DN/s; GAIN converts from DN to electrons; and, FRAMETIME is the frame time used.  Note that GAIN, FRAMETIME and FLUXCONV can all be found in the headers of a BCD image.  GAIN and FLUXCONV are constants for a given IRAC channel.  FLUXCONV can also be found in Table 5.1 of the IRAC Data Handbook and GAIN is tabulated in Table 6.6 of the SOM.', $
'', $  
'Simplifying the expression,', $
'', $  
'e_s = fd * FRAMETIME * fd_to_e', $
'', $  
'where fd_to_e = 0.867, 0.764, 0.182, 0.537 electrons / (uJy * s) for channels 1-4, respectively.', $
'', $  
'Likewise, the number of electrons contributed by the background is given by', $
'', $  
'e_bg = background_sb * FRAMETIME * sb_to_e', $
'', $  
'where the background_sb is an estimate of the surface brightness of the background in MJy/sr (such as the estimate given by Spot) and sb_to_e is the scaling between surface brightness and electrons.', $
'', $  
'The term sb_to_e = 30.331, 26.729, 6.384, 18.802 electrons / (MJy/sr * s) for channels 1-4, respectively.', $
'', $  
'To be conservative, assume that the background subtraction used in either aperture photometry or PSF fitting contributes noise from the background summed over the number of noise pixels for the source.  Then, ap_fac = npix and', $
'', $  
'sigma_bg^2 = e_bg  / nback = background_sb * FRAMETIME * sb_to_e / nback', $
'', $  
'where nback is the number of pixels used in a reasonably-sized background annulus.', $
'', $  
'A reasonable estimate of the noise (in electron units) in a single exposure is then', $
'', $  
'sigma^2 = sigma_rn^2 + FRAMETIME * (fd * fd_to_e + npix * background_sb * sb_to_e + npix * background_sb * sb_to_e / nback).', $
'', $  
'To be conservative, the code scales the noise upward 50% to account for systematics, etc.', $
'', $  
'The signal-to-noise ratio for one exposure is simply', $
'', $  
'fd * fd_to_e * FRAMETIME / sigma.', $
'' ])


 hb4 = widget_base(helpbase, /frame, /row, space=30)
 ht1 = widget_button(hb4, value='Dismiss', uvalue='DONE')

 widget_control, helpbase, /realize

 xmanager, 'SNIRAC_HELP', helpbase, /no_block
end


pro snirac_warm

 forward_function snirac_calc
 resolve_routine, 'snirac_calc', /is_function, /no_recompile
 resolve_routine, ['snirac_warm', 'snirac_event', 'snirac_help', 'snirac_help_event'], /no_recompile

 common block1, VERSION, ch, frametime, nexp, fd, background_sb, rin, rout, snout, nback, sigma_rn, e_s, e_bg, sigma_s, sigma_bg, sigma

;***********************************************
;*** DON'T FORGET TO UPDATE THE VERSION NUMBER
 VERSION = '1.0'   ; original code
 VERSION = '1.1'   ; fixes for bugs found by Seppo and Sean
 VERSION = '1.2'   ; removed 1.5x scaling of total noise, requested by Sean
 VERSION = '1.3'   ; warm IRAC version, scales noise by factor 1.5
;***********************************************
;*** Set default values
 fd=-999.999
 background_sb=-999.999
 ch=1
 frametime=30.
 nexp=1.
 rin = 10.
 rout = 20.

 swin = !d.window   ;Previous window

 base = widget_base(title='IRAC S/N Calculator v'+VERSION, row=3)
 widget_control, /managed, base

 b0 = widget_base(base, /frame, column=1)
 widget_control, base, set_uvalue=t1

 b1 = widget_base(base, /frame, /row)
 t1 = widget_text(b1, xsize=80, ysize=10, /scroll, uname='info_window', $
       value=[ 'IRAC S/N Calculator v'+VERSION, $
	       '', $
	       '', $
               'Default IRAC Channel: 1 (3.6 microns)', $
               'Default IRAC Frame Time: 30 s', $
               'Default Total Number of Exposures: 1', $
               'Default Inner Radius of Background Aperture: 10 pixels', $
               'Default Inner Radius of Background Aperture: 20 pixels', $
	       '' ])


 b2 = widget_base(base, /frame, /column, space=30)
 t2 = widget_base(b2, /column)
 t1 = widget_label(t2, value='IRAC Channel')
 ch_list = [ '1 (3.6 microns)', '2 (4.5 microns)']
 t1 = widget_list(t2, ysize=2, uvalue=ch_list, value=ch_list, tab_mode=1)

 t1 = widget_label(t2, value='IRAC Frame Time')
 frametime_list = [ '0.1 s', '0.4 s (Full array)', '2 s', '6 s', '12 s', '30 s', '100 s', '200 s', '400 s' ]
 t1 = widget_list(t2, ysize=9, uvalue=frametime_list, value=frametime_list, tab_mode=1)


 b3 = widget_base(base, /frame, /column, space=30)
 t2 = widget_base(b3, /row)
 t1 = widget_label(t2, value = 'Total Number of Exposures:')
 t1 = widget_text(t2, /editable, /all_events, xsize=10, ysize=1, value='1', uvalue='w_nexp')

 t2 = widget_base(b3, /row)
 t1 = widget_label(t2, value = 'Target Flux Density (micro-Jy):')
 t1 = widget_text(t2, /editable, /all_events, xsize=10, ysize=1, uvalue='w_fd')

 t2 = widget_base(b3, /row)
 t1 = widget_label(t2, value = 'Background Surface Brightness (MJy/sr):')
 t1 = widget_text(t2, /editable, /all_events, xsize=10, ysize=1, uvalue='w_background_sb')

 t2 = widget_base(b3, /row)
 t1 = widget_label(t2, value = 'Inner Radius of Background Annulus (pixels):')
 t1 = widget_text(t2, /editable, /all_events, xsize=10, ysize=1, value='10', uvalue='w_rin')

 t2 = widget_base(b3, /row)
 t1 = widget_label(t2, value = 'Outer Radius of Background Annulus (pixels):')
 t1 = widget_text(t2, /editable, /all_events, xsize=10, ysize=1, value='20', uvalue='w_rout')


 b4 = widget_base(base, /frame, /row, space=30)
 t1 = widget_button(b4, value='Calculate', uvalue='CALC')
 t1 = widget_button(b4, value='Help', uvalue='HELP')
 t1 = widget_button(b4, value='Exit', uvalue='DONE')

 widget_control, base, /realize

 xmanager, 'SNIRAC', base, /no_block
end

