22 __all__ = [
"FitSipDistortionTask",
"FitSipDistortionConfig"]
31 from .scaledPolynomialTransformFitter
import ScaledPolynomialTransformFitter, OutlierRejectionControl
32 from .sipTransform
import SipForwardTransform, SipReverseTransform, makeWcs
33 from .makeMatchStatistics
import makeMatchStatisticsInRadians
35 from .setMatchDistance
import setMatchDistance
39 """Config for FitSipDistortionTask""" 41 doc=
"Order of SIP polynomial",
47 doc=
"Number of rejection iterations",
53 doc=
"Number of standard deviations for clipping level",
59 doc=
"Minimum number of matches to reject when sigma-clipping",
64 doc=
"Maximum number of matches to reject when sigma-clipping",
69 doc=
"Maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " 70 "be generous, as this is only intended to catch catastrophic failures",
76 doc=
"RMS uncertainty in reference catalog positions, in pixels. Will be added " 77 "in quadrature with measured uncertainties in the fit.",
82 doc=
"Number of X grid points used to invert the SIP reverse transform.",
87 doc=
"Number of Y grid points used to invert the SIP reverse transform.",
92 doc=
"When setting the gird region, how much to extend the image " 93 "bounding box (in pixels) before transforming it to intermediate " 94 "world coordinates using the initial WCS.",
101 """Fit a TAN-SIP WCS given a list of reference object/source matches. 103 ConfigClass = FitSipDistortionConfig
104 _DefaultName =
"fitWcs" 107 lsst.pipe.base.Task.__init__(self, **kwargs)
113 @lsst.pipe.base.timeMethod
114 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
115 """Fit a TAN-SIP WCS from a list of reference object/source matches. 119 matches : `list` of `lsst.afw.table.ReferenceMatch` 120 A sequence of reference object/source matches. 121 The following fields are read: 122 - match.first (reference object) coord 123 - match.second (source) centroid 125 The following fields are written: 126 - match.first (reference object) centroid 127 - match.second (source) centroid 128 - match.distance (on sky separation, in radians) 130 initWcs : `lsst.afw.geom.SkyWcs` 131 An initial WCS whose CD matrix is used as the final CD matrix. 132 bbox : `lsst.geom.Box2I` 133 The region over which the WCS will be valid (PARENT pixel coordinates); 134 if `None` or an empty box then computed from matches 135 refCat : `lsst.afw.table.SimpleCatalog` 136 Reference object catalog, or `None`. 137 If provided then all centroids are updated with the new WCS, 138 otherwise only the centroids for ref objects in matches are updated. 139 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec". 140 sourceCat : `lsst.afw.table.SourceCatalog` 141 Source catalog, or `None`. 142 If provided then coords are updated with the new WCS; 143 otherwise only the coords for sources in matches are updated. 144 Required input fields are "slot_Centroid_x", "slot_Centroid_y", 145 "slot_Centroid_xErr", "slot_Centroid_yErr", and optionally 146 "slot_Centroid_x_y_Cov". The "coord_ra" and "coord_dec" fields 147 will be updated but are not used as input. 148 exposure : `lsst.afw.image.Exposure` 149 An Exposure or other displayable image on which matches can be 150 overplotted. Ignored (and may be `None`) if display-based debugging 151 is not enabled via lsstDebug. 155 An lsst.pipe.base.Struct with the following fields: 156 - wcs : `lsst.afw.geom.SkyWcs` 158 - scatterOnSky : `lsst.geom.Angle` 159 The median on-sky separation between reference objects and 160 sources in "matches", as an `lsst.geom.Angle` 169 for match
in matches:
170 bbox.include(match.second.getCentroid())
182 revFitter = ScaledPolynomialTransformFitter.fromMatches(self.
config.order, matches, wcs,
183 self.
config.refUncertainty)
185 for nIter
in range(self.
config.numRejIter):
186 revFitter.updateModel()
187 intrinsicScatter = revFitter.updateIntrinsicScatter()
190 "Iteration {0}: intrinsic scatter is {1:4.3f} pixels, " 191 "rejected {2} outliers at {3:3.2f} sigma.".
format(
192 nIter+1, intrinsicScatter, nRejected, clippedSigma
196 displayFrame = self.
display(revFitter, exposure=exposure, bbox=bbox,
197 frame=displayFrame, displayPause=displayPause)
199 revScaledPoly = revFitter.getTransform()
203 sipReverse = SipReverseTransform.convert(revScaledPoly, wcs.getPixelOrigin(), cdMatrix)
211 gridBBoxPix.grow(self.
config.gridBorder)
217 for point
in gridBBoxPix.getCorners():
219 gridBBoxIwc.include(cdMatrix(point))
220 fwdFitter = ScaledPolynomialTransformFitter.fromGrid(self.
config.order, gridBBoxIwc,
225 fwdScaledPoly = fwdFitter.getTransform()
226 sipForward = SipForwardTransform.convert(fwdScaledPoly, wcs.getPixelOrigin(), cdMatrix)
230 wcs =
makeWcs(sipForward, sipReverse, wcs.getSkyOrigin())
232 if refCat
is not None:
233 self.
log.
debug(
"Updating centroids in refCat")
236 self.
log.
warn(
"Updating reference object centroids in match list; refCat is None")
239 if sourceCat
is not None:
240 self.
log.
debug(
"Updating coords in sourceCat")
243 self.
log.
warn(
"Updating source coords in match list; sourceCat is None")
246 self.
log.
debug(
"Updating distance in match list")
250 scatterOnSky = stats.getValue()*lsst.afw.geom.radians
252 if scatterOnSky.asArcseconds() > self.
config.maxScatterArcsec:
254 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
255 (scatterOnSky.asArcseconds(), self.
config.maxScatterArcsec))
259 scatterOnSky=scatterOnSky,
262 def display(self, revFitter, exposure=None, bbox=None, frame=0, pause=True):
263 """Display positions and outlier status overlaid on an image. 265 This method is called by fitWcs when display debugging is enabled. It 266 always drops into pdb before returning to allow interactive inspection, 267 and hence it should never be called in non-interactive contexts. 271 revFitter : :cpp:class:`lsst::meas::astrom::ScaledPolynomialTransformFitter` 272 Fitter object initialized with `fromMatches` for fitting a "reverse" 273 distortion: the mapping from intermediate world coordinates to 275 exposure : :cpp:class:`lsst::afw::image::Exposure` 276 An Exposure or other displayable image on which matches can be 278 bbox : :cpp:class:`lsst::afw::geom::Box2I` 279 Bounding box of the region on which matches should be plotted. 281 data = revFitter.getData()
282 disp = lsst.afw.display.getDisplay(frame=frame)
283 if exposure
is not None:
285 elif bbox
is not None:
286 disp.mtv(exposure=lsst.afw.image.ExposureF(bbox))
288 raise TypeError(
"At least one of 'exposure' and 'bbox' must be provided.")
289 data = revFitter.getData()
291 srcErrKey = lsst.afw.table.CovarianceMatrix2fKey(data.schema[
"src"], [
"x",
"y"])
294 rejectedKey = data.schema.find(
"rejected").key
295 with disp.Buffering():
297 colors = ((lsst.afw.display.RED, lsst.afw.display.GREEN)
298 if not record.get(rejectedKey)
else 299 (lsst.afw.display.MAGENTA, lsst.afw.display.CYAN))
300 rx, ry = record.get(refKey)
301 disp.dot(
"x", rx, ry, size=10, ctype=colors[0])
302 mx, my = record.get(modelKey)
303 disp.dot(
"o", mx, my, size=10, ctype=colors[0])
304 disp.line([(rx, ry), (mx, my)], ctype=colors[0])
305 sx, sy = record.get(srcKey)
306 sErr = record.get(srcErrKey)
308 disp.dot(sEllipse, sx, sy, ctype=colors[1])
309 if pause
or pause
is None:
310 print(
"Dropping into debugger to allow inspection of display. Type 'continue' when done.")
318 """Generate a guess Wcs from the astrometric matches 320 We create a Wcs anchored at the center of the matches, with the scale 321 of the input Wcs. This is necessary because the Wcs may have a very 322 approximation position (as is common with telescoped-generated Wcs). 323 We're using the best of each: positions from the matches, and scale 328 matches : list of :cpp:class:`lsst::afw::table::ReferenceMatch` 329 A sequence of reference object/source matches. 330 The following fields are read: 332 - match.first (reference object) coord 333 - match.second (source) centroid 335 wcs : :cpp:class:`lsst::afw::geom::SkyWcs` 336 An initial WCS whose CD matrix is used as the CD matrix of the 341 newWcs : `lsst.afw.geom.SkyWcs` 348 crval += mm.first.getCoord().getVector()
349 crpix /= len(matches)
350 crval /= len(matches)
351 cd = wcs.getCdMatrix()
An ellipse core with quadrupole moments as parameters.
def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None)
A floating-point coordinate rectangle geometry.
def display(self, revFitter, exposure=None, bbox=None, frame=0, pause=True)
def __init__(self, kwargs)
Vector3d is a vector in ℝ³ with components stored in double precision.
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
Update centroids in a collection of reference objects.
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
Update sky coordinates in a collection of source objects.
def makeInitialWcs(self, matches, wcs)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
std::shared_ptr< afw::geom::SkyWcs > makeWcs(SipForwardTransform const &sipForward, SipReverseTransform const &sipReverse, geom::SpherePoint const &skyOrigin)
Create a new TAN SIP Wcs from a pair of SIP transforms and the sky origin.
afw::math::Statistics makeMatchStatisticsInRadians(afw::geom::SkyWcs const &wcs, std::vector< MatchT > const &matchList, int const flags, afw::math::StatisticsControl const &sctrl=afw::math::StatisticsControl())
Compute statistics of on-sky radial separation for a match list, in radians.
def setMatchDistance(matches)
Point in an unspecified spherical coordinate system.
std::shared_ptr< SkyWcs > makeSkyWcs(TransformPoint2ToPoint2 const &pixelsToFieldAngle, lsst::geom::Angle const &orientation, bool flipX, lsst::geom::SpherePoint const &boresight, std::string const &projection="TAN")
Construct a FITS SkyWcs from camera geometry.
An integer coordinate rectangle.