From 4b65b226085ccb9e665a5866023d8114f7438188 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Thu, 4 Aug 2016 17:42:01 -0400 Subject: restructure and add stwcs tests --- lib/stwcs/wcsutil/hstwcs.py | 988 -------------------------------------------- 1 file changed, 988 deletions(-) delete mode 100644 lib/stwcs/wcsutil/hstwcs.py (limited to 'lib/stwcs/wcsutil/hstwcs.py') diff --git a/lib/stwcs/wcsutil/hstwcs.py b/lib/stwcs/wcsutil/hstwcs.py deleted file mode 100644 index bfebcfc..0000000 --- a/lib/stwcs/wcsutil/hstwcs.py +++ /dev/null @@ -1,988 +0,0 @@ -from __future__ import absolute_import, division, print_function # confidence high - -import os -from astropy.wcs import WCS -from astropy.io import fits -from stwcs.distortion import models, coeff_converter -import numpy as np -from stsci.tools import fileutil - -from . import altwcs -from . import getinput -from . import mappings -from . import instruments -from .mappings import inst_mappings, ins_spec_kw -from .mappings import basic_wcs - -__docformat__ = 'restructuredtext' - -# -#### Utility functions copied from 'updatewcs.utils' to avoid circular imports -# -def extract_rootname(kwvalue,suffix=""): - """ Returns the rootname from a full reference filename - - If a non-valid value (any of ['','N/A','NONE','INDEF',None]) is input, - simply return a string value of 'NONE' - - This function will also replace any 'suffix' specified with a blank. - """ - # check to see whether a valid kwvalue has been provided as input - if kwvalue.strip() in ['','N/A','NONE','INDEF',None]: - return 'NONE' # no valid value, so return 'NONE' - - # for a valid kwvalue, parse out the rootname - # strip off any environment variable from input filename, if any are given - if '$' in kwvalue: - fullval = kwvalue[kwvalue.find('$')+1:] - else: - fullval = kwvalue - # Extract filename without path from kwvalue - fname = os.path.basename(fullval).strip() - - # Now, rip out just the rootname from the full filename - rootname = fileutil.buildNewRootname(fname) - - # Now, remove any known suffix from rootname - rootname = rootname.replace(suffix,'') - return rootname.strip() - -def build_default_wcsname(idctab): - - idcname = extract_rootname(idctab,suffix='_idc') - wcsname = 'IDC_' + idcname - return wcsname - - -class NoConvergence(Exception): - """ - An error class used to report non-convergence and/or divergence of - numerical methods. It is used to report errors in the iterative solution - used by the :py:meth:`~stwcs.hstwcs.HSTWCS.all_world2pix`\ . - - Attributes - ---------- - - best_solution : numpy.array - Best solution achieved by the method. - - accuracy : float - Accuracy of the :py:attr:`best_solution`\ . - - niter : int - Number of iterations performed by the numerical method to compute - :py:attr:`best_solution`\ . - - divergent : None, numpy.array - Indices of the points in :py:attr:`best_solution` array for which the - solution appears to be divergent. If the solution does not diverge, - `divergent` will be set to `None`. - - failed2converge : None, numpy.array - Indices of the points in :py:attr:`best_solution` array for which the - solution failed to converge within the specified maximum number - of iterations. If there are no non-converging poits (i.e., if - the required accuracy has been achieved for all points) then - `failed2converge` will be set to `None`. - - """ - def __init__(self, *args, **kwargs): - super(NoConvergence, self).__init__(*args) - - self.best_solution = kwargs.pop('best_solution', None) - self.accuracy = kwargs.pop('accuracy', None) - self.niter = kwargs.pop('niter', None) - self.divergent = kwargs.pop('divergent', None) - self.failed2converge= kwargs.pop('failed2converge', None) - - -# -#### HSTWCS Class definition -# -class HSTWCS(WCS): - - def __init__(self, fobj=None, ext=None, minerr=0.0, wcskey=" "): - """ - Create a WCS object based on the instrument. - - In addition to basic WCS keywords this class provides - instrument specific information needed in distortion computation. - - Parameters - ---------- - fobj : str or `astropy.io.fits.HDUList` object or None - file name, e.g j9irw4b1q_flt.fits - fully qualified filename[EXTNAME,EXTNUM], e.g. j9irw4b1q_flt.fits[sci,1] - `astropy.io.fits` file object, e.g fits.open('j9irw4b1q_flt.fits'), in which case the - user is responsible for closing the file object. - ext : int, tuple or None - extension number - if ext is tuple, it must be ("EXTNAME", EXTNUM), e.g. ("SCI", 2) - if ext is None, it is assumed the data is in the primary hdu - minerr : float - minimum value a distortion correction must have in order to be applied. - If CPERRja, CQERRja are smaller than minerr, the corersponding - distortion is not applied. - wcskey : str - A one character A-Z or " " used to retrieve and define an - alternate WCS description. - """ - - self.inst_kw = ins_spec_kw - self.minerr = minerr - self.wcskey = wcskey - - if fobj is not None: - filename, hdr0, ehdr, phdu = getinput.parseSingleInput(f=fobj, - ext=ext) - self.filename = filename - instrument_name = hdr0.get('INSTRUME', 'DEFAULT') - if instrument_name == 'DEFAULT' or instrument_name not in list(inst_mappings.keys()): - #['IRAF/ARTDATA','',' ','N/A']: - self.instrument = 'DEFAULT' - else: - self.instrument = instrument_name - # Set the correct reference frame - refframe = determine_refframe(hdr0) - ehdr['RADESYS'] = refframe - - WCS.__init__(self, ehdr, fobj=phdu, minerr=self.minerr, - key=self.wcskey) - if self.instrument == 'DEFAULT': - self.pc2cd() - # If input was a `astropy.io.fits.HDUList` object, it's the user's - # responsibility to close it, otherwise, it's closed here. - if not isinstance(fobj, fits.HDUList): - phdu.close() - self.setInstrSpecKw(hdr0, ehdr) - self.readIDCCoeffs(ehdr) - extname = ehdr.get('EXTNAME', '') - extnum = ehdr.get('EXTVER', None) - self.extname = (extname, extnum) - else: - # create a default HSTWCS object - self.instrument = 'DEFAULT' - WCS.__init__(self, minerr=self.minerr, key=self.wcskey) - self.pc2cd() - self.setInstrSpecKw() - self.setPscale() - self.setOrient() - - @property - def naxis1(self): - return self._naxis1 - - @naxis1.setter - def naxis1(self, value): - self._naxis1 = value - - @property - def naxis2(self): - return self._naxis2 - - @naxis2.setter - def naxis2(self, value): - self._naxis2 = value - - def readIDCCoeffs(self, header): - """ - Reads in first order IDCTAB coefficients if present in the header - """ - coeffs = ['ocx10', 'ocx11', 'ocy10', 'ocy11', 'idcscale', - 'idcv2ref','idcv3ref', 'idctheta'] - for c in coeffs: - self.__setattr__(c, header.get(c, None)) - - def setInstrSpecKw(self, prim_hdr=None, ext_hdr=None): - """ - Populate the instrument specific attributes: - - These can be in different headers but each instrument class has knowledge - of where to look for them. - - Parameters - ---------- - prim_hdr : `astropy.io.fits.Header` - primary header - ext_hdr : `astropy.io.fits.Header` - extension header - - """ - if self.instrument in list(inst_mappings.keys()): - inst_kl = inst_mappings[self.instrument] - inst_kl = instruments.__dict__[inst_kl] - insobj = inst_kl(prim_hdr, ext_hdr) - - for key in self.inst_kw: - try: - self.__setattr__(key, insobj.__getattribute__(key)) - except AttributeError: - # Some of the instrument's attributes are recorded in the primary header and - # were already set, (e.g. 'DETECTOR'), the code below is a check for that case. - if not self.__getattribute__(key): - raise - else: - pass - - else: - raise KeyError("Unsupported instrument - %s" %self.instrument) - - def setPscale(self): - """ - Calculates the plate scale from the CD matrix - """ - try: - cd11 = self.wcs.cd[0][0] - cd21 = self.wcs.cd[1][0] - self.pscale = np.sqrt(np.power(cd11,2)+np.power(cd21,2)) * 3600. - except AttributeError: - if self.wcs.has_cd(): - print("This file has a PC matrix. You may want to convert it \n \ - to a CD matrix, if reasonable, by running pc2.cd() method.\n \ - The plate scale can be set then by calling setPscale() method.\n") - self.pscale = None - - def setOrient(self): - """ - Computes ORIENTAT from the CD matrix - """ - try: - cd12 = self.wcs.cd[0][1] - cd22 = self.wcs.cd[1][1] - self.orientat = np.rad2deg(np.arctan2(cd12,cd22)) - except AttributeError: - if self.wcs.has_cd(): - print("This file has a PC matrix. You may want to convert it \n \ - to a CD matrix, if reasonable, by running pc2.cd() method.\n \ - The orientation can be set then by calling setOrient() method.\n") - self.pscale = None - - def updatePscale(self, scale): - """ - Updates the CD matrix with a new plate scale - """ - self.wcs.cd = self.wcs.cd/self.pscale*scale - self.setPscale() - - def readModel(self, update=False, header=None): - """ - Reads distortion model from IDCTAB. - - If IDCTAB is not found ('N/A', "", or not found on disk), then - if SIP coefficients and first order IDCTAB coefficients are present - in the header, restore the idcmodel from the header. - If not - assign None to self.idcmodel. - - Parameters - ---------- - header : `astropy.io.fits.Header` - fits extension header - update : bool (False) - if True - record the following IDCTAB quantities as header keywords: - CX10, CX11, CY10, CY11, IDCSCALE, IDCTHETA, IDCXREF, IDCYREF, - IDCV2REF, IDCV3REF - """ - if self.idctab in [None, '', ' ','N/A']: - #Keyword idctab is not present in header - check for sip coefficients - if header is not None and 'IDCSCALE' in header: - self._readModelFromHeader(header) - else: - print("Distortion model is not available: IDCTAB=None\n") - self.idcmodel = None - elif not os.path.exists(fileutil.osfn(self.idctab)): - if header is not None and 'IDCSCALE' in header: - self._readModelFromHeader(header) - else: - print('Distortion model is not available: IDCTAB file %s not found\n' % self.idctab) - self.idcmodel = None - else: - self.readModelFromIDCTAB(header=header, update=update) - - def _readModelFromHeader(self, header): - # Recreate idc model from SIP coefficients and header kw - print('Restoring IDC model from SIP coefficients\n') - model = models.GeometryModel() - cx, cy = coeff_converter.sip2idc(self) - model.cx = cx - model.cy = cy - model.name = "sip" - model.norder = header['A_ORDER'] - - refpix = {} - refpix['XREF'] = header['IDCXREF'] - refpix['YREF'] = header['IDCYREF'] - refpix['PSCALE'] = header['IDCSCALE'] - refpix['V2REF'] = header['IDCV2REF'] - refpix['V3REF'] = header['IDCV3REF'] - refpix['THETA'] = header['IDCTHETA'] - model.refpix = refpix - - self.idcmodel = model - - - def readModelFromIDCTAB(self, header=None, update=False): - """ - Read distortion model from idc table. - - Parameters - ---------- - header : `astropy.io.fits.Header` - fits extension header - update : booln (False) - if True - save teh following as header keywords: - CX10, CX11, CY10, CY11, IDCSCALE, IDCTHETA, IDCXREF, IDCYREF, - IDCV2REF, IDCV3REF - - """ - if self.date_obs == None: - print('date_obs not available\n') - self.idcmodel = None - return - if self.filter1 == None and self.filter2 == None: - 'No filter information available\n' - self.idcmodel = None - return - - self.idcmodel = models.IDCModel(self.idctab, - chip=self.chip, direction='forward', date=self.date_obs, - filter1=self.filter1, filter2=self.filter2, - offtab=self.offtab, binned=self.binned) - - if self.ltv1 != 0. or self.ltv2 != 0.: - self.resetLTV() - - if update: - if header==None: - print('Update header with IDC model kw requested but header was not provided\n.') - else: - self._updatehdr(header) - - def resetLTV(self): - """ - Reset LTV values for polarizer data - - The polarizer field is smaller than the detector field. - The distortion coefficients are defined for the entire - polarizer field and the LTV values are set as with subarray - data. This may also be true for other special filters. - This is a special case when the observation is considered - a subarray in terms of detector field but a full frame in - terms of distortion model. - To avoid shifting the distortion coefficients the LTV values - are reset to 0. - """ - if self.naxis1 == self.idcmodel.refpix['XSIZE'] and \ - self.naxis2 == self.idcmodel.refpix['YSIZE']: - self.ltv1 = 0. - self.ltv2 = 0. - - def wcs2header(self, sip2hdr=False, idc2hdr=True, wcskey=None, relax=False): - """ - Create a `astropy.io.fits.Header` object from WCS keywords. - - If the original header had a CD matrix, return a CD matrix, - otherwise return a PC matrix. - - Parameters - ---------- - sip2hdr : bool - If True - include SIP coefficients - """ - - h = self.to_header(key=wcskey, relax=relax) - if not wcskey: - wcskey = self.wcs.alt - if self.wcs.has_cd(): - h = altwcs.pc2cd(h, key=wcskey) - - if 'wcsname' not in h: - if self.idctab is not None: - wname = build_default_wcsname(self.idctab) - else: - wname = 'DEFAULT' - h['wcsname{0}'.format(wcskey)] = wname - - if idc2hdr: - for card in self._idc2hdr(): - h[card.keyword + wcskey] = (card.value, card.comment) - try: - del h['RESTFRQ'] - del h['RESTWAV'] - except KeyError: pass - - if sip2hdr and self.sip: - for card in self._sip2hdr('a'): - h[card.keyword] = (card.value, card.comment) - for card in self._sip2hdr('b'): - h[card.keyword] = (card.value, card.comment) - - try: - ap = self.sip.ap - except AssertionError: - ap = None - try: - bp = self.sip.bp - except AssertionError: - bp = None - - if ap: - for card in self._sip2hdr('ap'): - h[card.keyword] = (card.value, card.comment) - if bp: - for card in self._sip2hdr('bp'): - h[card.keyword] = (card.value, card.comment) - return h - - def _sip2hdr(self, k): - """ - Get a set of SIP coefficients in the form of an array - and turn them into a `astropy.io.fits.Cardlist`. - k - one of 'a', 'b', 'ap', 'bp' - """ - - cards = [] #fits.CardList() - korder = self.sip.__getattribute__(k+'_order') - cards.append(fits.Card(keyword=k.upper()+'_ORDER', value=korder)) - coeffs = self.sip.__getattribute__(k) - ind = coeffs.nonzero() - for i in range(len(ind[0])): - card = fits.Card(keyword=k.upper()+'_'+str(ind[0][i])+'_'+str(ind[1][i]), - value=coeffs[ind[0][i], ind[1][i]]) - cards.append(card) - return cards - - def _idc2hdr(self): - # save some of the idc coefficients - coeffs = ['ocx10', 'ocx11', 'ocy10', 'ocy11', 'idcscale'] - cards = [] #fits.CardList() - for c in coeffs: - try: - val = self.__getattribute__(c) - except AttributeError: - continue - if val: - cards.append(fits.Card(keyword=c, value=val)) - return cards - - def pc2cd(self): - if not self.wcs.has_pc(): - self.wcs.pc = self.wcs.get_pc() - self.wcs.cd = self.wcs.pc * self.wcs.cdelt[1] - - def all_world2pix(self, *args, **kwargs): - """ - all_world2pix(*arg, accuracy=1.0e-4, maxiter=20, adaptive=False, \ -detect_divergence=True, quiet=False) - - Performs full inverse transformation using iterative solution - on full forward transformation with complete distortion model. - - Parameters - ---------- - accuracy : float, optional (Default = 1.0e-4) - Required accuracy of the solution. Iteration terminates when the - correction to the solution found during the previous iteration - is smaller (in the sence of the L2 norm) than `accuracy`\ . - - maxiter : int, optional (Default = 20) - Maximum number of iterations allowed to reach the solution. - - adaptive : bool, optional (Default = False) - Specifies whether to adaptively select only points that did not - converge to a solution whithin the required accuracy for the - next iteration. Default is recommended for HST as well as most - other instruments. - - .. note:: - The :py:meth:`all_world2pix` uses a vectorized implementation - of the method of consecutive approximations (see `Notes` - section below) in which it iterates over *all* input poits - *regardless* until the required accuracy has been reached for - *all* input points. In some cases it may be possible that - *almost all* points have reached the required accuracy but - there are only a few of input data points left for which - additional iterations may be needed (this depends mostly on the - characteristics of the geometric distortions for a given - instrument). In this situation it may be - advantageous to set `adaptive` = `True`\ in which - case :py:meth:`all_world2pix` will continue iterating *only* over - the points that have not yet converged to the required - accuracy. However, for the HST's ACS/WFC detector, which has - the strongest distortions of all HST instruments, testing has - shown that enabling this option would lead to a about 10-30\% - penalty in computational time (depending on specifics of the - image, geometric distortions, and number of input points to be - converted). Therefore, for HST instruments, - it is recommended to set `adaptive` = `False`\ . The only - danger in getting this setting wrong will be a performance - penalty. - - .. note:: - When `detect_divergence` is `True`\ , :py:meth:`all_world2pix` \ - will automatically switch to the adaptive algorithm once - divergence has been detected. - - detect_divergence : bool, optional (Default = True) - Specifies whether to perform a more detailed analysis of the - convergence to a solution. Normally :py:meth:`all_world2pix` - may not achieve the required accuracy - if either the `tolerance` or `maxiter` arguments are too low. - However, it may happen that for some geometric distortions - the conditions of convergence for the the method of consecutive - approximations used by :py:meth:`all_world2pix` may not be - satisfied, in which case consecutive approximations to the - solution will diverge regardless of the `tolerance` or `maxiter` - settings. - - When `detect_divergence` is `False`\ , these divergent points - will be detected as not having achieved the required accuracy - (without further details). In addition, if `adaptive` is `False` - then the algorithm will not know that the solution (for specific - points) is diverging and will continue iterating and trying to - "improve" diverging solutions. This may result in NaN or Inf - values in the return results (in addition to a performance - penalties). Even when `detect_divergence` is - `False`\ , :py:meth:`all_world2pix`\ , at the end of the iterative - process, will identify invalid results (NaN or Inf) as "diverging" - solutions and will raise :py:class:`NoConvergence` unless - the `quiet` parameter is set to `True`\ . - - When `detect_divergence` is `True`\ , :py:meth:`all_world2pix` will - detect points for - which current correction to the coordinates is larger than - the correction applied during the previous iteration **if** the - requested accuracy **has not yet been achieved**\ . In this case, - if `adaptive` is `True`, these points will be excluded from - further iterations and if `adaptive` - is `False`\ , :py:meth:`all_world2pix` will automatically - switch to the adaptive algorithm. - - .. note:: - When accuracy has been achieved, small increases in - current corrections may be possible due to rounding errors - (when `adaptive` is `False`\ ) and such increases - will be ignored. - - .. note:: - Setting `detect_divergence` to `True` will incurr about 5-10\% - performance penalty (in our testing on ACS/WFC images). - Because the benefits of enabling this feature outweigh - the small performance penalty, it is recommended to set - `detect_divergence` to `True`\ , unless extensive testing - of the distortion models for images from specific - instruments show a good stability of the numerical method - for a wide range of coordinates (even outside the image - itself). - - .. note:: - Indices of the diverging inverse solutions will be reported - in the `divergent` attribute of the - raised :py:class:`NoConvergence` object. - - quiet : bool, optional (Default = False) - Do not throw :py:class:`NoConvergence` exceptions when the method - does not converge to a solution with the required accuracy - within a specified number of maximum iterations set by `maxiter` - parameter. Instead, simply return the found solution. - - Raises - ------ - NoConvergence - The method does not converge to a - solution with the required accuracy within a specified number - of maximum iterations set by the `maxiter` parameter. - - Notes - ----- - Inputs can either be (RA, Dec, origin) or (RADec, origin) where RA - and Dec are 1-D arrays/lists of coordinates and RADec is an - array/list of pairs of coordinates. - - Using the method of consecutive approximations we iterate starting - with the initial approximation, which is computed using the - non-distorion-aware :py:meth:`wcs_world2pix` (or equivalent). - - The :py:meth:`all_world2pix` function uses a vectorized implementation - of the method of consecutive approximations and therefore it is - highly efficient (>30x) when *all* data points that need to be - converted from sky coordinates to image coordinates are passed at - *once*\ . Therefore, it is advisable, whenever possible, to pass - as input a long array of all points that need to be converted - to :py:meth:`all_world2pix` instead of calling :py:meth:`all_world2pix` - for each data point. Also see the note to the `adaptive` parameter. - - Examples - -------- - >>> import stwcs - >>> from astropy.io import fits - >>> hdulist = fits.open('j94f05bgq_flt.fits') - >>> w = stwcs.wcsutil.HSTWCS(hdulist, ext=('sci',1)) - >>> hdulist.close() - - >>> ra, dec = w.all_pix2world([1,2,3],[1,1,1],1); print(ra); print(dec) - [ 5.52645241 5.52649277 5.52653313] - [-72.05171776 -72.05171295 -72.05170814] - >>> radec = w.all_pix2world([[1,1],[2,1],[3,1]],1); print(radec) - [[ 5.52645241 -72.05171776] - [ 5.52649277 -72.05171295] - [ 5.52653313 -72.05170814]] - >>> x, y = w.all_world2pix(ra,dec,1) - >>> print(x) - [ 1.00000233 2.00000232 3.00000233] - >>> print(y) - [ 0.99999997 0.99999997 0.99999998] - >>> xy = w.all_world2pix(radec,1) - >>> print(xy) - [[ 1.00000233 0.99999997] - [ 2.00000232 0.99999997] - [ 3.00000233 0.99999998]] - >>> xy = w.all_world2pix(radec,1, maxiter=3, accuracy=1.0e-10, \ -quiet=False) - NoConvergence: 'HSTWCS.all_world2pix' failed to converge to requested \ -accuracy after 3 iterations. - - >>> - Now try to use some diverging data: - >>> divradec = w.all_pix2world([[1.0,1.0],[10000.0,50000.0],\ -[3.0,1.0]],1); print(divradec) - [[ 5.52645241 -72.05171776] - [ 7.15979392 -70.81405561] - [ 5.52653313 -72.05170814]] - - >>> try: - >>> xy = w.all_world2pix(divradec,1, maxiter=20, accuracy=1.0e-4, \ -adaptive=False, detect_divergence=True, quiet=False) - >>> except stwcs.wcsutil.hstwcs.NoConvergence as e: - >>> print("Indices of diverging points: {}".format(e.divergent)) - >>> print("Indices of poorly converging points: {}".format(e.failed2converge)) - >>> print("Best solution: {}".format(e.best_solution)) - >>> print("Achieved accuracy: {}".format(e.accuracy)) - >>> raise e - Indices of diverging points: - [1] - Indices of poorly converging points: - None - Best solution: - [[ 1.00006219e+00 9.99999288e-01] - [ -1.99440907e+06 1.44308548e+06] - [ 3.00006257e+00 9.99999316e-01]] - Achieved accuracy: - [[ 5.98554253e-05 6.79918148e-07] - [ 8.59514088e+11 6.61703754e+11] - [ 6.02334592e-05 6.59713067e-07]] - Traceback (innermost last): - File "", line 8, in - NoConvergence: 'HSTWCS.all_world2pix' failed to converge to the requested accuracy. - After 5 iterations, the solution is diverging at least for one input point. - - >>> try: - >>> xy = w.all_world2pix(divradec,1, maxiter=20, accuracy=1.0e-4, \ -adaptive=False, detect_divergence=False, quiet=False) - >>> except stwcs.wcsutil.hstwcs.NoConvergence as e: - >>> print("Indices of diverging points: {}".format(e.divergent)) - >>> print("Indices of poorly converging points: {}".format(e.failed2converge)) - >>> print("Best solution: {}".format(e.best_solution)) - >>> print("Achieved accuracy: {}".format(e.accuracy)) - >>> raise e - Indices of diverging points: - [1] - Indices of poorly converging points: - None - Best solution: - [[ 1. 1.] - [ nan nan] - [ 3. 1.]] - Achieved accuracy: - [[ 0. 0.] - [ nan nan] - [ 0. 0.]] - Traceback (innermost last): - File "", line 8, in - NoConvergence: 'HSTWCS.all_world2pix' failed to converge to the requested accuracy. - After 20 iterations, the solution is diverging at least for one input point. - - """ - ##################################################################### - ## PROCESS ARGUMENTS: ## - ##################################################################### - nargs = len(args) - - if nargs == 3: - try: - ra = np.asarray(args[0], dtype=np.float64) - dec = np.asarray(args[1], dtype=np.float64) - #assert( len(ra.shape) == 1 and len(dec.shape) == 1 ) - origin = int(args[2]) - vect1D = True - except: - raise TypeError("When providing three arguments, they must " \ - "be (Ra, Dec, origin) where Ra and Dec are " \ - "Nx1 vectors.") - elif nargs == 2: - try: - rd = np.asarray(args[0], dtype=np.float64) - #assert( rd.shape[1] == 2 ) - ra = rd[:,0] - dec = rd[:,1] - origin = int(args[1]) - vect1D = False - except: - raise TypeError("When providing two arguments, they must " \ - "be (RaDec, origin) where RaDec is a Nx2 array.") - else: - raise TypeError("Expected 2 or 3 arguments, {:d} given." \ - .format(nargs)) - - # process optional arguments: - accuracy = kwargs.pop('accuracy', 1.0e-4) - maxiter = kwargs.pop('maxiter', 20) - adaptive = kwargs.pop('adaptive', False) - detect_divergence = kwargs.pop('detect_divergence', True) - quiet = kwargs.pop('quiet', False) - - ##################################################################### - ## INITIALIZE ITERATIVE PROCESS: ## - ##################################################################### - x0, y0 = self.wcs_world2pix(ra, dec, origin) # <-- initial approximation - # (WCS based only) - - # see if an iterative solution is required (when any of the - # non-CD-matrix corrections are present). If not required - # return initial approximation (x0, y0). - if self.sip is None and \ - self.cpdis1 is None and self.cpdis2 is None and \ - self.det2im1 is None and self.det2im2 is None: - # no non-WCS corrections are detected - return - # initial approximation - if vect1D: - return [x0, y0] - else: - return np.dstack([x0,y0])[0] - - x = x0.copy() # 0-order solution - y = y0.copy() # 0-order solution - - # initial correction: - dx, dy = self.pix2foc(x, y, origin) - # If pix2foc does not apply all the required distortion - # corrections then replace the above line with: - #r0, d0 = self.all_pix2world(x, y, origin) - #dx, dy = self.wcs_world2pix(r0, d0, origin ) - dx -= x0 - dy -= y0 - - # update initial solution: - x -= dx - y -= dy - - # norn (L2) squared of the correction: - dn2prev = dx**2+dy**2 - dn2 = dn2prev - - # prepare for iterative process - iterlist = list(range(1, maxiter+1)) - accuracy2 = accuracy**2 - ind = None - inddiv = None - - npts = x.shape[0] - - # turn off numpy runtime warnings for 'invalid' and 'over': - old_invalid = np.geterr()['invalid'] - old_over = np.geterr()['over'] - np.seterr(invalid = 'ignore', over = 'ignore') - - ##################################################################### - ## NON-ADAPTIVE ITERATIONS: ## - ##################################################################### - if not adaptive: - for k in iterlist: - # check convergence: - if np.max(dn2) < accuracy2: - break - - # find correction to the previous solution: - dx, dy = self.pix2foc(x, y, origin) - # If pix2foc does not apply all the required distortion - # corrections then replace the above line with: - #r0, d0 = self.all_pix2world(x, y, origin) - #dx, dy = self.wcs_world2pix(r0, d0, origin ) - dx -= x0 - dy -= y0 - - # update norn (L2) squared of the correction: - dn2 = dx**2+dy**2 - - # check for divergence (we do this in two stages - # to optimize performance for the most common - # scenario when succesive approximations converge): - if detect_divergence: - ind, = np.where(dn2 <= dn2prev) - if ind.shape[0] < npts: - inddiv, = np.where( - np.logical_and(dn2 > dn2prev, dn2 >= accuracy2)) - if inddiv.shape[0] > 0: - # apply correction only to the converging points: - x[ind] -= dx[ind] - y[ind] -= dy[ind] - # switch to adaptive iterations: - ind, = np.where((dn2 >= accuracy2) & \ - (dn2 <= dn2prev) & np.isfinite(dn2)) - iterlist = iterlist[k:] - adaptive = True - break - #dn2prev[ind] = dn2[ind] - dn2prev = dn2 - - # apply correction: - x -= dx - y -= dy - - ##################################################################### - ## ADAPTIVE ITERATIONS: ## - ##################################################################### - if adaptive: - if ind is None: - ind = np.asarray(list(range(npts)), dtype=np.int64) - - for k in iterlist: - # check convergence: - if ind.shape[0] == 0: - break - - # find correction to the previous solution: - dx[ind], dy[ind] = self.pix2foc(x[ind], y[ind], origin) - # If pix2foc does not apply all the required distortion - # corrections then replace the above line with: - #r0[ind], d0[ind] = self.all_pix2world(x[ind], y[ind], origin) - #dx[ind], dy[ind] = self.wcs_world2pix(r0[ind], d0[ind], origin) - dx[ind] -= x0[ind] - dy[ind] -= y0[ind] - - # update norn (L2) squared of the correction: - dn2 = dx**2+dy**2 - - # update indices of elements that still need correction: - if detect_divergence: - ind, = np.where((dn2 >= accuracy2) & (dn2 <= dn2prev)) - #ind = ind[np.where((dn2[ind] >= accuracy2) & (dn2[ind] <= dn2prev))] - dn2prev[ind] = dn2[ind] - else: - ind, = np.where(dn2 >= accuracy2) - #ind = ind[np.where(dn2[ind] >= accuracy2)] - - # apply correction: - x[ind] -= dx[ind] - y[ind] -= dy[ind] - - ##################################################################### - ## FINAL DETECTION OF INVALID, DIVERGING, ## - ## AND FAILED-TO-CONVERGE POINTS ## - ##################################################################### - # Identify diverging and/or invalid points: - invalid = (((~np.isfinite(y)) | (~np.isfinite(x)) | \ - (~np.isfinite(dn2))) & \ - (np.isfinite(ra)) & (np.isfinite(dec))) - # When detect_divergence==False, dn2prev is outdated (it is the - # norm^2 of the very first correction). Still better than nothing... - inddiv, = np.where(((dn2 >= accuracy2) & (dn2 > dn2prev)) | invalid) - if inddiv.shape[0] == 0: - inddiv = None - # identify points that did not converge within - # 'maxiter' iterations: - if k >= maxiter: - ind,= np.where((dn2 >= accuracy2) & (dn2 <= dn2prev) & (~invalid)) - if ind.shape[0] == 0: - ind = None - else: - ind = None - - ##################################################################### - ## RAISE EXCEPTION IF DIVERGING OR TOO SLOWLY CONVERGING ## - ## DATA POINTS HAVE BEEN DETECTED: ## - ##################################################################### - # raise exception if diverging or too slowly converging - if (ind is not None or inddiv is not None) and not quiet: - if vect1D: - sol = [x, y] - err = [np.abs(dx), np.abs(dy)] - else: - sol = np.dstack( [x, y] )[0] - err = np.dstack( [np.abs(dx), np.abs(dy)] )[0] - - # restore previous numpy error settings: - np.seterr(invalid = old_invalid, over = old_over) - - if inddiv is None: - raise NoConvergence("'HSTWCS.all_world2pix' failed to " \ - "converge to the requested accuracy after {:d} " \ - "iterations.".format(k), best_solution = sol, \ - accuracy = err, niter = k, failed2converge = ind, \ - divergent = None) - else: - raise NoConvergence("'HSTWCS.all_world2pix' failed to " \ - "converge to the requested accuracy.{0:s}" \ - "After {1:d} iterations, the solution is diverging " \ - "at least for one input point." \ - .format(os.linesep, k), best_solution = sol, \ - accuracy = err, niter = k, failed2converge = ind, \ - divergent = inddiv) - - ##################################################################### - ## FINALIZE AND FORMAT DATA FOR RETURN: ## - ##################################################################### - # restore previous numpy error settings: - np.seterr(invalid = old_invalid, over = old_over) - - if vect1D: - return [x, y] - else: - return np.dstack( [x, y] )[0] - - def _updatehdr(self, ext_hdr): - #kw2add : OCX10, OCX11, OCY10, OCY11 - # record the model in the header for use by pydrizzle - ext_hdr['OCX10'] = self.idcmodel.cx[1,0] - ext_hdr['OCX11'] = self.idcmodel.cx[1,1] - ext_hdr['OCY10'] = self.idcmodel.cy[1,0] - ext_hdr['OCY11'] = self.idcmodel.cy[1,1] - ext_hdr['IDCSCALE'] = self.idcmodel.refpix['PSCALE'] - ext_hdr['IDCTHETA'] = self.idcmodel.refpix['THETA'] - ext_hdr['IDCXREF'] = self.idcmodel.refpix['XREF'] - ext_hdr['IDCYREF'] = self.idcmodel.refpix['YREF'] - ext_hdr['IDCV2REF'] = self.idcmodel.refpix['V2REF'] - ext_hdr['IDCV3REF'] = self.idcmodel.refpix['V3REF'] - - def printwcs(self): - """ - Print the basic WCS keywords. - """ - print('WCS Keywords\n') - print('CD_11 CD_12: %r %r' % (self.wcs.cd[0,0], self.wcs.cd[0,1])) - print('CD_21 CD_22: %r %r' % (self.wcs.cd[1,0], self.wcs.cd[1,1])) - print('CRVAL : %r %r' % (self.wcs.crval[0], self.wcs.crval[1])) - print('CRPIX : %r %r' % (self.wcs.crpix[0], self.wcs.crpix[1])) - print('NAXIS : %d %d' % (self.naxis1, self.naxis2)) - print('Plate Scale : %r' % self.pscale) - print('ORIENTAT : %r' % self.orientat) - - -def determine_refframe(phdr): - """ - Determine the reference frame in standard FITS WCS terms. - - Parameters - ---------- - phdr : `astropy.io.fits.Header` - Primary Header of an HST observation - - In HST images the reference frame is recorded in the primary extension as REFFRAME. - Values are "GSC1" which means FK5 or ICRS (for GSC2 observations). - """ - try: - refframe = phdr['REFFRAME'] - except KeyError: - refframe = " " - if refframe == "GSC1": - refframe = "FK5" - return refframe -- cgit