
;+
; NAME
;
;     EIS_FIT_TEMPLATE
;
; PROJECT
;
;     Hinode/EIS
;
; EXPLANATION
;
;     A widget-based routine for creating a fitting template for input
;     to EIS_AUTO_FIT. A tutorial is available on the EIS wiki for
;     using this routine.
;
; INPUTS
;
;     WINDATA    Window structure produced by EIS_GETWINDATA.
;
; OPTIONAL INPUTS
;
;     OFFSET     The wavelength offsets due to spectrum drift and slit
;                tilt are, by default, taken from
;                WINDATA.WAVE_CORR. Specifying OFFSET= allows users
;                to specify their own wavelength offset array. The
;                array must have dimensions (WINDATA.NX,WINDATA.NY).
;
;     WVL        Sets the displayed wavelength range to
;                [WVL-2,WVL+2]. It is intended that this be used for
;                large data windows, such as full CCD spectra.
;
;     OUTFILE    The name of a text file to which to send the fit
;                template. E.g., outfile='fe12_template.txt'.
;
;     IEXP       This is only active if the data-set has nexp_prp>1
;                (i.e., more than one exposure per raster
;                position). It is an index that selects which exposure
;                to use. By default the routine selects the exposure
;                with the longest exposure time.
;
; KEYWORD PARAMETERS:
;     PSF_CORR:  This keyword only applies to SPICE data. If set, then
;                the initial width and the allowed width range are modified
;                to be compatible with SPICE data that have had the PSF
;                correction applied (i.e., the lines are narrower than
;                the original data).
;
; OUTPUTS
;
;     TEMPLATE   An IDL structure containing the fit template. A full
;                list of tags is given in the header of the routine
;                eis_read_template.pro.
;
; CALLS
;
;     EIS_WRITE_TEMPLATE, EIS_READ_TEMPLATE
;
; INTERNAL ROUTINES
;
;     TEMPLATE_FONT, TEMPLATE_DRAWBOX, TEMPLATE_PLOT_PROFILE,
;     TEMPLATE_PLOT_IMAGE, TEMPLATE_BASE_EVENT, EIS_TEMPLATE_WIDGET
; 
; EXAMPLES
;
;      IDL> eis_fit_template, windata, template
;
; HISTORY
;
;     Version 1, 2-Feb-2010, Peter Young
;     Version 2, 9-Apr-2010, Peter Young
;       no longer flags missing data for intensity maps; added
;       OUTFILE= keyword; changed widget title
;     Version 3, 15-Apr-2010, Peter Young
;       Now checks to make sure OFFSET does not contain missing
;       values.
;     Version 4, 23-Apr-2010, Peter Young
;       Removed warning message about missing data in OFFSET as
;       sometimes there's a legitimate reason for missing data
;       (i.e., missing exposures); de-sensitized Exit button when
;       choosing lines.
;     Version 5, 12-Apr-2011, Peter Young
;       Routine now takes the 'offset' array from WINDATA.WAVE_CORR,
;       unless the keyword OFFSET= is given.
;     Version 6, 24-Jun-2011, Peter Young
;       Now uses sigrange to better scale the image.
;     Version 7, 8-Apr-2012, Peter Young
;       Now recommends the user to call EIS_TRIM_WINDATA when the
;       wavelength windows are large (e.g., full CCD spectra).
;     Version 8, 4-Apr-2013, Peter Young
;       I'm attempting to make the auto_fit suite of routines
;       work with CDS data, so I've added some code to set up
;       different template parameters for CDS. Also changed how image
;       and spectrum are plotted to better handle missing data. 
;     Version 9, 7-Nov-2013, Peter Young
;       Now checks if windata belongs to IRIS and fit parameters are
;       modified. 
;     Version 10, 30-Apr-2014, Peter Young
;       Updated default IRIS parameters based on experience with data.
;     Version 11, 23-Sep-2014, Peter Young
;       Modified so that missing value is no longer hard-coded (it is
;       now taken from windata).
;     Version 12, 2-Mar-2015, Peter Young
;       Fixed bug for nexp_prp>1 data; also now applied correct
;       X-scaling to image.
;     Version 13, 9-Jun-2015, Peter Young
;       Fixed Y-scaling problem for IRIS data.
;     Version 14, 2-Mar-2018, Peter Young
;       Changed a comment that was incorrect; no change to code.
;     Version 15, 09-Jul-2020, Peter Young
;       Checks to make sure windata is a structure.
;     Version 16, 24-Jan-2022, Peter Young
;       Now handles SPICE data.
;     Version 17, 17-Jul-2023, Peter Young
;       Updated default parameters for SPICE.
;     Version 18, 29-May-2025, Peter Young
;       Added /psf_corr for SPICE data.
;     Version 19, 02-Jun-2025, Peter Young
;       /psf_corr wasn't properly implemented, so I've fixed this.
;-

;------------------
PRO template_font, font, big=big, fixed=fixed
;+
;  Defines the fonts to be used in the widgets. Allows for Windows and Unix 
;  operating systems.
;-
CASE !version.os_family OF

  'unix': BEGIN
    IF keyword_set(fixed) THEN fstr='-*-courier-' ELSE $
         fstr='-adobe-helvetica-'
    IF keyword_set(big) THEN str='18' ELSE str='12'
    font=fstr+'bold-r-*-*-'+str+'-*'
  END

  ELSE: BEGIN
    IF keyword_set(fixed) THEN fstr='Courier' ELSE $
         fstr='Arial'
    IF keyword_set(big) THEN str='20' ELSE str='16'
    font=fstr+'*bold*'+str
  END

ENDCASE

END


;----------------
PRO template_drawbox, event, state, wid
;
; This is the rubber-band box for the image (left panel).
;
; 2-Mar-2015: added 'xsc' to give scaling factor for X-dimension.
;

xsc=state.windata.scale[0]
ysc=state.windata.scale[1]

CASE 1 OF
  event.press EQ 1 AND state.box.flag EQ 0: BEGIN
    state.box.flag=1
    state.box.x0=event.x
    state.box.y0=event.y
    state.box.wid=wid
    wset,wid
    template_plot_image, state
    Window, /Pixmap, /Free, XSize=state.box.xsiz, YSize=state.box.ysiz
    state.box.pixid = !D.Window
    Device, Copy=[0, 0, state.box.xsiz, state.box.ysiz, 0, 0, wid]
    wset,wid
    widget_control,state.template_base,set_uvalue=state
  END

  event.release EQ 1 AND state.box.flag EQ 1: BEGIN
    WDelete, state.box.pixid
    state.box.flag=0
    state.box.x1=event.x
    state.box.y1=event.y
    widget_control,state.template_base,set_uvalue=state
    result=convert_coord([state.box.x0,state.box.x1], $
                         [state.box.y0,state.box.y1],/device,/to_data)

    xpix=round([result[0,0]/xsc>0,result[0,1]/xsc>0])
    ypix=round([result[1,0]/ysc>0,result[1,1]/ysc>0])

    nx=state.windata.nx
    ny=state.windata.ny

    xpix=xpix < (nx-1)
    ypix=ypix < (ny-1)

    xpix=xpix[sort(xpix)]
    ypix=ypix[sort(ypix)]

    state.wid_data.xpix=xpix
    state.wid_data.ypix=ypix

    widget_control,state.template_base,set_uvalue=state

    template_plot_profile, state

  END

  state.box.flag EQ 1: BEGIN
    Device, Copy=[0, 0, state.box.xsiz, state.box.ysiz, 0, 0, state.box.pixid]
    PlotS, [state.box.x0, state.box.x0, event.x, event.x, state.box.x0], $
         [state.box.y0, event.y, event.y, state.box.y0, state.box.y0], $
         /Device
  END
  
  ELSE: 
  
ENDCASE

END


;---------------------
PRO template_plot_profile, state
;
; Plots the line profile for the spatial pixel selected by the user,
; together with the selected pixels (stored in pix_all).
;
windata=state.windata
wid_data=state.wid_data
offset=state.offset
xpix=state.wid_data.xpix
ypix=state.wid_data.ypix
wrange=state.wid_data.wrange

wset,wid_data.profile_id


xpix=wid_data.xpix
ypix=wid_data.ypix
nx=xpix[1]-xpix[0]+1
ny=ypix[1]-ypix[0]+1
xcen=round(mean(xpix))
ycen=round(mean(ypix))
IF state.windata.hdr.nexp_prp GT 1 THEN BEGIN 
  iexp=state.iexp
  int=windata.int[*,xpix,ypix,iexp]
ENDIF ELSE BEGIN
  int=windata.int[*,xpix,ypix]
ENDELSE 
int=windata.int
int=int[*,xpix[0]:xpix[1],ypix[0]:ypix[1]]

CASE 1 OF
  nx EQ 1 AND ny EQ 1: spec=int[*,0,0]
  nx EQ 1 AND ny NE 1: spec=average(int,2,missing=windata.missing)
  nx NE 1 AND ny EQ 1: spec=average(int,2,missing=windata.missing)
  nx NE 1 AND ny NE 1: spec=average(int,[2,3],missing=windata.missing)
ENDCASE 

;
; I do a check to make sure the offset is not set to missing as this
; can make a mess of the template.
;
IF n_elements(offset) GT 1 THEN BEGIN
  off_pix=offset[xcen,ycen]
  IF off_pix EQ windata.missing THEN off_pix=0
  wvl=windata.wvl-off_pix
ENDIF ELSE BEGIN
  wvl=state.windata.wvl
ENDELSE 

IF total(wrange) EQ 0. THEN wrange=[min(wvl),max(wvl)]

title='X '+trim(xpix[0])+':'+trim(xpix[1])+', Y '+trim(ypix[0])+':'+trim(ypix[1])

plot,wvl,spec>0,psym=10,/xsty,xrange=wrange,title=title

template=state.template
ng=template.ngauss
IF ng GT 0 THEN BEGIN
  wvl_corr=0.
  FOR i=0,ng-1 DO BEGIN
    oplot,[1,1]*template.lines[i].centroid+wvl_corr,[0,template.lines[i].peak],th=2
  ENDFOR
ENDIF 

state.wid_data.xmin=min(wvl)
state.wid_data.xmax=max(wvl)
state.wid_data.ymin=!y.crange[0]
state.wid_data.ymax=!y.crange[1]

widget_control,state.template_base,set_uval=state

END


;---------------------
PRO template_plot_image, state
;
; Plots the image to be displayed on the left-side of the widget. The
; image is simply derived by averaging in the wavelength direction.
;
wset,state.wid_data.image_id
windata=state.windata
nexp_prp=state.windata.hdr.nexp_prp
iexp=state.iexp
scale=state.windata.scale
nx=state.windata.nx

;
; Need to check for nexp_prp
;
IF nexp_prp GT 1 THEN BEGIN
  int=windata.int[*,*,*,iexp]
  err=windata.err[*,*,*,iexp]
ENDIF ELSE BEGIN 
  int=windata.int
  err=windata.err
ENDELSE 
;i=where(err EQ  windata.missing,ni)
;IF ni NE 0 THEN int[i]=windata.missing
image=average(int,1,missing=windata.missing)
imchck=sigrange(image,missing=windata.missing,range=range)
plot_image,image,min=range[0],max=range[1],scale=scale,xsty=5
xx=findgen(nx)
xra=[min(xx),max(xx)]
axis,xaxis=0,xra=xra,xsty=1
axis,xaxis=1,xra=xra,xsty=1,charsiz=0.0001

END


;------------------
PRO template_base_event, event
;
; Handles widget events.
;
WIDGET_CONTROL,Event.top, get_uvalue=state

CASE event.id OF

  state.image_plot: BEGIN
    template_drawbox, event, state, state.wid_data.image_id
    refpix=[round(mean(state.wid_data.xpix)), $
            round(mean(state.wid_data.ypix))+state.windata.hdr.yws]
    state.template.refpix=refpix
    widget_control,state.template_base,set_uval=state
    widget_control,state.profile_base,sens=1
  END

  state.profile_plot: BEGIN
    wid_data=state.wid_data
    template=state.template
    x=event.x
    y=event.y
    result=convert_coord(x,y,/device,/to_data)
    x=result[0]
    y=result[1]
   ;
    IF event.release EQ 1 AND x GE wid_data.xmin AND x LE wid_data.xmax AND y GE wid_data.ymin AND y LE wid_data.ymax THEN BEGIN
      k=template.ngauss
     ;
      CASE trim(state.windata.hdr.instrume) OF
        'CDS': BEGIN
          IF mean(state.windata.wvl) LE 400. THEN BEGIN
            width=0.30/2.35
            dcen=0.50
            wid_lim=[0.20,0.6]/2.35
          ENDIF ELSE BEGIN 
            width=0.50/2.35
            dcen=0.50
            wid_lim=[0.4,0.8]/2.35
          ENDELSE 
        END
       ;
        'EIS': BEGIN 
          width=0.03
          dcen=0.15
          wid_lim=[0.02,0.09]
        END 
       ;
        'SPICE': BEGIN
          IF state.psf_corr THEN BEGIN
            width=0.22
            wid_lim=[0.10,1.00]
          ENDIF ELSE BEGIN 
            width=0.35
            wid_lim=[0.25,1.00]
          ENDELSE 
          dcen=0.90
        END 
       ;
       ; This is IRIS. I've seen Si IV profiles over 1A wide, so need
       ; to give a wide range of widths.
        'SPEC': BEGIN
          width=0.03
          dcen=0.50
          wid_lim=[0.02,0.55]
        END 
      ENDCASE 
      template.lines[k].centroid=x
      template.lines[k].peak=y
      template.lines[k].width=width
      template.lines[k].peak_lim=[0.0,state.windata.missing]
      template.lines[k].cen_lim=[x-dcen,x+dcen]
      template.lines[k].wid_lim=wid_lim
     ;
      template.ngauss=k+1
     ;
      wset,wid_data.profile_id
      oplot,[1,1]*x,[0,y],th=2
      state.wid_data=wid_data
      state.template=template
      widget_control,state.template_base,set_uval=state
    ENDIF 

  END

  state.profile_butt1: BEGIN
    widget_control,state.profile_butt1,sens=0
    widget_control,state.exit,sens=0
    widget_control,state.profile_plot,sens=1
    widget_control,state.profile_butt2,sens=1
    widget_control,state.image_plot,sens=0
    state.template.ngauss=0
    widget_control,state.template_base,set_uval=state
    template_plot_profile,state
  END

  state.profile_butt2: BEGIN
    widget_control,state.profile_butt1,sens=1
    widget_control,state.exit,sens=1
    widget_control,state.profile_plot,sens=1
    widget_control,state.profile_butt2,sens=0
    widget_control,state.image_plot,sens=1
    wid_data=state.wid_data
    template=state.template
    template.null_value=state.windata.missing
    line_str=template.lines[0:template.ngauss-1]
    eis_write_template,wid_data.outfile,template
  END 

  state.exit: widget_control, event.top, /destroy

ENDCASE 

END


;-----------------
PRO eis_template_widget, windata, offset, template, wvl, group=group, $
                         outfile=outfile, iexp=iexp, psf_corr=psf_corr


IF n_elements(wvl) NE 0 THEN wrange=[wvl-2,wvl+2] ELSE wrange=[0,0]

;
; 'lines' is a structure that will contain initial parameters for the
; user-selected lines. A maximum of 20 is allowed.
;
str={wvl: 0., peak: 0.}
lines=replicate(str,20)

IF n_elements(outfile) EQ 0 THEN outfile='template.txt'

;
; 'active' indicates whether the spectrum plot window will respond to
; mouse events or not. Initially the window will be inactive since the
; user needs to first select a spatial region.
;
wid_data={image_id: 0, $
          profile_id: 0, $
          sensitive: 0, $
          xpix: [0,0], $
          ypix: [0,0], $
          lines_bg: 0, $
          lines: lines, $
          nlines: 0, $
          xmin: 0., $
          xmax: 0., $
          ymin: 0., $
          ymax: 0., $
          outfile: outfile, $
          wrange: wrange}

xsiz=600
ysiz=500

template_font,font
template_font,bigfont,/big
template_font,fixfont,/fixed

template_base=widget_base(/col,map=1,title='EIS_FIT_TEMPLATE')

exit=widget_button(template_base,value='EXIT',font=bigfont)

plot_base=widget_base(template_base,/row,map=1)

image_base=widget_base(plot_base,/row,map=1)
image_plot=widget_draw(image_base,xsiz=xsiz,ysiz=ysiz,/sens, $
                    /button_events,/motion_events)

profile_base=widget_base(plot_base,/col,map=1,sens=0)
profile_plot=widget_draw(profile_base,xsiz=xsiz,ysiz=ysiz,sens=0, $
                        /button_events)
butt_base=widget_base(profile_base,/row,map=1)
profile_butt1=widget_button(butt_base,val='Choose lines',font=font)
profile_butt2=widget_button(butt_base,val='End selection',sens=0,font=font)

;
; Structure for defining the rubber-band box used for selecting sub-images
;
box={flag: 0, wid: 0, x0: 0, y0: 0, x1:0, y1:0, $
     xsiz:xsiz, ysiz: ysiz, pixid:0}


state={exit: exit, $
       image_plot: image_plot, $
       profile_plot: profile_plot, $
       wid_data: wid_data, $
       offset: offset, $
       windata: windata, $
       iexp: iexp, $
       template: template, $
       template_base: template_base, $
       box: box, $
       image_base: image_base, $
       profile_base: profile_base, $
       profile_butt1: profile_butt1, $
       profile_butt2: profile_butt2, $
       psf_corr: keyword_set(psf_corr)}

WIDGET_CONTROL, template_base, /REALIZE, set_uvalue=state

widget_control,image_plot,get_value=image_id
widget_control,profile_plot,get_value=profile_id
wid_data.image_id=image_id
wid_data.profile_id=profile_id


state.wid_data=wid_data
widget_control,template_base,set_uvalue=state

template_plot_image, state


XMANAGER, 'template_base', template_base, group=group

END


;-----------------
PRO eis_fit_template, windata, template, offset=offset, wvl=wvl, outfile=outfile, $
                      iexp=iexp, psf_corr=psf_corr


IF n_params() LT 2 THEN BEGIN
  print,'Use: IDL> eis_fit_template, windata, template [, offset=, wvl=, outfile= ]'
  return
ENDIF

IF n_tags(windata) EQ 0 THEN BEGIN
  print,'% EIS_FIT_TEMPLATE: the input WINDATA is not a structure. Returning...'
  return
ENDIF 

chck=max(windata.wvl)-min(windata.wvl)
IF chck GT 4.0 AND n_elements(wvl) EQ 0 THEN BEGIN
  print,''
  print,'% EIS_FIT_TEMPLATE: for large wavelength windows try using the routine EIS_TRIM_WINDATA() to reduce'
  print,'                    the size the of the window. For example:'
  print,''
  print,'                    IDL> wd_new=eis_trim(windata,[194,196])'
  print,''
ENDIF


;
; This checks for nexp_prp>1
;
nexp_prp=windata.hdr.nexp_prp
IF nexp_prp GT 1 THEN BEGIN
  exp_times=reform(windata.exposure_time[0,*])
  nexp=n_elements(exp_times)
  IF n_elements(iexp) EQ 0 THEN BEGIN
    getmax=max(exp_times,iexp)
    print,'% EIS_FIT_TEMPLATE: this data-set has nexp_prp>1. The exposure times are:'
    FOR i=0,nexp-1 DO BEGIN
      print,format='(i10,f8.1," s")',i,exp_times[i]
    ENDFOR 
    print,' The largest exposure time will be used. Use iexp= to select a different exposure index.'
  ENDIF 
ENDIF ELSE BEGIN
  iexp=-1
ENDELSE 


IF NOT tag_exist(windata,'wave_corr') AND n_elements(offset) EQ 0 THEN BEGIN
  print,'% EIS_FIT_TEMPLATE: Your WINDATA structure is out-of-date and does not contain the WAVE_CORR tag.'
  print,'Please re-generate the structure with EIS_GETWINDATA before calling EIS_FIT_TEMPLATE.'
  print,'Returning...'
  return
ENDIF 


IF n_elements(offset) EQ 0 THEN BEGIN
  offset=windata.wave_corr
ENDIF ELSE BEGIN
  siz=size(offset)
  IF siz[1] NE windata.nx OR siz[2] NE windata.ny THEN BEGIN
    print,'% EIS_FIT_TEMPLATE: the input OFFSET= has been specified, but the dimensions do not match those of WINDATA.'
    print,'Please check your data.  Returning...'
    return
  ENDIF 
ENDELSE 


template=eis_read_template(/empty)

wvlmin=min(windata.wvl-max(offset))
wvlmax=max(windata.wvl-min(offset))

IF n_elements(outfile) NE 0 THEN swtch=1 ELSE swtch=0

eis_template_widget, windata, offset, template, wvl, outfile=outfile, $
                     iexp=iexp, psf_corr=psf_corr

;
; I don't know how to return a structure back from the widget
; routines, so I write the template structure to a text file, read it,
; then delete the file.
;
template=eis_read_template(outfile)
IF swtch EQ 0 THEN file_delete,outfile,/allow_nonexistent

END
