Home Download Tutorial 1 Tutorial 2 Tutorial 3 Tutorial 4 Tutorial 5
;+
; NAME:      
;                 ULY_SPECT_LOGREBIN
;
; PURPOSE:  
;                 Rebin logarithmically a spectrum 
;
; USAGE:
;                 slog = uly_spect_logrebin(<spectrum> [,<vsc>])
;
; ARGUMENTS:
;   <spectrum>:   Input, data structure (spectrum).
;
;   <vsc>:        I/O, size of each pixel in km/s for the output data.
;
; KEYWORDS:
;   WAVERANGE     Input scalar or 2-elements vector. Wavelength range.
;                 If a single value s passed, it is interpreted as the
;                 start of the wavelength range.
;
;   FLUX_CONSERVE Input boolean.
;                 Set this keyword to skip the intensity correction
;                 for the change of the local pixel size.
;                 If flux is conserved, intensity is not; by default
;                 intensity (i.e. flux density) is conserved.
;
;   LINEAR        Input boolean
;                 Set this keyword to use a linear interpolation of the
;                 cumul function. By default a spline interpolation is made.
;                 A linear interpolation implies a convolution by a one-pixel
;                 width boxcar function, while a spline interpolation
;                 performs an implicite deconvolution of the pixels signature.
;                 The difference may be sentitive in case of rebinning to
;                 similar or finer pixels.
;
;   EXACT         Input boolean.
;                 Specify that the output must match exactly the WCS
;                 specified by <waverange> and <vsc>. Otherwise approximations
;                 can be made to avoid shifts by small fractions of pixels.
;
;   OVERWRITE     Input boolean.
;                 Set this keyword if input and output spect are the
;                 same variable, to save memory and prevent a memory leak.
; OUTPUT:
;                 SLOG, spectrum structure containing the log rebinned data.
;
; DESCRIPTION:
;     This routine performs rigorously a rebinning into logarithmically
;     spaced wavelength points. The algorithm consists in:
;     (i) integrate the spectrum, (ii) resample (i. e. interpolate)
;     this integral and (iii) differentiate the resampled integral. This
;     naturally conserves the flux. The resampling is done with a spline
;     or linear interpolation (see XREBIN).
;
;     As it conserves the flux, the intensity of each pixel change
;     because the size of a pixel change. As well, the shape of the
;     intensity distribution changes, for example when converting a 
;     linearly sampled spectrum into a log-sampled one. To alleviate
;     this aspect, the function corrects by default for the change of  
;     the pixel size. in is way the unit of the Intensity-axis are preserved.
;     To skip this correction, use the keyword FLUX_CONSERVE.
;
;     The input data must be a 'spectrum structure' (see ULY_SPECT_ALLOC), 
;     and the data can be sampled linearly, in log or at explicit wavelength.
;     
;     By default the routine avoids a resampling if the input spectrum
;     has the required step (<vsc>), and in that case simply extract
;     the spectral range specified with <waverange>. Therefore, in this
;     case the spectrum may be shifted by a fraction of pixel with respect
;     to the requirement. To force a resampling to the exact grid,
;     specify the keyword /EXACT. (note that in the case the constraint on the
;     wavelength of the last pixel cannot be exact).
;
;     <vsc> is the velocity scale, ie. size in km/s of each pixel in the
;     log-rebinned spectrum.
;     If <vsc> is initialized in input, this value will be used to rebin
;     the input spectrum, otherwise the velocity scale is computed
;     in order to keep the same number of pixels in output as in input.
;     The computed value is returned in <vsc>.
;
;     WAVERANGE. If a scalar or 1-element array is passed, it is the
;     wavelength of the center of the output spectrum's 1st pixel in Angstrom. 
;     If a 2-elements array array is passed, the wavelength range is [start, end]
;     If the specified start is greater than the last wavelength of the 
;     input spectrum, the routine fails with an error. 
;     If the specified start is smaller than the begining of the spectrum,
;     the effective start is shifted by a round number of pixels.
;     If <waverange> is not passed, the spectrum is rebinned with a WCS
;     so that the beginning edge of the first pixel coincide with each
;     other both in input and output spectra.
;
;     The error spectrum and pixel mask, if present in (spectrum) are
;     also transformed in accordance with the signal. 
;
;     If the data and error arrays contained in (spectrum) have two dimensions,
;     all the lines are rebinned similarly. 
;
; SEE ALSO:       ULY_SPECT_LINREBIN, ULY_SPECT_READ
;
; REQUIRED ROUTINES:  
;                 XREBIN.PRO
;
; HISTORY:        Mina Koleva (2007/03/01)
;     
;-
; CATEGORY:       ULY
;------------------------------------------------------------------------------
function uly_spect_logrebin, SignalIn, vsc, WAVERANGE=waverange, $
                             FLUX_CONSERVE=flux_conserve,        $
                             LINEAR=linear, EXACT=exact, OVERWRITE=overwrite

compile_opt idl2
on_error, 2

if size(SignalIn, /TYPE) ne 8 then message, 'Input must be a spectrum structure'
if n_elements(*SignalIn.data) eq 0 then message, 'Input spectrum undefined'

npix = (size(*SignalIn.data,/DIM))

c = 299792.458d                 ; Speed of light in km/s

if keyword_set(linear) then splinf = 0 else splinf = 1

if SignalIn.sampling eq 1 then begin
    cpout = 0
;   Note that comparison between SignalIn.step and vsc/c tolerates
;   rounding errors (0.001 px over the whole range)
    if (n_elements(vsc) eq 1) then begin
        if (abs(1d - double(vsc)/c/SignalIn.step)*npix[0]) lt 0.001 then cpout = 1
    endif else if n_elements(vsc) eq 0 then cpout = 1
    if keyword_set(exact) and cpout eq 1 and n_elements(waverange) gt 0 then begin
        nshift = (SignalIn.start-alog(waverange[0]))/SignalIn.step mod 1d
        if abs(nshift) gt 0.001 then cpout = 0

        if cpout eq 1 and n_elements(waverange) eq 2 then begin
           nshift = (SignalIn.start + (npix[0]-1)*SignalIn.step $
                    - alog(waverange[0]))/SignalIn.step mod 1d
           if abs(nshift) gt 0.001 then cpout = 0
        endif
    endif
    if cpout eq 1 then begin    ; just have to copy input to output...
        vsc = c * SignalIn.step ; for when vsc arg is not set
        return, uly_spect_extract(SignalIn, WAVERANGE=waverange, $
                                  OVERWRITE=overwrite)
    endif
endif

;compute the borders of input bins
if SignalIn.sampling eq 0 then begin
    indxarr = lindgen(npix[0])
    borders = SignalIn.start + [-0.5d,$
                                (indxarr+0.5d)] * SignalIn.step
    bordersLog = alog(borders)

endif else if SignalIn.sampling eq 1 then begin
    indxarr = lindgen(npix[0])
    bordersLog = SignalIn.start + [-0.5d,$
                                   (indxarr+0.5d)] * SignalIn.step
    borders = exp(bordersLog)
endif else if SignalIn.sampling eq 2 then begin
    borders = [(*SignalIn.wavelen + shift(*SignalIn.wavelen, 1)) / 2d, 0d]
    borders[0] = 2.*(*SignalIn.wavelen)[0] - borders[1]
    borders[npix[0]] = 2.*(*SignalIn.wavelen)[npix[0]-1] - borders[npix[0]-1]
    bordersLog = alog(borders)
endif else $
  message, 'Sampling mode of input is invalid:'+SignalIn.sampling

; Determine the velocity scale, vsc, preserving the total number of pixels
if n_elements(vsc) eq 0 then begin
    if SignalIn.sampling eq 1 then vsc = SignalIn.step * c $
    else if SignalIn.sampling eq 2 then begin
;       We assume that the wavelength are ordered, and we do not check it
        if n_elements(waverange) gt 0 then begin
            nw = value_locate(*SignalIn.wavelen, waverange)
            if nw[0] eq -1 then nw[0] = 0
            if n_elements(waverange) eq 1 then nw = [nw[0], npix[0]]
            if nw[1] eq -1 then nw[1] = 0 ; this is bad
        endif else nw = [0d, npix[0]-1]
        wr = (*SignalIn.wavelen)[nw]
        vsc =  alog(wr[1]/wr[0]) / npix[0] * c 
    endif else begin
        if n_elements(waverange) gt 0 then begin
            nw = (waverange - SignalIn.start)/SignalIn.step -0.5d
            if n_elements(waverange) gt 1 then $
              nw[1] = min([nw[1]+1, npix[0]-0.5]) $
            else nw = [nw[0], npix[0]-0.5]
        endif else nw = [-0.5d, npix[0]-0.5]

        wr = alog(SignalIn.start + nw * SignalIn.step)
        vsc =  (wr[1]-wr[0]) / (nw[1] - nw[0]) * c 
    endelse
endif

logScale = vsc/c   ; Scaling in the output spectrum

; Determine the start wavelength, logStart, and number of pix, nout, in output
logRange = SignalIn.start + [-0.5d,(npix[0]-0.5)] * SignalIn.step
if SignalIn.sampling eq 0 then logRange = alog(logRange) else $
if SignalIn.sampling eq 2 then logRange = alog([(*SignalIn.wavelen)[0],(*SignalIn.wavelen)[npix-1]])
logRange += [0.5d,-0.5d]*logScale
if n_elements(waverange) gt 0 then begin
;  the 1D-7 is a tolerance for rounding errors
   nshift = ceil(max([0d, (logRange[0] - alog(waverange[0])) / logScale - 1d-7]))
   logRange[0] = alog(waverange[0]) + logScale * nshift  ; center of 1st out pix

   if n_elements(waverange) eq 2 then $
      logRange[1] = min([alog(waverange[1]), logRange[1]]) 

   if logRange[1] lt logRange[0] then begin
       message, /INFO, 'waverange is not valid: '+ $
         strjoin(strtrim(waverange,2),',')
       return, 0
   endif
endif 
nout = round((logRange[1]-logRange[0]) / logScale + 1)
logStart = logRange[0]

; determine the DOF factor
; Note that this is an approximation. Even if the number of pixels is conserved
; the rebinning introduces a correlation. We shall improve this...
if SignalIn.sampling lt 2 then begin
   if SignalIn.sampling eq 0 then logRange = exp(logRange)
   nin = round((logRange[1]-logRange[0]) / SignalIn.step + 1)
endif else nin = n_elements(*SignalIn.wavelen)
dof_factor = nout/double(nin)

; we may think of a correction of dof_factor in case of splinf (obvious reason) 
; (the following one is OK only for large factors, it is too strong for small factors:)
;if splinf eq 1 and dof_factor gt 1 then dof_factor *= 0.86

if logStart-logScale/2d gt bordersLog[npix[0]] then begin
    message, 'start value is not in the valid range', /INFO
    return, 0
endif

; define the new borders of the bins
NewBorders = exp(logStart + (dindgen(nout+1)-0.5d) * logScale)

dim = size(*SignalIn.data, /DIM)
n_data = n_elements(*SignalIn.data)
n_err = n_elements(*SignalIn.err)
n_dim = size(*SignalIn.data, /N_DIM)

statmath = check_math(MASK=32, /NOCLEAR)  ; do we have an underflow error on the stack?

; Determine the conversion factor/vector accounting for pixel size change
case SignalIn.sampling of
    0: begin
        flat = exp(logStart + dindgen(nout)*logScale) * logScale/SignalIn.step
        if n_dim gt 1 then flat = rebin(flat, [nout, dim[1:*]])
    end
    1: begin        
        flat = logScale/SignalIn.step
    end
    else: begin
       flat = fltarr(dim[0], /NOZERO) ; faster to create an array and
       replicate_inplace, flat, 1B    ; populate with 1s 
       flat = xrebin(borders, flat, NewBorders, SPLINF=splinf) 
       if n_dim gt 1 then flat = rebin(flat, [nout, dim[1:*]])
    end
endcase

if keyword_set(overwrite) then SignalOut = SignalIn $
else SignalOut = uly_spect_alloc()

SignalIn_type = size(*SignalIn.data, /TYPE)

if keyword_set(flux_conserve) then $
  *SignalOut.data = xrebin(borders,*SignalIn.data,NewBorders,SPLINF=splinf) $
else $
  *SignalOut.data = xrebin(borders,*SignalIn.data,NewBorders,SPLINF=splinf) / flat

if SignalIn_type lt 5 then *SignalOut.data = float(temporary(*SignalOut.data))

if n_err eq n_data then begin
; if err is very small, then the ^2 is making it close to 0 and log_rebinning
; may produce negative values. Even small values can have dramatic 
; consequences, as they give an exagerated weight to the affected pixels.
; To prevent subsequent errors we set these small values to
; the min error in the input spectrum.
; It may possibly be better to simply use a linear interpolation
; that would not induce such artifacts...
    err = xrebin(borders,*SignalIn.err^2,NewBorders,SPLINF=splinf) 
    if n_elements(*SignalIn.goodpix) ne 0 then $
       minerr = min((*SignalIn.err)[*SignalIn.goodpix])^2 *  n_elements(*SignalIn.err) / n_elements(err)$
    else $
       minerr = min(*SignalIn.err)^2 *  n_elements(*SignalIn.err) / n_elements(err)
    negative = where(err le minerr, cnt)  
    if cnt ne 0 then err[negative] = minerr  
    if keyword_set(flux_conserve) then *SignalOut.err = sqrt(err) $
    else *SignalOut.err = sqrt(err) / flat 

; When the input pixels are uncorrelated (consecutive pixels independent) and
; we bin to coarser bins (the output will also be independent) we rebin the 
; variance. 
; When we rebin to finer bins, we have to rebin the error (so have to
; divide by sqrt(dof_factor). When the input spectrum is dependent, and we
; go to a coraser grid ... it depends also of the final dof_factor.
    if dof_factor gt 1 then begin  ; the resampling make dependent pixels
       *SignalOut.err /= sqrt(dof_factor)
    endif else if SignalIn.dof_factor gt 1 then begin ; the input is dependent
       d = dof_factor
       if d*SignalIn.dof_factor lt 1 then d=1./SignalIn.dof_factor
       *SignalOut.err /= sqrt(d)
    endif

endif else if n_elements(*SignalIn.err) gt 0 then $
  message, 'Error spectrum and data do not have the same number of pixels.'


if n_elements(*SignalIn.goodpix) ne 0 then begin
; need to (1) make an array for the pixel list, (2) rebin it (3) remake a list
   maskI = bytarr(n_data)
   maskI[*SignalIn.goodpix] = 1B
   maskI = reform(maskI, dim, /OVER)
   maskO = xrebin(borders, maskI, NewBorders, SPLINF=splinf) / flat
   goodpix = where(abs(maskO-1) lt 0.1, cnt)
   if cnt gt 0 then *SignalOut.goodpix = goodpix $
   else message, 'No good pixel left in the output spectrum'
endif

SignalOut.title = SignalIn.title
if n_elements(*SignalIn.hdr) ne 0 then *SignalOut.hdr = *SignalIn.hdr
SignalOut.start = logStart
SignalOut.step =  logScale
SignalOut.sampling = 1
SignalOut.dof_factor = max([1d, SignalIn.dof_factor * dof_factor])

if statmath eq 0 then statmath = check_math(MASK=32)  ; ignore any underflow happening in this routine

sxaddpar, *SignalOut.hdr, 'HISTORY', 'uly_spect_logrebin, vsc='+strtrim(vsc,2)

return, SignalOut

; test the difference between the orig spectrum and the log and
; lin-rebinned one
; The experiment below showed that SPLINF is a little better (and 3-4
; times faster) than SPLINE
;bb =xrebin(borders,*SignalIn.data,NewBorders,/SPLINF)
;ba = xrebin(NewBorders,bb,borders,/SPLINF)
;
;aa = xrebin(NewBorders,*SignalOut.data,borders,/SPLINE)
;
;;print, stddev(*signalIn.data/aa), stddev(*signalIn.data/ba), mean(*signalIn.data/aa), mean(*signalIn.data,/D)/mean(aa,/D)
;print, stddev(*signalIn.data/aa), stddev(*signalIn.data/ba), stddev(*signalIn.data/aa)-stddev(*signalIn.data/ba)

end
Part of ULySS package