diff options
Diffstat (limited to 'lib/stwcs/distortion')
-rw-r--r-- | lib/stwcs/distortion/__init__.py | 0 | ||||
-rw-r--r-- | lib/stwcs/distortion/coeff_converter.py | 142 | ||||
-rw-r--r-- | lib/stwcs/distortion/models.py | 362 | ||||
-rw-r--r-- | lib/stwcs/distortion/mutil.py | 703 | ||||
-rw-r--r-- | lib/stwcs/distortion/utils.py | 270 |
5 files changed, 0 insertions, 1477 deletions
diff --git a/lib/stwcs/distortion/__init__.py b/lib/stwcs/distortion/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/lib/stwcs/distortion/__init__.py +++ /dev/null diff --git a/lib/stwcs/distortion/coeff_converter.py b/lib/stwcs/distortion/coeff_converter.py deleted file mode 100644 index 415b512..0000000 --- a/lib/stwcs/distortion/coeff_converter.py +++ /dev/null @@ -1,142 +0,0 @@ -from __future__ import division, print_function # confidence high - -import numpy as np -from astropy.io import fits -from astropy import wcs as pywcs - -def sip2idc(wcs): - """ - Converts SIP style coefficients to IDCTAB coefficients. - - Parameters - ---------- - wcs : `astropy.io.fits.Header` or `astropy.wcs.WCS` object - """ - if isinstance(wcs, fits.Header): - ocx10 = wcs.get('OCX10', None) - ocx11 = wcs.get('OCX11', None) - ocy10 = wcs.get('OCY10', None) - ocy11 = wcs.get('OCY11', None) - order = wcs.get('A_ORDER', None) - sipa, sipb = _read_sip_kw(wcs) - if None in [ocx10, ocx11, ocy10, ocy11, sipa, sipb]: - print('Cannot convert SIP to IDC coefficients.\n') - return None, None - elif isinstance(wcs, pywcs.WCS): - try: - ocx10 = wcs.ocx10 - ocx11 = wcs.ocx11 - ocy10 = wcs.ocy10 - ocy11 = wcs.ocy11 - except AttributeError: - print('First order IDCTAB coefficients are not available.\n') - print('Cannot convert SIP to IDC coefficients.\n') - return None, None - try: - sipa = wcs.sip.a - sipb = wcs.sip.b - except AttributeError: - print('SIP coefficients are not available.') - print('Cannot convert SIP to IDC coefficients.\n') - return None, None - try: - order = wcs.sip.a_order - except AttributeError: - print('SIP model order unknown, exiting ...\n') - return None, None - - else: - print('Input to sip2idc must be a PyFITS header or a wcsutil.HSTWCS object\n') - return - - - if None in [ocx10, ocx11, ocy10, ocy11]: - print('First order IDC coefficients not found, exiting ...\n') - return None, None - idc_coeff = np.array([[ocx11, ocx10], [ocy11, ocy10]]) - cx = np.zeros((order+1,order+1), dtype=np.double) - cy = np.zeros((order+1,order+1), dtype=np.double) - for n in range(order+1): - for m in range(order+1): - if n >= m and n>=2: - sipval = np.array([[sipa[m,n-m]],[sipb[m,n-m]]]) - idcval = np.dot(idc_coeff, sipval) - cx[n,m] = idcval[0] - cy[n,m] = idcval[1] - - cx[1,0] = ocx10 - cx[1,1] = ocx11 - cy[1,0] = ocy10 - cy[1,1] = ocy11 - - return cx, cy - -def _read_sip_kw(header): - """ - Reads SIP header keywords and returns an array of coefficients. - - If no SIP header keywords are found, None is returned. - """ - if "A_ORDER" in header: - if "B_ORDER" not in header: - raise ValueError( - "A_ORDER provided without corresponding B_ORDER " - "keyword for SIP distortion") - - m = int(header["A_ORDER"]) - a = np.zeros((m+1, m+1), np.double) - for i in range(m+1): - for j in range(m-i+1): - a[i, j] = header.get("A_%d_%d" % (i, j), 0.0) - - m = int(header["B_ORDER"]) - b = np.zeros((m+1, m+1), np.double) - for i in range(m+1): - for j in range(m-i+1): - b[i, j] = header.get("B_%d_%d" % (i, j), 0.0) - elif "B_ORDER" in header: - raise ValueError( - "B_ORDER provided without corresponding A_ORDER " - "keyword for SIP distortion") - else: - a = None - b = None - - return a , b - - -""" -def idc2sip(wcsobj, idctab = None): - if isinstance(wcs,pywcs.WCS): - try: - cx10 = wcsobj.ocx10 - cx11 = wcsobj.cx11 - cy10 = wcsobj.cy10 - cy11 = wcsobj.cy11 - except AttributeError: - print - try: - order = wcs.sip.a_order - except AttributeError: - print 'SIP model order unknown, exiting ...\n' - return - else: - print 'Input to sip2idc must be a PyFITS header or a wcsutil.HSTWCS object\n' - return - - if None in [ocx10, ocx11, ocy10, ocy11]: - print 'First order IDC coefficients not found, exiting ...\n' - return - idc_coeff = np.array([[wcsobj.cx11, wcsobj.cx10], [wcsobj.cy11, wcsobj.cy10]]) - cx = numpy.zeros((order+1,order+1), dtype=numpy.double) - cy = numpy.zeros((order+1,order+1), dtype=numpy.double) - for n in range(order+1): - for m in range(order+1): - if n >= m and n>=2: - sipval = numpy.array([[wcsobj.sip.a[n,m]],[wcsobj.sip.b[n,m]]]) - idcval = numpy.dot(idc_coeff, sipval) - cx[m,n-m] = idcval[0] - cy[m,n-m] = idcval[1] - - return cx, cy -""" diff --git a/lib/stwcs/distortion/models.py b/lib/stwcs/distortion/models.py deleted file mode 100644 index 231a9f1..0000000 --- a/lib/stwcs/distortion/models.py +++ /dev/null @@ -1,362 +0,0 @@ -from __future__ import absolute_import, division, print_function # confidence high - - -import numpy as np - -# Import PyDrizzle utility modules -from . import mutil -from .mutil import combin - -yes = True -no = False - -################# -# -# -# Geometry/Distortion Classes -# -# -################# - -class GeometryModel: - """ - Base class for Distortion model. - There will be a separate class for each type of - model/filetype used with drizzle, i.e., IDCModel and - DrizzleModel. - - Each class will know how to apply the distortion to a - single point and how to convert coefficients to an input table - suitable for the drizzle task. - - Coefficients will be stored in CX,CY arrays. - """ - # - # - # - # - # - # - # - NORDER = 3 - - def __init__(self): - " This will open the given file and determine its type and norder." - - # Method to read in coefficients from given table and - # populate the n arrays 'cx' and 'cy'. - # This will be different for each type of input file, - # IDCTAB vs. drizzle table. - - # Set these up here for all sub-classes to use... - # But, calculate norder and cx,cy arrays in detector specific classes. - self.cx = None - self.cy = None - self.refpix = None - self.norder = self.NORDER - # Keep track of computed zero-point for distortion coeffs - self.x0 = None - self.y0 = None - - # default values for these attributes - self.direction = 'forward' - - self.pscale = 1.0 - - def shift(self, xs, ys): - """ - Shift reference position of coefficients to new center - where (xs,ys) = old-reference-position - subarray/image center. - This will support creating coeffs files for drizzle which will - be applied relative to the center of the image, rather than relative - to the reference position of the chip. - """ - - _cxs = np.zeros(shape=self.cx.shape,dtype=self.cx.dtype) - _cys = np.zeros(shape=self.cy.shape,dtype=self.cy.dtype) - _k = self.norder + 1 - # loop over each input coefficient - for m in range(_k): - for n in range(_k): - if m >= n: - # For this coefficient, shift by xs/ys. - _ilist = list(range(m, _k)) - # sum from m to k - for i in _ilist: - _jlist = list(range(n, i - (m-n)+1)) - # sum from n to i-(m-n) - for j in _jlist: - _cxs[m,n] += self.cx[i,j]*combin(j,n)*combin((i-j),(m-n))*pow(xs,(j-n))*pow(ys,((i-j)-(m-n))) - _cys[m,n] += self.cy[i,j]*combin(j,n)*combin((i-j),(m-n))*pow(xs,(j-n))*pow(ys,((i-j)-(m-n))) - self.cx = _cxs.copy() - self.cy = _cys.copy() - - def convert(self, tmpname, xref=None,yref=None,delta=yes): - """ - Open up an ASCII file, output coefficients in drizzle - format after converting them as necessary. - First, normalize these coefficients to what drizzle expects - Normalize the coefficients by the MODEL/output plate scale. - - 16-May-2002: - Revised to work with higher order polynomials by John Blakeslee. - 27-June-2002: - Added ability to shift coefficients to new center for support - of subarrays. - """ - cx = self.cx/self.pscale - cy = self.cy/self.pscale - x0 = self.refpix['XDELTA'] + cx[0,0] - y0 = self.refpix['YDELTA'] + cy[0,0] - #xr = self.refpix['XREF'] - #yr = self.refpix['YREF'] - xr = self.refpix['CHIP_XREF'] - yr = self.refpix['CHIP_YREF'] - - - - ''' - if xref != None: - # Shift coefficients for use with drizzle - _xs = xref - self.refpix['XREF'] + 1.0 - _ys = yref - self.refpix['YREF'] + 1.0 - - - if _xs != 0 or _ys != 0: - cxs,cys= self.shift(cx, cy, _xs, _ys) - cx = cxs - cy = cys - - # We only want to apply this shift to coeffs - # for subarray images. - if delta == no: - cxs[0,0] = cxs[0,0] - _xs - cys[0,0] = cys[0,0] - _ys - - # Now, apply only the difference introduced by the distortion.. - # i.e., (undistorted - original) shift. - x0 += cxs[0,0] - y0 += cys[0,0] - ''' - self.x0 = x0 #+ 1.0 - self.y0 = y0 #+ 1.0 - - # Now, write out the coefficients into an ASCII - # file in 'drizzle' format. - lines = [] - - - lines.append('# Polynomial distortion coefficients\n') - lines.append('# Extracted from "%s" \n'%self.name) - lines.append('refpix %f %f \n'%(xr,yr)) - if self.norder==3: - lines.append('cubic\n') - elif self.norder==4: - lines.append('quartic\n') - elif self.norder==5: - lines.append('quintic\n') - else: - raise ValueError("Drizzle cannot handle poly distortions of order %d" % self.norder) - - str = '%16.8f %16.8g %16.8g %16.8g %16.8g \n'% (x0,cx[1,1],cx[1,0],cx[2,2],cx[2,1]) - lines.append(str) - str = '%16.8g %16.8g %16.8g %16.8g %16.8g \n'% (cx[2,0],cx[3,3],cx[3,2],cx[3,1],cx[3,0]) - lines.append(str) - if self.norder>3: - str = '%16.8g %16.8g %16.8g %16.8g %16.8g \n'% (cx[4,4],cx[4,3],cx[4,2],cx[4,1],cx[4,0]) - lines.append(str) - if self.norder>4: - str = '%16.8g %16.8g %16.8g %16.8g %16.8g %16.8g \n'% (cx[5,5],cx[5,4],cx[5,3],cx[5,2],cx[5,1],cx[5,0]) - lines.append(str) - lines.append("\n") - - str = '%16.8f %16.8g %16.8g %16.8g %16.8g \n'% (y0,cy[1,1],cy[1,0],cy[2,2],cy[2,1]) - lines.append(str) - str = '%16.8g %16.8g %16.8g %16.8g %16.8g \n'% (cy[2,0],cy[3,3],cy[3,2],cy[3,1],cy[3,0]) - lines.append(str) - if self.norder>3: - str = '%16.8g %16.8g %16.8g %16.8g %16.8g \n'% (cy[4,4],cy[4,3],cy[4,2],cy[4,1],cy[4,0]) - lines.append(str) - if self.norder>4: - str = '%16.8g %16.8g %16.8g %16.8g %16.8g %16.8g \n'% (cy[5,5],cy[5,4],cy[5,3],cy[5,2],cy[5,1],cy[5,0]) - lines.append(str) - - output = open(tmpname,'w') - output.writelines(lines) - output.close() - - - def apply(self, pixpos,scale=1.0,order=None): - """ - Apply coefficients to a pixel position or a list of positions. - This should be the same for all coefficients tables. - Return the geometrically-adjusted position - in arcseconds from the reference position as a tuple (x,y). - - Compute delta from reference position - """ - - """ - scale actually is a ratio of pscale/self.model.pscale - what is pscale? - """ - if self.cx == None: - return pixpos[:,0],pixpos[:,1] - - if order is None: - order = self.norder - - # Apply in the same way that 'drizzle' would... - _cx = self.cx / (self.pscale * scale) - _cy = self.cy / (self.pscale * scale) - _convert = no - _p = pixpos - - # Do NOT include any zero-point terms in CX,CY here - # as they should not be scaled by plate-scale like rest - # of coeffs... This makes the computations consistent - # with 'drizzle'. WJH 17-Feb-2004 - _cx[0,0] = 0. - _cy[0,0] = 0. - - if isinstance(_p, list) or isinstance(_p, tuple): - _p = np.array(_p,dtype=np.float64) - _convert = yes - - dxy = _p - (self.refpix['XREF'],self.refpix['YREF']) - # Apply coefficients from distortion model here... - c = _p * 0. - for i in range(order+1): - for j in range(i+1): - c[:,0] = c[:,0] + _cx[i][j] * pow(dxy[:,0],j) * pow(dxy[:,1],(i-j)) - c[:,1] = c[:,1] + _cy[i][j] * pow(dxy[:,0],j) * pow(dxy[:,1],(i-j)) - xc = c[:,0] - yc = c[:,1] - - # Convert results back to same form as original input - if _convert: - xc = xc.tolist() - yc = yc.tolist() - # If a single tuple was input, return just a single tuple - if len(xc) == 1: - xc = xc[0] - yc = yc[0] - - return xc,yc - - def setPScaleCoeffs(self,pscale): - self.cx[1,1] = pscale - self.cy[1,0] = pscale - - self.refpix['PSCALE'] = pscale - self.pscale = pscale - - -class IDCModel(GeometryModel): - """ - This class will open the IDCTAB, select proper row based on - chip/direction and populate cx,cy arrays. - We also need to read in SCALE, XCOM,YCOM, XREF,YREF as well. - """ - def __init__(self, idcfile, date=None, chip=1, direction='forward', - filter1='CLEAR1',filter2='CLEAR2',offtab=None, binned=1): - GeometryModel.__init__(self) - # - # Norder must be derived from the coeffs file itself, - # then the arrays can be setup. Thus, it needs to be - # done in the sub-class, not in the base class. - # Read in table. - # Populate cx,cy,scale, and other variables here. - # - self.name = idcfile - self.cx,self.cy,self.refpix,self.norder = mutil.readIDCtab(idcfile, - chip=chip,direction=direction,filter1=filter1,filter2=filter2, - date=date, offtab=offtab) - - if 'empty_model' in self.refpix and self.refpix['empty_model']: - pass - else: - self.refpix['PSCALE'] = self.refpix['PSCALE'] * binned - self.cx = self.cx * binned - self.cy = self.cy * binned - self.refpix['XREF'] = self.refpix['XREF'] / binned - self.refpix['YREF'] = self.refpix['YREF'] / binned - self.refpix['XSIZE'] = self.refpix['XSIZE'] / binned - self.refpix['YSIZE'] = self.refpix['YSIZE'] / binned - - self.pscale = self.refpix['PSCALE'] - - -class WCSModel(GeometryModel): - """ - This class sets up a distortion model based on coefficients - found in the image header. - """ - def __init__(self,header,rootname): - GeometryModel.__init__(self) - - - if 'rootname' in header: - self.name = header['rootname'] - else: - self.name = rootname - # Initialize all necessary distortion arrays with - # default model... - #self.cx,self.cy,self.refpix,self.order = mutil.defaultModel() - - # Read in values from header, and update distortion arrays. - self.cx,self.cy,self.refpix,self.norder = mutil.readWCSCoeffs(header) - - self.pscale = self.refpix['PSCALE'] - - - -class DrizzleModel(GeometryModel): - """ - This class will read in an ASCII Cubic - drizzle coeffs file and populate the cx,cy arrays. - """ - - def __init__(self, idcfile, scale = None): - GeometryModel.__init__(self) - # - # We now need to read in the file, populate cx,cy, and - # other variables as necessary. - # - self.name = idcfile - self.cx,self.cy,self.refpix,self.norder = mutil.readCubicTable(idcfile) - - # scale is the ratio wcs.pscale/model.pscale. - # model.pscale for WFPC2 is passed from REFDATA. - # This is needed for WFPC2 binned data. - - if scale != None: - self.pscale = scale - else: - self.pscale = self.refpix['PSCALE'] - - """ - The above definition looks wrong. - In one case it's a ratio in the other it's pscale. - - """ - -class TraugerModel(GeometryModel): - """ - This class will read in the ASCII Trauger coeffs - file, convert them to SIAF coefficients, then populate - the cx,cy arrays. - """ - NORDER = 3 - - def __init__(self, idcfile,lam): - GeometryModel.__init__(self) - self.name = idcfile - self.cx,self.cy,self.refpix,self.norder = mutil.readTraugerTable(idcfile,lam) - self.pscale = self.refpix['PSCALE'] - # - # Read in file here. - # Populate cx,cy, and other variables. - # - - diff --git a/lib/stwcs/distortion/mutil.py b/lib/stwcs/distortion/mutil.py deleted file mode 100644 index ed6a1ea..0000000 --- a/lib/stwcs/distortion/mutil.py +++ /dev/null @@ -1,703 +0,0 @@ -from __future__ import division, print_function # confidence high - -from stsci.tools import fileutil -import numpy as np -import calendar - -# Set up IRAF-compatible Boolean values -yes = True -no = False - -# This function read the IDC table and generates the two matrices with -# the geometric correction coefficients. -# -# INPUT: FITS object of open IDC table -# OUTPUT: coefficient matrices for Fx and Fy -# -#### If 'tabname' == None: This should return a default, undistorted -#### solution. -# - -def readIDCtab (tabname, chip=1, date=None, direction='forward', - filter1=None,filter2=None, offtab=None): - - """ - Read IDCTAB, and optional OFFTAB if sepcified, and generate - the two matrices with the geometric correction coefficients. - - If tabname == None, then return a default, undistorted solution. - If offtab is specified, dateobs also needs to be given. - - """ - - # Return a default geometry model if no IDCTAB filename - # is given. This model will not distort the data in any way. - if tabname == None: - print('Warning: No IDCTAB specified! No distortion correction will be applied.') - return defaultModel() - - # Implement default values for filters here to avoid the default - # being overwritten by values of None passed by user. - if filter1 == None or filter1.find('CLEAR') == 0 or filter1.strip() == '': - filter1 = 'CLEAR' - if filter2 == None or filter2.find('CLEAR') == 0 or filter2.strip() == '': - filter2 = 'CLEAR' - - # Insure that tabname is full filename with fully expanded - # IRAF variables; i.e. 'jref$mc41442gj_idc.fits' should get - # expanded to '/data/cdbs7/jref/mc41442gj_idc.fits' before - # being used here. - # Open up IDC table now... - try: - ftab = fileutil.openImage(tabname) - except: - err_str = "------------------------------------------------------------------------ \n" - err_str += "WARNING: the IDCTAB geometric distortion file specified in the image \n" - err_str += "header was not found on disk. Please verify that your environment \n" - err_str += "variable ('jref'/'uref'/'oref'/'nref') has been correctly defined. If \n" - err_str += "you do not have the IDCTAB file, you may obtain the latest version \n" - err_str += "of it from the relevant instrument page on the STScI HST website: \n" - err_str += "http://www.stsci.edu/hst/ For WFPC2, STIS and NICMOS data, the \n" - err_str += "present run will continue using the old coefficients provided in \n" - err_str += "the Dither Package (ca. 1995-1998). \n" - err_str += "------------------------------------------------------------------------ \n" - raise IOError(err_str) - - #First thing we need, is to read in the coefficients from the IDC - # table and populate the Fx and Fy matrices. - - if 'DETECTOR' in ftab['PRIMARY'].header: - detector = ftab['PRIMARY'].header['DETECTOR'] - else: - if 'CAMERA' in ftab['PRIMARY'].header: - detector = str(ftab['PRIMARY'].header['CAMERA']) - else: - detector = 1 - # First, read in TDD coeffs if present - phdr = ftab['PRIMARY'].header - instrument = phdr['INSTRUME'] - if instrument == 'ACS' and detector == 'WFC': - skew_coeffs = read_tdd_coeffs(phdr, chip=chip) - else: - skew_coeffs = None - - # Set default filters for SBC - if detector == 'SBC': - if filter1 == 'CLEAR': - filter1 = 'F115LP' - filter2 = 'N/A' - if filter2 == 'CLEAR': - filter2 = 'N/A' - - # Read FITS header to determine order of fit, i.e. k - norder = ftab['PRIMARY'].header['NORDER'] - if norder < 3: - order = 3 - else: - order = norder - - fx = np.zeros(shape=(order+1,order+1),dtype=np.float64) - fy = np.zeros(shape=(order+1,order+1),dtype=np.float64) - - #Determine row from which to get the coefficients. - # How many rows do we have in the table... - fshape = ftab[1].data.shape - colnames = ftab[1].data.names - row = -1 - - # Loop over all the rows looking for the one which corresponds - # to the value of CCDCHIP we are working on... - for i in range(fshape[0]): - - try: - # Match FILTER combo to appropriate row, - #if there is a filter column in the IDCTAB... - if 'FILTER1' in colnames and 'FILTER2' in colnames: - - filt1 = ftab[1].data.field('FILTER1')[i] - if filt1.find('CLEAR') > -1: filt1 = filt1[:5] - - filt2 = ftab[1].data.field('FILTER2')[i] - if filt2.find('CLEAR') > -1: filt2 = filt2[:5] - else: - if 'OPT_ELEM' in colnames: - filt1 = ftab[1].data.field('OPT_ELEM') - if filt1.find('CLEAR') > -1: filt1 = filt1[:5] - else: - filt1 = filter1 - - if 'FILTER' in colnames: - _filt = ftab[1].data.field('FILTER')[i] - if _filt.find('CLEAR') > -1: _filt = _filt[:5] - if 'OPT_ELEM' in colnames: - filt2 = _filt - else: - filt1 = _filt - filt2 = 'CLEAR' - else: - filt2 = filter2 - except: - # Otherwise assume all rows apply and compare to input filters... - filt1 = filter1 - filt2 = filter2 - - if 'DETCHIP' in colnames: - detchip = ftab[1].data.field('DETCHIP')[i] - if not str(detchip).isdigit(): - detchip = 1 - else: - detchip = 1 - - if 'DIRECTION' in colnames: - direct = ftab[1].data.field('DIRECTION')[i].lower().strip() - else: - direct = 'forward' - - if filt1 == filter1.strip() and filt2 == filter2.strip(): - if direct == direction.strip(): - if int(detchip) == int(chip) or int(detchip) == -999: - row = i - break - - joinstr = ',' - if 'CLEAR' in filter1: - f1str = '' - joinstr = '' - else: - f1str = filter1.strip() - if 'CLEAR' in filter2: - f2str = '' - joinstr = '' - else: - f2str = filter2.strip() - filtstr = (joinstr.join([f1str,f2str])).strip() - if row < 0: - err_str = '\nProblem finding row in IDCTAB! Could not find row matching:\n' - err_str += ' CHIP: '+str(detchip)+'\n' - err_str += ' FILTERS: '+filtstr+'\n' - ftab.close() - del ftab - raise LookupError(err_str) - else: - print('- IDCTAB: Distortion model from row',str(row+1),'for chip',detchip,':',filtstr) - - # Read in V2REF and V3REF: this can either come from current table, - # or from an OFFTAB if time-dependent (i.e., for WFPC2) - theta = None - if 'V2REF' in colnames: - v2ref = ftab[1].data.field('V2REF')[row] - v3ref = ftab[1].data.field('V3REF')[row] - else: - # Read V2REF/V3REF from offset table (OFFTAB) - if offtab: - v2ref,v3ref,theta = readOfftab(offtab, date, chip=detchip) - else: - v2ref = 0.0 - v3ref = 0.0 - - if theta == None: - if 'THETA' in colnames: - theta = ftab[1].data.field('THETA')[row] - else: - theta = 0.0 - - refpix = {} - refpix['XREF'] = ftab[1].data.field('XREF')[row] - refpix['YREF'] = ftab[1].data.field('YREF')[row] - refpix['XSIZE'] = ftab[1].data.field('XSIZE')[row] - refpix['YSIZE'] = ftab[1].data.field('YSIZE')[row] - refpix['PSCALE'] = round(ftab[1].data.field('SCALE')[row],8) - refpix['V2REF'] = v2ref - refpix['V3REF'] = v3ref - refpix['THETA'] = theta - refpix['XDELTA'] = 0.0 - refpix['YDELTA'] = 0.0 - refpix['DEFAULT_SCALE'] = yes - refpix['centered'] = no - refpix['skew_coeffs'] = skew_coeffs - # Now that we know which row to look at, read coefficients into the - # numeric arrays we have set up... - # Setup which column name convention the IDCTAB follows - # either: A,B or CX,CY - if 'CX10' in ftab[1].data.names: - cxstr = 'CX' - cystr = 'CY' - else: - cxstr = 'A' - cystr = 'B' - - for i in range(norder+1): - if i > 0: - for j in range(i+1): - xcname = cxstr+str(i)+str(j) - ycname = cystr+str(i)+str(j) - fx[i,j] = ftab[1].data.field(xcname)[row] - fy[i,j] = ftab[1].data.field(ycname)[row] - - ftab.close() - del ftab - - # If CX11 is 1.0 and not equal to the PSCALE, then the - # coeffs need to be scaled - - if fx[1,1] == 1.0 and abs(fx[1,1]) != refpix['PSCALE']: - fx *= refpix['PSCALE'] - fy *= refpix['PSCALE'] - - # Return arrays and polynomial order read in from table. - # NOTE: XREF and YREF are stored in Fx,Fy arrays respectively. - return fx,fy,refpix,order -# -# -# Time-dependent skew correction coefficients (only ACS/WFC) -# -# -def read_tdd_coeffs(phdr, chip=1): - ''' Read in the TDD related keywords from the PRIMARY header of the IDCTAB - ''' - # Insure we have an integer form of chip - ic = int(chip) - - skew_coeffs = {} - skew_coeffs['TDDORDER'] = 0 - skew_coeffs['TDD_DATE'] = "" - skew_coeffs['TDD_A'] = None - skew_coeffs['TDD_B'] = None - skew_coeffs['TDD_CY_BETA'] = None - skew_coeffs['TDD_CY_ALPHA'] = None - skew_coeffs['TDD_CX_BETA'] = None - skew_coeffs['TDD_CX_ALPHA'] = None - - # Skew-based TDD coefficients - skew_terms = ['TDD_CTB','TDD_CTA','TDD_CYA','TDD_CYB','TDD_CXA','TDD_CXB'] - for s in skew_terms: - skew_coeffs[s] = None - - if "TDD_CTB1" in phdr: - # We have the 2015-calibrated TDD correction to apply - # This correction is based on correcting the skew in the linear terms - # not just set polynomial terms - print("Using 2015-calibrated VAFACTOR-corrected TDD correction...") - skew_coeffs['TDD_DATE'] = phdr['TDD_DATE'] - for s in skew_terms: - skew_coeffs[s] = phdr.get('{0}{1}'.format(s,ic),None) - - elif "TDD_CYB1" in phdr: - # We have 2014-calibrated TDD correction to apply, not J.A.-derived values - print("Using 2014-calibrated TDD correction...") - skew_coeffs['TDD_DATE'] = phdr['TDD_DATE'] - # Read coefficients for TDD Y coefficient - cyb_kw = 'TDD_CYB{0}'.format(int(chip)) - skew_coeffs['TDD_CY_BETA'] = phdr.get(cyb_kw,None) - cya_kw = 'TDD_CYA{0}'.format(int(chip)) - tdd_cya = phdr.get(cya_kw,None) - if tdd_cya == 0 or tdd_cya == 'N/A': tdd_cya = None - skew_coeffs['TDD_CY_ALPHA'] = tdd_cya - - # Read coefficients for TDD X coefficient - cxb_kw = 'TDD_CXB{0}'.format(int(chip)) - skew_coeffs['TDD_CX_BETA'] = phdr.get(cxb_kw,None) - cxa_kw = 'TDD_CXA{0}'.format(int(chip)) - tdd_cxa = phdr.get(cxa_kw,None) - if tdd_cxa == 0 or tdd_cxa == 'N/A': tdd_cxa = None - skew_coeffs['TDD_CX_ALPHA'] = tdd_cxa - - else: - if "TDDORDER" in phdr: - n = int(phdr["TDDORDER"]) - else: - print('TDDORDER kw not present, using default TDD correction') - return None - - a = np.zeros((n+1,), np.float64) - b = np.zeros((n+1,), np.float64) - for i in range(n+1): - a[i] = phdr.get(("TDD_A%d" % i), 0.0) - b[i] = phdr.get(("TDD_B%d" % i), 0.0) - if (a==0).all() and (b==0).all(): - print('Warning: TDD_A and TDD_B coeffiecients have values of 0, \n \ - but TDDORDER is %d.' % TDDORDER) - - skew_coeffs['TDDORDER'] = n - skew_coeffs['TDD_DATE'] = phdr['TDD_DATE'] - skew_coeffs['TDD_A'] = a - skew_coeffs['TDD_B'] = b - - return skew_coeffs - -def readOfftab(offtab, date, chip=None): - - -#Read V2REF,V3REF from a specified offset table (OFFTAB). -# Return a default geometry model if no IDCTAB filenam e -# is given. This model will not distort the data in any way. - - if offtab == None: - return 0.,0. - - # Provide a default value for chip - if chip: - detchip = chip - else: - detchip = 1 - - # Open up IDC table now... - try: - ftab = fileutil.openImage(offtab) - except: - raise IOError("Offset table '%s' not valid as specified!" % offtab) - - #Determine row from which to get the coefficients. - # How many rows do we have in the table... - fshape = ftab[1].data.shape - colnames = ftab[1].data.names - row = -1 - - row_start = None - row_end = None - - v2end = None - v3end = None - date_end = None - theta_end = None - - num_date = convertDate(date) - # Loop over all the rows looking for the one which corresponds - # to the value of CCDCHIP we are working on... - for ri in range(fshape[0]): - i = fshape[0] - ri - 1 - if 'DETCHIP' in colnames: - detchip = ftab[1].data.field('DETCHIP')[i] - else: - detchip = 1 - - obsdate = convertDate(ftab[1].data.field('OBSDATE')[i]) - - # If the row is appropriate for the chip... - # Interpolate between dates - if int(detchip) == int(chip) or int(detchip) == -999: - if num_date <= obsdate: - date_end = obsdate - v2end = ftab[1].data.field('V2REF')[i] - v3end = ftab[1].data.field('V3REF')[i] - theta_end = ftab[1].data.field('THETA')[i] - row_end = i - continue - - if row_end == None and (num_date > obsdate): - date_end = obsdate - v2end = ftab[1].data.field('V2REF')[i] - v3end = ftab[1].data.field('V3REF')[i] - theta_end = ftab[1].data.field('THETA')[i] - row_end = i - continue - - if num_date > obsdate: - date_start = obsdate - v2start = ftab[1].data.field('V2REF')[i] - v3start = ftab[1].data.field('V3REF')[i] - theta_start = ftab[1].data.field('THETA')[i] - row_start = i - break - - ftab.close() - del ftab - - if row_start == None and row_end == None: - print('Row corresponding to DETCHIP of ',detchip,' was not found!') - raise LookupError - elif row_start == None: - print('- OFFTAB: Offset defined by row',str(row_end+1)) - else: - print('- OFFTAB: Offset interpolated from rows',str(row_start+1),'and',str(row_end+1)) - - # Now, do the interpolation for v2ref, v3ref, and theta - if row_start == None or row_end == row_start: - # We are processing an observation taken after the last calibration - date_start = date_end - v2start = v2end - v3start = v3end - _fraction = 0. - theta_start = theta_end - else: - _fraction = float((num_date - date_start)) / float((date_end - date_start)) - - v2ref = _fraction * (v2end - v2start) + v2start - v3ref = _fraction * (v3end - v3start) + v3start - theta = _fraction * (theta_end - theta_start) + theta_start - - return v2ref,v3ref,theta - -def readWCSCoeffs(header): - - #Read distortion coeffs from WCS header keywords and - #populate distortion coeffs arrays. - - # Read in order for polynomials - _xorder = header['a_order'] - _yorder = header['b_order'] - order = max(max(_xorder,_yorder),3) - - fx = np.zeros(shape=(order+1,order+1),dtype=np.float64) - fy = np.zeros(shape=(order+1,order+1),dtype=np.float64) - - # Read in CD matrix - _cd11 = header['cd1_1'] - _cd12 = header['cd1_2'] - _cd21 = header['cd2_1'] - _cd22 = header['cd2_2'] - _cdmat = np.array([[_cd11,_cd12],[_cd21,_cd22]]) - _theta = np.arctan2(-_cd12,_cd22) - _rotmat = np.array([[np.cos(_theta),np.sin(_theta)], - [-np.sin(_theta),np.cos(_theta)]]) - _rCD = np.dot(_rotmat,_cdmat) - _skew = np.arcsin(-_rCD[1][0] / _rCD[0][0]) - _scale = _rCD[0][0] * np.cos(_skew) * 3600. - _scale2 = _rCD[1][1] * 3600. - - # Set up refpix - refpix = {} - refpix['XREF'] = header['crpix1'] - refpix['YREF'] = header['crpix2'] - refpix['XSIZE'] = header['naxis1'] - refpix['YSIZE'] = header['naxis2'] - refpix['PSCALE'] = _scale - refpix['V2REF'] = 0. - refpix['V3REF'] = 0. - refpix['THETA'] = np.rad2deg(_theta) - refpix['XDELTA'] = 0.0 - refpix['YDELTA'] = 0.0 - refpix['DEFAULT_SCALE'] = yes - refpix['centered'] = yes - - - # Set up template for coeffs keyword names - cxstr = 'A_' - cystr = 'B_' - # Read coeffs into their own matrix - for i in range(_xorder+1): - for j in range(i+1): - xcname = cxstr+str(j)+'_'+str(i-j) - if xcname in header: - fx[i,j] = header[xcname] - - # Extract Y coeffs separately as a different order may - # have been used to fit it. - for i in range(_yorder+1): - for j in range(i+1): - ycname = cystr+str(j)+'_'+str(i-j) - if ycname in header: - fy[i,j] = header[ycname] - - # Now set the linear terms - fx[0][0] = 1.0 - fy[0][0] = 1.0 - - return fx,fy,refpix,order - - -def readTraugerTable(idcfile,wavelength): - - # Return a default geometry model if no coefficients filename - # is given. This model will not distort the data in any way. - if idcfile == None: - return fileutil.defaultModel() - - # Trauger coefficients only result in a cubic file... - order = 3 - numco = 10 - a_coeffs = [0] * numco - b_coeffs = [0] * numco - indx = _MgF2(wavelength) - - ifile = open(idcfile,'r') - # Search for the first line of the coefficients - _line = fileutil.rAsciiLine(ifile) - while _line[:7].lower() != 'trauger': - _line = fileutil.rAsciiLine(ifile) - # Read in each row of coefficients,split them into their values, - # and convert them into cubic coefficients based on - # index of refraction value for the given wavelength - # Build X coefficients from first 10 rows of Trauger coefficients - j = 0 - while j < 20: - _line = fileutil.rAsciiLine(ifile) - if _line == '': continue - _lc = _line.split() - if j < 10: - a_coeffs[j] = float(_lc[0])+float(_lc[1])*(indx-1.5)+float(_lc[2])*(indx-1.5)**2 - else: - b_coeffs[j-10] = float(_lc[0])+float(_lc[1])*(indx-1.5)+float(_lc[2])*(indx-1.5)**2 - j = j + 1 - - ifile.close() - del ifile - - # Now, convert the coefficients into a Numeric array - # with the right coefficients in the right place. - # Populate output values now... - fx = np.zeros(shape=(order+1,order+1),dtype=np.float64) - fy = np.zeros(shape=(order+1,order+1),dtype=np.float64) - # Assign the coefficients to their array positions - fx[0,0] = 0. - fx[1] = np.array([a_coeffs[2],a_coeffs[1],0.,0.],dtype=np.float64) - fx[2] = np.array([a_coeffs[5],a_coeffs[4],a_coeffs[3],0.],dtype=np.float64) - fx[3] = np.array([a_coeffs[9],a_coeffs[8],a_coeffs[7],a_coeffs[6]],dtype=np.float64) - fy[0,0] = 0. - fy[1] = np.array([b_coeffs[2],b_coeffs[1],0.,0.],dtype=np.float64) - fy[2] = np.array([b_coeffs[5],b_coeffs[4],b_coeffs[3],0.],dtype=np.float64) - fy[3] = np.array([b_coeffs[9],b_coeffs[8],b_coeffs[7],b_coeffs[6]],dtype=np.float64) - - # Used in Pattern.computeOffsets() - refpix = {} - refpix['XREF'] = None - refpix['YREF'] = None - refpix['V2REF'] = None - refpix['V3REF'] = None - refpix['XDELTA'] = 0. - refpix['YDELTA'] = 0. - refpix['PSCALE'] = None - refpix['DEFAULT_SCALE'] = no - refpix['centered'] = yes - - return fx,fy,refpix,order - - -def readCubicTable(idcfile): - # Assumption: this will only be used for cubic file... - order = 3 - # Also, this function does NOT perform any scaling on - # the coefficients, it simply passes along what is found - # in the file as is... - - # Return a default geometry model if no coefficients filename - # is given. This model will not distort the data in any way. - if idcfile == None: - return fileutil.defaultModel() - - ifile = open(idcfile,'r') - # Search for the first line of the coefficients - _line = fileutil.rAsciiLine(ifile) - - _found = no - while _found == no: - if _line[:7] in ['cubic','quartic','quintic'] or _line[:4] == 'poly': - found = yes - break - _line = fileutil.rAsciiLine(ifile) - - # Read in each row of coefficients, without line breaks or newlines - # split them into their values, and create a list for A coefficients - # and another list for the B coefficients - _line = fileutil.rAsciiLine(ifile) - a_coeffs = _line.split() - - x0 = float(a_coeffs[0]) - _line = fileutil.rAsciiLine(ifile) - a_coeffs[len(a_coeffs):] = _line.split() - # Scale coefficients for use within PyDrizzle - for i in range(len(a_coeffs)): - a_coeffs[i] = float(a_coeffs[i]) - - _line = fileutil.rAsciiLine(ifile) - b_coeffs = _line.split() - y0 = float(b_coeffs[0]) - _line = fileutil.rAsciiLine(ifile) - b_coeffs[len(b_coeffs):] = _line.split() - # Scale coefficients for use within PyDrizzle - for i in range(len(b_coeffs)): - b_coeffs[i] = float(b_coeffs[i]) - - ifile.close() - del ifile - # Now, convert the coefficients into a Numeric array - # with the right coefficients in the right place. - # Populate output values now... - fx = np.zeros(shape=(order+1,order+1),dtype=np.float64) - fy = np.zeros(shape=(order+1,order+1),dtype=np.float64) - # Assign the coefficients to their array positions - fx[0,0] = 0. - fx[1] = np.array([a_coeffs[2],a_coeffs[1],0.,0.],dtype=np.float64) - fx[2] = np.array([a_coeffs[5],a_coeffs[4],a_coeffs[3],0.],dtype=np.float64) - fx[3] = np.array([a_coeffs[9],a_coeffs[8],a_coeffs[7],a_coeffs[6]],dtype=np.float64) - fy[0,0] = 0. - fy[1] = np.array([b_coeffs[2],b_coeffs[1],0.,0.],dtype=np.float64) - fy[2] = np.array([b_coeffs[5],b_coeffs[4],b_coeffs[3],0.],dtype=np.float64) - fy[3] = np.array([b_coeffs[9],b_coeffs[8],b_coeffs[7],b_coeffs[6]],dtype=np.float64) - - # Used in Pattern.computeOffsets() - refpix = {} - refpix['XREF'] = None - refpix['YREF'] = None - refpix['V2REF'] = x0 - refpix['V3REF'] = y0 - refpix['XDELTA'] = 0. - refpix['YDELTA'] = 0. - refpix['PSCALE'] = None - refpix['DEFAULT_SCALE'] = no - refpix['centered'] = yes - - return fx,fy,refpix,order - -def factorial(n): - """ Compute a factorial for integer n. """ - m = 1 - for i in range(int(n)): - m = m * (i+1) - return m - -def combin(j,n): - """ Return the combinatorial factor for j in n.""" - return (factorial(j) / (factorial(n) * factorial( (j-n) ) ) ) - - -def defaultModel(): - """ This function returns a default, non-distorting model - that can be used with the data. - """ - order = 3 - - fx = np.zeros(shape=(order+1,order+1),dtype=np.float64) - fy = np.zeros(shape=(order+1,order+1),dtype=np.float64) - - fx[1,1] = 1. - fy[1,0] = 1. - - # Used in Pattern.computeOffsets() - refpix = {} - refpix['empty_model'] = yes - refpix['XREF'] = None - refpix['YREF'] = None - refpix['V2REF'] = 0. - refpix['XSIZE'] = 0. - refpix['YSIZE'] = 0. - refpix['V3REF'] = 0. - refpix['XDELTA'] = 0. - refpix['YDELTA'] = 0. - refpix['PSCALE'] = None - refpix['DEFAULT_SCALE'] = no - refpix['THETA'] = 0. - refpix['centered'] = yes - return fx,fy,refpix,order - -# Function to compute the index of refraction for MgF2 at -# the specified wavelength for use with Trauger coefficients -def _MgF2(lam): - _sig = pow((1.0e7/lam),2) - return np.sqrt(1.0 + 2.590355e10/(5.312993e10-_sig) + - 4.4543708e9/(11.17083e9-_sig) + 4.0838897e5/(1.766361e5-_sig)) - - -def convertDate(date): - """ Converts the DATE-OBS date string into an integer of the - number of seconds since 1970.0 using calendar.timegm(). - - INPUT: DATE-OBS in format of 'YYYY-MM-DD'. - OUTPUT: Date (integer) in seconds. - """ - - _dates = date.split('-') - _val = 0 - _date_tuple = (int(_dates[0]), int(_dates[1]), int(_dates[2]), 0, 0, 0, 0, 0, 0) - - return calendar.timegm(_date_tuple) diff --git a/lib/stwcs/distortion/utils.py b/lib/stwcs/distortion/utils.py deleted file mode 100644 index 4228f62..0000000 --- a/lib/stwcs/distortion/utils.py +++ /dev/null @@ -1,270 +0,0 @@ -from __future__ import division, print_function # confidence high - -import os - -import numpy as np -from numpy import linalg -from astropy import wcs as pywcs - -from stwcs import wcsutil -from stwcs import updatewcs -from numpy import sqrt, arctan2 -from stsci.tools import fileutil - -def output_wcs(list_of_wcsobj, ref_wcs=None, owcs=None, undistort=True): - """ - Create an output WCS. - - Parameters - ---------- - list_of_wcsobj: Python list - a list of HSTWCS objects - ref_wcs: an HSTWCS object - to be used as a reference WCS, in case outwcs is None. - if ref_wcs is None (default), the first member of the list - is used as a reference - outwcs: an HSTWCS object - the tangent plane defined by this object is used as a reference - undistort: boolean (default-True) - a flag whether to create an undistorted output WCS - """ - fra_dec = np.vstack([w.calc_footprint() for w in list_of_wcsobj]) - wcsname = list_of_wcsobj[0].wcs.name - - # This new algorithm may not be strictly necessary, but it may be more - # robust in handling regions near the poles or at 0h RA. - crval1,crval2 = computeFootprintCenter(fra_dec) - - crval = np.array([crval1,crval2], dtype=np.float64) # this value is now zero-based - if owcs is None: - if ref_wcs is None: - ref_wcs = list_of_wcsobj[0].deepcopy() - if undistort: - #outwcs = undistortWCS(ref_wcs) - outwcs = make_orthogonal_cd(ref_wcs) - else: - outwcs = ref_wcs.deepcopy() - outwcs.wcs.crval = crval - outwcs.wcs.set() - outwcs.pscale = sqrt(outwcs.wcs.cd[0,0]**2 + outwcs.wcs.cd[1,0]**2)*3600. - outwcs.orientat = arctan2(outwcs.wcs.cd[0,1],outwcs.wcs.cd[1,1]) * 180./np.pi - else: - outwcs = owcs.deepcopy() - outwcs.pscale = sqrt(outwcs.wcs.cd[0,0]**2 + outwcs.wcs.cd[1,0]**2)*3600. - outwcs.orientat = arctan2(outwcs.wcs.cd[0,1],outwcs.wcs.cd[1,1]) * 180./np.pi - - tanpix = outwcs.wcs.s2p(fra_dec, 0)['pixcrd'] - - outwcs._naxis1 = int(np.ceil(tanpix[:,0].max() - tanpix[:,0].min())) - outwcs._naxis2 = int(np.ceil(tanpix[:,1].max() - tanpix[:,1].min())) - crpix = np.array([outwcs._naxis1/2., outwcs._naxis2/2.], dtype=np.float64) - outwcs.wcs.crpix = crpix - outwcs.wcs.set() - tanpix = outwcs.wcs.s2p(fra_dec, 0)['pixcrd'] - - # shift crpix to take into account (floating-point value of) position of - # corner pixel relative to output frame size: no rounding necessary... - newcrpix = np.array([crpix[0]+tanpix[:,0].min(), crpix[1]+ - tanpix[:,1].min()]) - - newcrval = outwcs.wcs.p2s([newcrpix], 1)['world'][0] - outwcs.wcs.crval = newcrval - outwcs.wcs.set() - outwcs.wcs.name = wcsname # keep track of label for this solution - return outwcs - -def computeFootprintCenter(edges): - """ Geographic midpoint in spherical coords for points defined by footprints. - Algorithm derived from: http://www.geomidpoint.com/calculation.html - - This algorithm should be more robust against discontinuities at the poles. - """ - alpha = np.deg2rad(edges[:,0]) - dec = np.deg2rad(edges[:,1]) - - xmean = np.mean(np.cos(dec)*np.cos(alpha)) - ymean = np.mean(np.cos(dec)*np.sin(alpha)) - zmean = np.mean(np.sin(dec)) - - crval1 = np.rad2deg(np.arctan2(ymean,xmean))%360.0 - crval2 = np.rad2deg(np.arctan2(zmean,np.sqrt(xmean*xmean+ymean*ymean))) - - return crval1,crval2 - -def make_orthogonal_cd(wcs): - """ Create a perfect (square, orthogonal, undistorted) CD matrix from the - input WCS. - """ - # get determinant of the CD matrix: - cd = wcs.celestial.pixel_scale_matrix - - - if hasattr(wcs, 'idcv2ref') and wcs.idcv2ref is not None: - # Convert the PA_V3 orientation to the orientation at the aperture - # This is for the reference chip only - we use this for the - # reference tangent plane definition - # It has the same orientation as the reference chip - pv = updatewcs.makewcs.troll(wcs.pav3,wcs.wcs.crval[1],wcs.idcv2ref,wcs.idcv3ref) - # Add the chip rotation angle - if wcs.idctheta: - pv += wcs.idctheta - cs = np.cos(np.deg2rad(pv)) - sn = np.sin(np.deg2rad(pv)) - pvmat = np.dot(np.array([[cs,sn],[-sn,cs]]),wcs.parity) - rot = np.arctan2(pvmat[0,1],pvmat[1,1]) - scale = wcs.idcscale/3600. - - det = linalg.det(wcs.parity) - - else: - - det = linalg.det(cd) - - # find pixel scale: - if hasattr(wcs, 'idcscale'): - scale = (wcs.idcscale) / 3600. # HST pixel scale provided - else: - scale = np.sqrt(np.abs(det)) # find as sqrt(pixel area) - - # find Y-axis orientation: - if hasattr(wcs, 'orientat') and not ignoreHST: - rot = np.deg2rad(wcs.orientat) # use HST ORIENTAT - else: - rot = np.arctan2(wcs.wcs.cd[0,1], wcs.wcs.cd[1,1]) # angle of the Y-axis - - par = -1 if det < 0.0 else 1 - - # create a perfectly square, orthogonal WCS - sn = np.sin(rot) - cs = np.cos(rot) - orthogonal_cd = scale * np.array([[par*cs, sn], [-par*sn, cs]]) - - lin_wcsobj = pywcs.WCS() - lin_wcsobj.wcs.cd = orthogonal_cd - lin_wcsobj.wcs.set() - lin_wcsobj.orientat = arctan2(lin_wcsobj.wcs.cd[0,1],lin_wcsobj.wcs.cd[1,1]) * 180./np.pi - lin_wcsobj.pscale = sqrt(lin_wcsobj.wcs.cd[0,0]**2 + lin_wcsobj.wcs.cd[1,0]**2)*3600. - lin_wcsobj.wcs.crval = np.array([0.,0.]) - lin_wcsobj.wcs.crpix = np.array([0.,0.]) - lin_wcsobj.wcs.ctype = ['RA---TAN', 'DEC--TAN'] - lin_wcsobj.wcs.set() - - return lin_wcsobj - -def undistortWCS(wcsobj): - """ - Creates an undistorted linear WCS by applying the IDCTAB distortion model - to a 3-point square. The new ORIENTAT angle is calculated as well as the - plate scale in the undistorted frame. - """ - assert isinstance(wcsobj, pywcs.WCS) - from . import coeff_converter - - cx, cy = coeff_converter.sip2idc(wcsobj) - # cx, cy can be None because either there is no model available - # or updatewcs was not run. - if cx is None or cy is None: - if foundIDCTAB(wcsobj.idctab): - m = """IDCTAB is present but distortion model is missing. - Run updatewcs() to update the headers or - pass 'undistort=False' keyword to output_wcs().\n - """ - raise RuntimeError(m) - else: - print('Distortion model is not available, using input reference image for output WCS.\n') - return wcsobj.copy() - crpix1 = wcsobj.wcs.crpix[0] - crpix2 = wcsobj.wcs.crpix[1] - xy = np.array([(crpix1,crpix2),(crpix1+1.,crpix2),(crpix1,crpix2+1.)],dtype=np.double) - offsets = np.array([wcsobj.ltv1, wcsobj.ltv2]) - px = xy + offsets - #order = wcsobj.sip.a_order - pscale = wcsobj.idcscale - #pixref = np.array([wcsobj.sip.SIPREF1, wcsobj.sip.SIPREF2]) - - tan_pix = apply_idc(px, cx, cy, wcsobj.wcs.crpix, pscale, order=1) - xc = tan_pix[:,0] - yc = tan_pix[:,1] - am = xc[1] - xc[0] - bm = xc[2] - xc[0] - cm = yc[1] - yc[0] - dm = yc[2] - yc[0] - cd_mat = np.array([[am,bm],[cm,dm]],dtype=np.double) - - # Check the determinant for singularity - _det = (am * dm) - (bm * cm) - if ( _det == 0.0): - print('Singular matrix in updateWCS, aborting ...') - return - - lin_wcsobj = pywcs.WCS() - cd_inv = np.linalg.inv(cd_mat) - cd = np.dot(wcsobj.wcs.cd, cd_inv).astype(np.float64) - lin_wcsobj.wcs.cd = cd - lin_wcsobj.wcs.set() - lin_wcsobj.orientat = arctan2(lin_wcsobj.wcs.cd[0,1],lin_wcsobj.wcs.cd[1,1]) * 180./np.pi - lin_wcsobj.pscale = sqrt(lin_wcsobj.wcs.cd[0,0]**2 + lin_wcsobj.wcs.cd[1,0]**2)*3600. - lin_wcsobj.wcs.crval = np.array([0.,0.]) - lin_wcsobj.wcs.crpix = np.array([0.,0.]) - lin_wcsobj.wcs.ctype = ['RA---TAN', 'DEC--TAN'] - lin_wcsobj.wcs.set() - return lin_wcsobj - -def apply_idc(pixpos, cx, cy, pixref, pscale= None, order=None): - """ - Apply the IDCTAB polynomial distortion model to pixel positions. - pixpos must be already corrected for ltv1/2. - - Parameters - ---------- - pixpos: a 2D numpy array of (x,y) pixel positions to be distortion corrected - cx, cy: IDC model distortion coefficients - pixref: reference opixel position - - """ - if cx is None: - return pixpos - - if order is None: - print('Unknown order of distortion model \n') - return pixpos - if pscale is None: - print('Unknown model plate scale\n') - return pixpos - - # Apply in the same way that 'drizzle' would... - _cx = cx/pscale - _cy = cy/ pscale - _p = pixpos - - # Do NOT include any zero-point terms in CX,CY here - # as they should not be scaled by plate-scale like rest - # of coeffs... This makes the computations consistent - # with 'drizzle'. WJH 17-Feb-2004 - _cx[0,0] = 0. - _cy[0,0] = 0. - - dxy = _p - pixref - # Apply coefficients from distortion model here... - - c = _p * 0. - for i in range(order+1): - for j in range(i+1): - c[:,0] = c[:,0] + _cx[i][j] * pow(dxy[:,0],j) * pow(dxy[:,1],(i-j)) - c[:,1] = c[:,1] + _cy[i][j] * pow(dxy[:,0],j) * pow(dxy[:,1],(i-j)) - - return c - -def foundIDCTAB(idctab): - idctab_found = True - try: - idctab = fileutil.osfn(idctab) - if idctab == 'N/A' or idctab == "": - idctab_found = False - if os.path.exists(idctab): - idctab_found = True - else: - idctab_found = False - except KeyError: - idctab_found = False - return idctab_found |