summaryrefslogtreecommitdiff
path: root/updatewcs/dgeo.py
diff options
context:
space:
mode:
Diffstat (limited to 'updatewcs/dgeo.py')
-rw-r--r--updatewcs/dgeo.py250
1 files changed, 250 insertions, 0 deletions
diff --git a/updatewcs/dgeo.py b/updatewcs/dgeo.py
new file mode 100644
index 0000000..00178d9
--- /dev/null
+++ b/updatewcs/dgeo.py
@@ -0,0 +1,250 @@
+import pyfits
+from pytools import fileutil
+import numpy as np
+
+class DGEOCorr(object):
+ """
+ Purpose
+ =======
+ Defines a Lookup table prior distortion correction as per WCS paper IV.
+ It uses a reference file defined by the DGEOFILE keyword in the primary header.
+
+ Algorithm
+ =========
+ - Using extensions in the reference file create a WCSDVARR extension
+ and add it to the file object.
+ - Add record-valued keywords which describe the lookup tables to the
+ science extension header
+ - Add a keyword 'DGEOFILE' to the science extension header, whose
+ value is the reference file used to create the WCSVARR extension
+
+ If WCSDVARR extensions exist, subsequent updates will overwrite them.
+ If not, they will be added to the file object.
+
+ It is assumed that the DGEO reference files were created to work with IDC tables
+ but will be applied with SIP coefficients. A transformation is applied to correct
+ for the fact that the lookup tables will be applied before the first order coefficients
+ which are in the CD matrix when the SIP convention is used.
+ """
+
+ def updateWCS(cls, fobj):
+ """
+ :Parameters:
+ `fobj`: pyfits object
+ Science file, for which a distortion correction in a DGEOFILE is available
+
+ """
+ assert isinstance(fobj, pyfits.NP_pyfits.HDUList)
+ cls.applyDgeoCorr(fobj)
+ dgfile = fobj[0].header['DGEOFILE']
+
+ new_kw = {'DGEOEXT': dgfile}
+ return new_kw
+
+ updateWCS = classmethod(updateWCS)
+
+ def applyDgeoCorr(cls, fobj):
+ """
+ For each science extension in a pyfits file object:
+ - create a WCSDVARR extension
+ - update science header
+ - add/update DGEOEXT keyword
+ """
+ dgfile = fileutil.osfn(fobj[0].header['DGEOFILE'])
+ # Map WCSDVARR EXTVER numbers to extension numbers
+ wcsdvarr_ind = cls.getWCSIndex(fobj)
+ for ext in fobj:
+ try:
+ extname = ext.header['EXTNAME'].lower()
+ except KeyError:
+ continue
+ if extname == 'sci':
+ extversion = ext.header['EXTVER']
+ header = ext.header
+ # get the data arrays from the reference file and transform them for use with SIP
+ dx,dy = cls.getData(dgfile, extversion)
+ ndx, ndy = cls.transformData(header, dx,dy)
+ # determine EXTVER for the WCSDVARR extension from the DGEO file (EXTNAME, EXTVER) kw
+ wcsdvarr_x_version = 2 * extversion -1
+ wcsdvarr_y_version = 2 * extversion
+
+ for ename in zip(['DX', 'DY'], [wcsdvarr_x_version,wcsdvarr_y_version],[ndx, ndy]):
+ cls.addSciExtKw(header, wdvarr_ver=ename[1], dgeo_name=ename[0])
+ hdu = cls.createDgeoHDU(header, dgeofile=dgfile, wdvarr_ver=ename[1],dgeo_name=ename[0], data=ename[2],extver=extversion)
+ if wcsdvarr_ind:
+ fobj[wcsdvarr_ind[ename[1]]] = hdu
+ else:
+ fobj.append(hdu)
+
+
+ applyDgeoCorr = classmethod(applyDgeoCorr)
+
+ def getWCSIndex(cls, fobj):
+ """
+ If fobj has WCSDVARR extensions:
+ returns a mapping of their EXTVER kw are mapped to extension numbers
+ if fobj does not have WCSDVARR extensions:
+ an empty dictionary is returned.
+ """
+ wcsd = {}
+ for e in range(len(fobj)):
+ try:
+ ename = fobj[e].header['EXTNAME']
+ except KeyError:
+ continue
+ if ename == 'WCSDVARR':
+ wcsd[fobj[e].header['EXTVER']] = e
+
+ return wcsd
+
+ getWCSIndex = classmethod(getWCSIndex)
+
+ def addSciExtKw(cls, hdr, wdvarr_ver=None, dgeo_name=None):
+ """
+ Adds kw to sci extension to define WCSDVARR lookup table extensions
+
+ """
+ if dgeo_name =='DX':
+ j=1
+ else:
+ j=2
+
+ cperror = 'CPERROR%s' %j
+ cpdis = 'CPDIS%s' %j
+ dpext = 'DP%s.' %j + 'EXTVER'
+ dpnaxes = 'DP%s.' %j +'NAXES'
+ dpaxis1 = 'DP%s.' %j+'AXIS.1'
+ dpaxis2 = 'DP%s.' %j+'AXIS.2'
+ keys = [cperror, cpdis, dpext, dpnaxes, dpaxis1, dpaxis2]
+ values = {cperror: 0.0, cpdis: 'Lookup', dpext: wdvarr_ver, dpnaxes: 2,
+ dpaxis1: 1, dpaxis2: 2}
+
+ comments = {cperror: 'Maximum error of dgeo correction for axis %s' % (wdvarr_ver/2),
+ cpdis: 'Prior distortion funcion type',
+ dpext: 'Version number of WCSDVARR extension containing lookup distortion table',
+ dpnaxes: 'Number of independent variables in distortion function',
+ dpaxis1: 'Axis number of the jth independent variable in a distortion function',
+ dpaxis2: 'Axis number of the jth independent variable in a distortion function'
+ }
+
+ for key in keys:
+ hdr.update(key=key, value=values[key], comment=comments[key], before='HISTORY')
+
+ addSciExtKw = classmethod(addSciExtKw)
+
+ def getData(cls,dgfile, extver):
+ """
+ Get the data arrays from the reference DGEO files
+ """
+ xdata = pyfits.getdata(dgfile, ext=('DX',extver))
+ ydata = pyfits.getdata(dgfile, ext=('DY',extver))
+ return xdata, ydata
+ getData = classmethod(getData)
+
+ def transformData(cls, header, dx, dy):
+ """
+ Transform the DGEO data arrays for use with SIP
+ """
+ coeffs = cls.getCoeffs(header)
+ idcscale = header['IDCSCALE']
+ sclcoeffs = np.linalg.inv(coeffs/idcscale)
+ ndx, ndy = np.dot(sclcoeffs, [dx.ravel(), dy.ravel()])
+ ndx.shape = dx.shape
+ ndy.shape=dy.shape
+ return ndx, ndy
+ transformData = classmethod(transformData)
+
+ def getCoeffs(cls, header):
+ """
+ Return a matrix of the first order IDC coefficients.
+ """
+ try:
+ ocx10 = header['OCX10']
+ ocx11 = header['OCX11']
+ ocy10 = header['OCY10']
+ ocy11 = header['OCY11']
+ except AttributeError:
+ print 'First order IDCTAB coefficients are not available.\n'
+ print 'Cannot convert SIP to IDC coefficients.\n'
+ return None
+ return np.array([[ocx11, ocx10], [ocy11,ocy10]], dtype=np.float32)
+
+ getCoeffs = classmethod(getCoeffs)
+
+ def createDgeoHDU(cls, sciheader, dgeofile=None, wdvarr_ver=1, dgeo_name=None,data = None, extver=1):
+ """
+ Creates an HDU to be added to the file object.
+ """
+ hdr = cls.createDgeoHdr(sciheader, dgeofile=dgeofile, wdvarr_ver=wdvarr_ver, dgeoname=dgeo_name, extver=extver)
+ hdu=pyfits.ImageHDU(header=hdr, data=data)
+ return hdu
+
+ createDgeoHDU = classmethod(createDgeoHDU)
+
+ def createDgeoHdr(cls, sciheader, dgeofile, wdvarr_ver, dgeoname, extver):
+ """
+ Creates a header for the WCSDVARR extension based on the DGEO reference file
+ and sci extension header.
+ """
+ dgeo_header = pyfits.getheader(dgeofile, ext=(dgeoname,extver))
+ sci_naxis1 = sciheader['NAXIS1']
+ sci_naxis2 = sciheader['NAXIS2']
+ sci_crpix1 = sciheader['CRPIX1']
+ sci_crpix2 = sciheader['CRPIX2']
+
+ naxis1 = dgeo_header['naxis1']
+ naxis2 = dgeo_header['naxis2']
+ extver = dgeo_header['extver']
+ crpix1 = naxis1/2.
+ crpix2 = naxis2/2.
+ cdelt1 = sci_naxis1/naxis1
+ cdelt2 = sci_naxis2/naxis2
+ crval1 = sci_crpix1
+ crval2 = sci_crpix2
+ keys = ['XTENSION','BITPIX','NAXIS','NAXIS1','NAXIS2',
+ 'EXTNAME','EXTVER','PCOUNT','GCOUNT','CRPIX1',
+ 'CDELT1','CRVAL1','CRPIX2','CDELT2','CRVAL2']
+
+ comments = {'XTENSION': 'Image extension',
+ 'BITPIX': 'IEEE floating point',
+ 'NAXIS': 'Number of axes',
+ 'NAXIS1': 'Number of image columns',
+ 'NAXIS2': 'Number of image rows',
+ 'EXTNAME': 'WCS distortion array',
+ 'EXTVER': 'Distortion array version number',
+ 'PCOUNT': 'Special data area of size 0',
+ 'GCOUNT': 'One data group',
+ 'CRPIX1': 'Distortion array reference pixel',
+ 'CDELT1': 'Grid step size in first coordinate',
+ 'CRVAL1': 'Image array pixel coordinate',
+ 'CRPIX2': 'Distortion array reference pixel',
+ 'CDELT2': 'Grid step size in second coordinate',
+ 'CRVAL2': 'Image array pixel coordinate'}
+
+ values = {'XTENSION': 'IMAGE',
+ 'BITPIX': -32,
+ 'NAXIS': 2,
+ 'NAXIS1': naxis1,
+ 'NAXIS2': naxis2,
+ 'EXTNAME': 'WCSDVARR',
+ 'EXTVER': wdvarr_ver,
+ 'PCOUNT': 0,
+ 'GCOUNT': 1,
+ 'CRPIX1': crpix1,
+ 'CDELT1': cdelt1,
+ 'CRVAL1': crval1,
+ 'CRPIX2': crpix2,
+ 'CDELT2': cdelt2,
+ 'CRVAL2': crval2
+ }
+
+
+ cdl = pyfits.CardList()
+ for c in keys:
+ cdl.append(pyfits.Card(key=c, value=values[c], comment=comments[c]))
+
+ hdr = pyfits.Header(cards=cdl)
+ return hdr
+
+ createDgeoHdr = classmethod(createDgeoHdr)
+