22 __all__ = [
"FitSipDistortionTask", 
"FitSipDistortionConfig"]
 
   32 from .scaledPolynomialTransformFitter 
import ScaledPolynomialTransformFitter, OutlierRejectionControl
 
   33 from .sipTransform 
import SipForwardTransform, SipReverseTransform, makeWcs
 
   34 from .makeMatchStatistics 
import makeMatchStatisticsInRadians
 
   36 from .setMatchDistance 
import setMatchDistance
 
   40     """Config for FitSipDistortionTask""" 
   41     order = lsst.pex.config.RangeField(
 
   42         doc=
"Order of SIP polynomial",
 
   47     numRejIter = lsst.pex.config.RangeField(
 
   48         doc=
"Number of rejection iterations",
 
   53     rejSigma = lsst.pex.config.RangeField(
 
   54         doc=
"Number of standard deviations for clipping level",
 
   59     nClipMin = lsst.pex.config.Field(
 
   60         doc=
"Minimum number of matches to reject when sigma-clipping",
 
   64     nClipMax = lsst.pex.config.Field(
 
   65         doc=
"Maximum number of matches to reject when sigma-clipping",
 
   69     maxScatterArcsec = lsst.pex.config.RangeField(
 
   70         doc=
"Maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " 
   71             "be generous, as this is only intended to catch catastrophic failures",
 
   76     refUncertainty = lsst.pex.config.Field(
 
   77         doc=
"RMS uncertainty in reference catalog positions, in pixels.  Will be added " 
   78             "in quadrature with measured uncertainties in the fit.",
 
   82     nGridX = lsst.pex.config.Field(
 
   83         doc=
"Number of X grid points used to invert the SIP reverse transform.",
 
   87     nGridY = lsst.pex.config.Field(
 
   88         doc=
"Number of Y grid points used to invert the SIP reverse transform.",
 
   92     gridBorder = lsst.pex.config.Field(
 
   93         doc=
"When setting the gird region, how much to extend the image " 
   94             "bounding box (in pixels) before transforming it to intermediate " 
   95             "world coordinates using the initial WCS.",
 
  102     """Fit a TAN-SIP WCS given a list of reference object/source matches. 
  104     ConfigClass = FitSipDistortionConfig
 
  105     _DefaultName = 
"fitWcs" 
  108         lsst.pipe.base.Task.__init__(self, **kwargs)
 
  114     @lsst.pipe.base.timeMethod
 
  115     def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
 
  116         """Fit a TAN-SIP WCS from a list of reference object/source matches. 
  120         matches : `list` of `lsst.afw.table.ReferenceMatch` 
  121             A sequence of reference object/source matches. 
  122             The following fields are read: 
  123             - match.first (reference object) coord 
  124             - match.second (source) centroid 
  126             The following fields are written: 
  127             - match.first (reference object) centroid 
  128             - match.second (source) centroid 
  129             - match.distance (on sky separation, in radians) 
  131         initWcs : `lsst.afw.geom.SkyWcs` 
  132             An initial WCS whose CD matrix is used as the final CD matrix. 
  133         bbox : `lsst.geom.Box2I` 
  134             The region over which the WCS will be valid (PARENT pixel coordinates); 
  135             if `None` or an empty box then computed from matches 
  136         refCat : `lsst.afw.table.SimpleCatalog` 
  137             Reference object catalog, or `None`. 
  138             If provided then all centroids are updated with the new WCS, 
  139             otherwise only the centroids for ref objects in matches are updated. 
  140             Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec". 
  141         sourceCat : `lsst.afw.table.SourceCatalog` 
  142             Source catalog, or `None`. 
  143             If provided then coords are updated with the new WCS; 
  144             otherwise only the coords for sources in matches are updated. 
  145             Required input fields are "slot_Centroid_x", "slot_Centroid_y", 
  146             "slot_Centroid_xErr", "slot_Centroid_yErr", and optionally 
  147             "slot_Centroid_x_y_Cov".  The "coord_ra" and "coord_dec" fields 
  148             will be updated but are not used as input. 
  149         exposure : `lsst.afw.image.Exposure` 
  150             An Exposure or other displayable image on which matches can be 
  151             overplotted.  Ignored (and may be `None`) if display-based debugging 
  152             is not enabled via lsstDebug. 
  156         An lsst.pipe.base.Struct with the following fields: 
  157             - wcs : `lsst.afw.geom.SkyWcs` 
  159             - scatterOnSky : `lsst.geom.Angle` 
  160                 The median on-sky separation between reference objects and 
  161                 sources in "matches", as an `lsst.geom.Angle` 
  170             for match 
in matches:
 
  171                 bbox.include(match.second.getCentroid())
 
  183         revFitter = ScaledPolynomialTransformFitter.fromMatches(self.
config.order, matches, wcs,
 
  184                                                                 self.
config.refUncertainty)
 
  186         for nIter 
in range(self.
config.numRejIter):
 
  187             revFitter.updateModel()
 
  188             intrinsicScatter = revFitter.updateIntrinsicScatter()
 
  191                 "Iteration {0}: intrinsic scatter is {1:4.3f} pixels, " 
  192                 "rejected {2} outliers at {3:3.2f} sigma.".
format(
 
  193                     nIter+1, intrinsicScatter, nRejected, clippedSigma
 
  197                 displayFrame = self.
display(revFitter, exposure=exposure, bbox=bbox,
 
  198                                             frame=displayFrame, displayPause=displayPause)
 
  200         revScaledPoly = revFitter.getTransform()
 
  204         sipReverse = SipReverseTransform.convert(revScaledPoly, wcs.getPixelOrigin(), cdMatrix)
 
  212         gridBBoxPix.grow(self.
config.gridBorder)
 
  218         for point 
in gridBBoxPix.getCorners():
 
  220             gridBBoxIwc.include(cdMatrix(point))
 
  221         fwdFitter = ScaledPolynomialTransformFitter.fromGrid(self.
config.order, gridBBoxIwc,
 
  226         fwdScaledPoly = fwdFitter.getTransform()
 
  227         sipForward = SipForwardTransform.convert(fwdScaledPoly, wcs.getPixelOrigin(), cdMatrix)
 
  231         wcs = 
makeWcs(sipForward, sipReverse, wcs.getSkyOrigin())
 
  233         if refCat 
is not None:
 
  234             self.
log.
debug(
"Updating centroids in refCat")
 
  237             self.
log.
warn(
"Updating reference object centroids in match list; refCat is None")
 
  240         if sourceCat 
is not None:
 
  241             self.
log.
debug(
"Updating coords in sourceCat")
 
  244             self.
log.
warn(
"Updating source coords in match list; sourceCat is None")
 
  247         self.
log.
debug(
"Updating distance in match list")
 
  251         scatterOnSky = stats.getValue()*lsst.geom.radians
 
  253         if scatterOnSky.asArcseconds() > self.
config.maxScatterArcsec:
 
  255                 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
 
  256                 (scatterOnSky.asArcseconds(), self.
config.maxScatterArcsec))
 
  260             scatterOnSky=scatterOnSky,
 
  263     def display(self, revFitter, exposure=None, bbox=None, frame=0, pause=True):
 
  264         """Display positions and outlier status overlaid on an image. 
  266         This method is called by fitWcs when display debugging is enabled.  It 
  267         always drops into pdb before returning to allow interactive inspection, 
  268         and hence it should never be called in non-interactive contexts. 
  272         revFitter : :cpp:class:`lsst::meas::astrom::ScaledPolynomialTransformFitter` 
  273             Fitter object initialized with `fromMatches` for fitting a "reverse" 
  274             distortion: the mapping from intermediate world coordinates to 
  276         exposure : :cpp:class:`lsst::afw::image::Exposure` 
  277             An Exposure or other displayable image on which matches can be 
  279         bbox : :cpp:class:`lsst::afw::geom::Box2I` 
  280             Bounding box of the region on which matches should be plotted. 
  282         data = revFitter.getData()
 
  283         disp = lsst.afw.display.getDisplay(frame=frame)
 
  284         if exposure 
is not None:
 
  286         elif bbox 
is not None:
 
  287             disp.mtv(exposure=lsst.afw.image.ExposureF(bbox))
 
  289             raise TypeError(
"At least one of 'exposure' and 'bbox' must be provided.")
 
  290         data = revFitter.getData()
 
  292         srcErrKey = lsst.afw.table.CovarianceMatrix2fKey(data.schema[
"src"], [
"x", 
"y"])
 
  295         rejectedKey = data.schema.find(
"rejected").key
 
  296         with disp.Buffering():
 
  298                 colors = ((lsst.afw.display.RED, lsst.afw.display.GREEN)
 
  299                           if not record.get(rejectedKey) 
else 
  300                           (lsst.afw.display.MAGENTA, lsst.afw.display.CYAN))
 
  301                 rx, ry = record.get(refKey)
 
  302                 disp.dot(
"x", rx, ry, size=10, ctype=colors[0])
 
  303                 mx, my = record.get(modelKey)
 
  304                 disp.dot(
"o", mx, my, size=10, ctype=colors[0])
 
  305                 disp.line([(rx, ry), (mx, my)], ctype=colors[0])
 
  306                 sx, sy = record.get(srcKey)
 
  307                 sErr = record.get(srcErrKey)
 
  309                 disp.dot(sEllipse, sx, sy, ctype=colors[1])
 
  310         if pause 
or pause 
is None:  
 
  311             print(
"Dropping into debugger to allow inspection of display. Type 'continue' when done.")
 
  319         """Generate a guess Wcs from the astrometric matches 
  321         We create a Wcs anchored at the center of the matches, with the scale 
  322         of the input Wcs.  This is necessary because the Wcs may have a very 
  323         approximation position (as is common with telescoped-generated Wcs). 
  324         We're using the best of each: positions from the matches, and scale 
  329         matches : list of :cpp:class:`lsst::afw::table::ReferenceMatch` 
  330             A sequence of reference object/source matches. 
  331             The following fields are read: 
  333             - match.first (reference object) coord 
  334             - match.second (source) centroid 
  336         wcs : :cpp:class:`lsst::afw::geom::SkyWcs` 
  337             An initial WCS whose CD matrix is used as the CD matrix of the 
  342         newWcs : `lsst.afw.geom.SkyWcs` 
  349             crval += mm.first.getCoord().getVector()
 
  350         crpix /= len(matches)
 
  351         crval /= len(matches)
 
  352         cd = wcs.getCdMatrix()