===========
PSF Formats
===========
Base PSF
--------
*NOTE*: we are in the process of switching the format of how x and y vs.
wavelength are stored. This will create incompatibilities between old and
new PSFs.
HDUs 0 and 1 contain the PSF centroid x and y location vs. wavelength
stored as Legendre polynomial coefficients::
HDU 0 : XCOEFF[nspec, ncoeff]
HDU 1 : YCOEFF[nspec, ncoeff]
The header contains keywords WAVEMIN, WAVEMAX to define the wavelength
range in Angstroms to map to domain [-1,1] for evaluating the Legendre
polynomials. *e.g.* the x position for spectrum i at wavelength::
w = 2.0*(w-WAVEMIN)/(WAVEMAX-WAVEMIN) - 1.0 #- Map to [-1,1]
x[i] = Sum_j XCOEFF[i,j] L_j(w) #- evaluate Legendre series
Or in Python::
import fitsio
from numpy.polynomial.legendre import legval
h = fitsio.read_header('psf.fits', 'XCOEFF')
xcoeff = fitsio.read('psf.fits', 'XCOEFF')
w = 2.0 * (wavelength-h['WAVEMIN']) / (h['WAVEMAX'] - h['WAVEMIN']) - 1.0
x = legval(w, xcoeff[i])
Additional HDUs contain data specific to various parameterizations of the
PSF as listed below. Any additional extensions should be read by EXTNAME,
not by number -- the order and number of other extensions is arbitrary.
HDU 0 keywords:
- NPIX\_X, NPIX\_Y : CCD dimensions in pixels
(x,y) coordinates are *centered* on each pixel and start counting at 0.
i.e. the lower left corner of the CCD is (-0.5, -0.5) and the center of
the lower left pixel is (0,0).
Optional HDU EXTNAME=THROUGHPUT in the format as described in
throughput.md . *i.e.* the throughput may be kept in a separate FITS file,
or bundled with the instrument PSF for convenience.
If throughput isn't available, the PSF can still be used to project
photons onto a CCD, but not flux in erg/s/cm^2/A .
Old format for x and y
~~~~~~~~~~~~~~~~~~~~~~
The old base PSF format parameterized x and y vs. wavelength as::
HDU 0 : x[nspec, nwave] EXTNAME="X"
HDU 1 : y[nspec, nwave] EXTNAME="Y"
HDU 2 : wavelength[nspec, nwave] EXTNAME="WAVELENGTH", or
loglam[nspec, nwave] EXTNAME="LOGLAM"
HDU 3+ : specific to each subformat
We may revive this format.
Spot Grid PSF
-------------
PSFTYPE = "SPOTGRID"
This PSF type provides an x,y grid of spots to be interpolated.
The first interpolation dimension must monotonically increase with
spectrum number, *e.g.* the position of a fiber along a slit head.
The second interpolation dimension is wavelength.
HDU 0-2 : Same as Base PSF: X, Y, wavelength or loglam of traces::
HDU SPOTS : spot[i, j, iy, ix] #- 2D PSF spots
NAXIS1 = i = number of spot samples in the spectrum number dimension
NAXIS2 = j = number of spot samples in the wavelength direction
NAXIS3 = iy = size of spot in the CCD y dimension
NAXIS4 = ix = size of spot in the CCD x direction
HDU SPOTX : spotx[NAXIS1, NAXIS2] #- CCD X pixel loc of spot centroid
HDU SPOTY : spoty[NAXIS1, NAXIS2] #- CCD Y pixel loc of spot centroid
HDU FIBERPOS : fiberpos[nspec] #- Slit position of each fiber
HDU SPOTPOS : spotpos[NAXIS1] #- Slit positions where spots are sampled
HDU SPOTWAVE : spotwave[NAXIS2] #- Wavelengths where spots are sampled
``spot[i,j]`` is a 2D PSF spot sampled at slit position ``spotpos[i]`` and
wavelength ``spotwave[j]``. Its center is located on the CCD at
``spotx[i,j], spoty[i,j]``.
Pixellated PSF PCA expansion
----------------------------
PSFTYPE = "PCA-PIX"
This format is a PCA-like pixelated model such that::
pix = ConstImage + x*ImageX + y*ImageY + x*y*ImageXY + ...
HDU 0-2 : Same as Base PSF: X, Y, wavelength or loglam of traces
HDU 3 : table with x and y exponents to use for each image model::
Columns IMODEL, XEXP, YEXP
One row per model image
HDU 4 : table with x and y scale factors::
Columns IGROUP, X0, XSCALE, Y0, YSCALE
One row per fiber
The model images are separately calculated for groups of fibers
(could be bundles, but doesn't have to be). Within each group,
the x and y dimensions are rescaled such that::
x = xscale * (xpix - x0)
y = yscale * (ypix - y0)
HDU 5 : 4-D array with the PSF images::
Array[IGROUP, IMODEL, IY, IX]
*e.g.* to find the PSF for ifiber iflux::
xpix = HDU0[ifiber, iflux]
ypix = HDU1[ifiber, iflux]
x0 = HDU4.X0[ifiber]
y0 = HDU4.Y0[ifiber]
xscale = HDU4.XSCALE[ifiber]
yscale = HDU4.YSCALE[ifiber]
x = xscale*(xpix - x0)
y = yscale*(ypix - y0)
igroup = HDU4.IGROUP[ifiber]
psf = [BlankImage]
for imodel in range( len(HDU3) ):
xexp = HDU3.XEXP[imodel]
yexp = HDU3.YEXP[imodel]
psf += x^xexp * y^yexp * HDU5[igroup, imodel]
Gauss Hermite PSF
-----------------
.. _`specex PSF package`: https://github.com/julienguy/specex
This PSF format is generated by the `specex PSF package`_.
All parameters are stored in HDU 1 as Legendre coefficients describing
how each parameter varies with wavelength for each fiber.
The PSF is modeled by two Gauss-Hermite cores with different sigmas,
plus a semi-Lorentzian power-law tail.
Header Keywords for HDU 1
~~~~~~~~~~~~~~~~~~~~~~~~~
========= ===========
Parameter Description
========= ===========
PSFTYPE Must be 'GAUSS-HERMITE2'
PSFVER Must be '1 '
NPIX_X number of columns in input CCD image
NPIX_Y number of rows in input CCD image
HSIZEX Half size of PSF in fit, NX=2*HSIZEX+1
HSIZEY Half size of PSF in fit, NY=2*HSIZEY+1
BUNDLMIN first bundle of fibers (starting at 0)
BUNDLMAX last bundle of fibers (included)
FIBERMIN first fiber (starting at 0)
FIBERMAX last fiber (included)
NPARAMS number of PSF parameters
LEGDEG degree of Legendre pol.(wave) for parameters
GHDEGX degree of Hermite polynomial along CCD columns
GHDEGY degree of Hermite polynomial along CCD rows
GHDEGX2 degree of Hermite polynomial along CCD columns
GHDEGY2 degree of Hermite polynomial along CCD rows
========= ===========
Binary table in HDU 1
~~~~~~~~~~~~~~~~~~~~~
======= ===========
Column Description
======= ===========
PARAM Parameter name
WAVEMIN Mininum wavelength
WAVEMAX Maximum wavelength
COEFF Legendre coefficients. Each row is size [NSPEC, LEGDEG+1]
======= ===========
Each row contains the coefficients for the parameter name in the PARAM column.
WAVEMIN and WAVEMAX define the wavelength extent in Angstroms
that should be mapped to [-1,1] for evaluating the Legendre polynomials.
The coefficients themselves are in the rows of the COEFF column. Each
row of COEFF is a 2D array of shape [NSPEC, LEGDEG+1]
The parameters named in the PARAM column are:
========= ===========
Parameter Description
========= ===========
X CCD column coordinate (as a function of fiber and wavelength)
Y CCD row coordinate (as a function of fiber and wavelength)
(X,Y)=(0,0) means that PSF is centered on center of first pixel
GHSIGX Sigma of first Gaussian along CCD columns for PSF core
GHSIGY Sigma of first Gaussian along CCD rows for PSF core
GHNSIG NxSigma cutoff for first Gaussian-Hermite core term
GHSIGX2 Sigma of second Gaussian along CCD columns for PSF wings
GHSIGY2 Sigma of second Gaussian along CCD rows for PSF wings
GH-i-j Hermite pol. coefficents, i along columns, j along rows,
i is integer from 0 to GHDEGX, j is integer from 0 to GHDEGY,
there are (GHDEGX+1)*(GHDEGY+1) such coefficents.
GH2-i-j Hermite pol. coefficents for sec. GH psf, i=columns, j=rows
TAILAMP Amplitude of PSF tail
TAILCORE Size in pixels of PSF tail saturation in PSF core
TAILXSCA Scaling apply to CCD coordinate along columns for PSF tail
TAILYSCA Scaling apply to CCD coordinate along rows for PSF tail
TAILINDE Asymptotic power law index of PSF tail
CONT Continuum flux in arc image (not part of PSF)
========= ===========
These are evaluated as::
PSF_core1(X,Y) = SUM_ij (GH-i-j)*HERM(i,X/GHSIGX)*HERM(j,Y/GHSIGY)
* GAUS(X,GHSIGX)*GAUS(Y,GHSIGY)
--> Evaluated only for pixels where (X/GHSIGX)^2 + (Y/GHSIGY)^2 < GHNSIG^2
PSF_core2(X,Y) = SUM_ij (GH2-i-j)*HERM(i,X/GHSIGX2)*HERM(j,Y/GHSIGY2)
* GAUS(X,GHSIGX2)*GAUS(Y,GHSIGY2)
PSF_tail(X,Y) = TAILAMP*R^2/(TAILCORE^2+R^2)^(1+TAILINDE/2)
with R^2=(X*TAILXSCA)^2+(Y*TAILYSCA)^2
PSF_core is *integrated* in each pixel.
PSF_tail is not integrated, it is *evaluated* at the center of each pixel.
For example, if PARAM[0] = 'X', this means that row 0 contains the
Legendre coefficiencts for the X centroids of the PSFs on the CCD.:
# Map wavelength -> [-1,1]
w = 2.0*(wavelength - WAVEMIN[0]) / (WAVEMAX[0]-WAVEMIN[0]) - 1.0
# Evaluate the CCD X centroid for Fiber ispec
# x = Sum_i COEFF[ispec,i] * L_i(w)
from numpy.polynomial.legendre import legval
xcoeff = COEFF[0] #- since PARAM[0] == 'X'
x = legval(w, xcoeff[ispec])
Gauss Hermite PSF (Deprecated)
------------------------------
*NOTE* : This format is implemented but somewhat cumbersome. We may
abandon it for a simpler format.
This format models the PSF as 2D Gauss-Hermite polynomials. The coefficients
of the polynomials are modeled as smoothly varying in (x,y) using 2D Legendre
polynomials. Spectra grouped in bundles with a smoothly
varying solution within the bundle but independent of other bundles.
HDU 0-2 : Same as Base PSF: X, Y, wavelength or loglam of traces::
PSFTYPE = "PCA-PIX"
PSFVERS = "2.0" or above
HDU 3+ : One HDU per bundle of spectra on the CCD
Header Keywords for HDUs 3+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
================== ===========
Keyword Meaning
================== ===========
GHSIGX, GHSIGY Gauss-Hermite sigma in x and y directions [pixel units]
GHDEGX, GHDEGY Gauss-Hermite degree in x and y directions
FIBERMIN, FIBERMAX Fibers covered by this bundle min to max inclusive, 0-indexed
LXMIN, LXMAX X-coordinate min/max to transform CCD x -> [-1,1] range for Legendre polynomial
LYMIN, LYMAX Y-coordinate min/max to transform CCD y -> [-1,1] range for Legendre polynomial
LDEGX, LDEGY Degree of Legendre polynomial in x and y directions
================== ===========
Data in HDU 3+
~~~~~~~~~~~~~~
A 4D image `coeff[GHDEGY+1, GHDEGX+1, LDEGY+1, LDEGX+1]` for that bundle.
Example
-------
Find the PSF for fiber 5 at wavelength 6000 Angstroms:
* Map fiber 5, wavelength 6000 A -> (x,y) on the CCD using HDUs 0-2:
- x = numpy.interp(6000, WAVELENGTH[5], X[5])
- y = numpy.interp(6000, WAVELENGTH[5], Y[5])
* Find which bundle fiber 5 is included in (probably bundle 0 in HDU 3)
- Convert x,y -> to ranges [-1,1]
* xx = 2*(x-LXMIN)/(LXMAX - LXMIN) - 1
* yy = 2*(y-LYMIN)/(LYMAX - LYMIN) - 1
* The Gauss-Hermite coefficent c_ij = Sum_kl data[i,j,k,l] L_k(yy) L_l(xx)
where L_k is the kth order Legendre Polynomial
* PSF(dx, dy) = Sum_ij c_ij H_i(dy/GHSIGY) H_j(dx/GHSIGX)
* Then integrate PSF(dx,dy) over the individual pixels
- In practice it is better to directly integrate the functions
Notes
-----
This is different from the original Gauss-Hermite format used by bbspec.
These may be distingished by the existence of PSFVERS >= 2.0
Future versions of this format may also include additional HDUs to model
the wings of the PSF and the covariance of the coefficients.
Other PSF Formats
-----------------
Specter grew out of "bbspec" which includes PSF formats for:
* 2D rotated asymmetric Gaussian
* An older format for Gauss-Hermite polynomials
These are structurally compatible with Specter PSFs but haven't been
ported yet.