118 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
119 """Fit a TAN-SIP WCS from a list of reference object/source matches.
123 matches : `list` of `lsst.afw.table.ReferenceMatch`
124 A sequence of reference object/source matches.
125 The following fields are read:
126 - match.first (reference object) coord
127 - match.second (source) centroid
129 The following fields are written:
130 - match.first (reference object) centroid
131 - match.second (source) centroid
132 - match.distance (on sky separation, in radians)
134 initWcs : `lsst.afw.geom.SkyWcs`
135 An initial WCS whose CD matrix is used as the final CD matrix.
136 bbox : `lsst.geom.Box2I`
137 The region over which the WCS will be valid (PARENT pixel coordinates);
138 if `None` or an empty box then computed from matches
139 refCat : `lsst.afw.table.SimpleCatalog`
140 Reference object catalog, or `None`.
141 If provided then all centroids are updated with the new WCS,
142 otherwise only the centroids for ref objects in matches are updated.
143 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
144 sourceCat : `lsst.afw.table.SourceCatalog`
145 Source catalog, or `None`.
146 If provided then coords are updated with the new WCS;
147 otherwise only the coords for sources in matches are updated.
148 Required input fields are "slot_Centroid_x", "slot_Centroid_y",
149 "slot_Centroid_xErr", "slot_Centroid_yErr", and optionally
150 "slot_Centroid_x_y_Cov". The "coord_ra" and "coord_dec" fields
151 will be updated but are not used as input.
152 exposure : `lsst.afw.image.Exposure`
153 An Exposure or other displayable image on which matches can be
154 overplotted. Ignored (and may be `None`) if display-based debugging
155 is not enabled via lsstDebug.
159 An lsst.pipe.base.Struct with the following fields:
160 - wcs : `lsst.afw.geom.SkyWcs`
162 - scatterOnSky : `lsst.geom.Angle`
163 The median on-sky separation between reference objects and
164 sources in "matches", as an `lsst.geom.Angle`
173 for match
in matches:
174 bbox.include(match.second.getCentroid())
186 revFitter = ScaledPolynomialTransformFitter.fromMatches(self.config.order, matches, wcs,
187 self.config.refUncertainty)
189 for nIter
in range(self.config.numRejIter):
190 revFitter.updateModel()
191 intrinsicScatter = revFitter.updateIntrinsicScatter()
194 "Iteration %s: intrinsic scatter is %4.3f pixels, "
195 "rejected %d outliers at %3.2f sigma.",
196 nIter+1, intrinsicScatter, nRejected, clippedSigma
199 displayFrame = self.
display(revFitter, exposure=exposure, bbox=bbox,
200 frame=displayFrame, displayPause=displayPause)
202 revScaledPoly = revFitter.getTransform()
206 sipReverse = SipReverseTransform.convert(revScaledPoly, wcs.getPixelOrigin(), cdMatrix)
214 gridBBoxPix.grow(self.config.gridBorder)
220 for point
in gridBBoxPix.getCorners():
222 gridBBoxIwc.include(cdMatrix(point))
223 fwdFitter = ScaledPolynomialTransformFitter.fromGrid(self.config.order, gridBBoxIwc,
224 self.config.nGridX, self.config.nGridY,
228 fwdScaledPoly = fwdFitter.getTransform()
229 sipForward = SipForwardTransform.convert(fwdScaledPoly, wcs.getPixelOrigin(), cdMatrix)
233 wcs = makeWcs(sipForward, sipReverse, wcs.getSkyOrigin())
235 if refCat
is not None:
236 self.log.debug(
"Updating centroids in refCat")
239 self.log.warning(
"Updating reference object centroids in match list; refCat is None")
242 if sourceCat
is not None:
243 self.log.debug(
"Updating coords in sourceCat")
246 self.log.warning(
"Updating source coords in match list; sourceCat is None")
249 self.log.debug(
"Updating distance in match list")
250 setMatchDistance(matches)
252 stats = makeMatchStatisticsInRadians(wcs, matches, lsst.afw.math.MEDIAN)
253 scatterOnSky = stats.getValue()*lsst.geom.radians
255 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
257 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
258 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
260 return lsst.pipe.base.Struct(
262 scatterOnSky=scatterOnSky,
265 def display(self, revFitter, exposure=None, bbox=None, frame=0, pause=True):
266 """Display positions and outlier status overlaid on an image.
268 This method is called by fitWcs when display debugging is enabled. It
269 always drops into pdb before returning to allow interactive inspection,
270 and hence it should never be called in non-interactive contexts.
274 revFitter : :cpp:class:`lsst::meas::astrom::ScaledPolynomialTransformFitter`
275 Fitter object initialized with `fromMatches` for fitting a "reverse"
276 distortion: the mapping from intermediate world coordinates to
278 exposure : :cpp:class:`lsst::afw::image::Exposure`
279 An Exposure or other displayable image on which matches can be
281 bbox : :cpp:class:`lsst::afw::geom::Box2I`
282 Bounding box of the region on which matches should be plotted.
284 data = revFitter.getData()
285 disp = lsst.afw.display.getDisplay(frame=frame)
286 if exposure
is not None:
288 elif bbox
is not None:
289 disp.mtv(exposure=lsst.afw.image.ExposureF(bbox))
291 raise TypeError(
"At least one of 'exposure' and 'bbox' must be provided.")
292 data = revFitter.getData()
297 rejectedKey = data.schema.find(
"rejected").key
298 with disp.Buffering():
300 colors = ((lsst.afw.display.RED, lsst.afw.display.GREEN)
301 if not record.get(rejectedKey)
else
302 (lsst.afw.display.MAGENTA, lsst.afw.display.CYAN))
303 rx, ry = record.get(refKey)
304 disp.dot(
"x", rx, ry, size=10, ctype=colors[0])
305 mx, my = record.get(modelKey)
306 disp.dot(
"o", mx, my, size=10, ctype=colors[0])
307 disp.line([(rx, ry), (mx, my)], ctype=colors[0])
308 sx, sy = record.get(srcKey)
309 sErr = record.get(srcErrKey)
310 sEllipse = lsst.afw.geom.Quadrupole(sErr[0, 0], sErr[1, 1], sErr[0, 1])
311 disp.dot(sEllipse, sx, sy, ctype=colors[1])
312 if pause
or pause
is None:
313 print(
"Dropping into debugger to allow inspection of display. Type 'continue' when done.")