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.
120
121 Parameters
122 ----------
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
128
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)
133
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.
156
157 Returns
158 -------
159 An lsst.pipe.base.Struct with the following fields:
160 - wcs : `lsst.afw.geom.SkyWcs`
161 The best-fit WCS.
162 - scatterOnSky : `lsst.geom.Angle`
163 The median on-sky separation between reference objects and
164 sources in "matches", as an `lsst.geom.Angle`
165 """
166 import lsstDebug
170
171 if bbox is None:
173 for match in matches:
174 bbox.include(match.second.getCentroid())
176
177 wcs = self.makeInitialWcs(matches, initWcs)
179
180
181
182
183
184
185
186 revFitter = ScaledPolynomialTransformFitter.fromMatches(self.config.order, matches, wcs,
187 self.config.refUncertainty)
188 revFitter.fit()
189 for nIter in range(self.config.numRejIter):
190 revFitter.updateModel()
191 intrinsicScatter = revFitter.updateIntrinsicScatter()
192 clippedSigma, nRejected = revFitter.rejectOutliers(self.outlierRejectionCtrl)
193 self.log.debug(
194 "Iteration %s: intrinsic scatter is %4.3f pixels, "
195 "rejected %d outliers at %3.2f sigma.",
196 nIter+1, intrinsicScatter, nRejected, clippedSigma
197 )
198 if display:
199 displayFrame = self.display(revFitter, exposure=exposure, bbox=bbox,
200 frame=displayFrame, displayPause=displayPause)
201 revFitter.fit()
202 revScaledPoly = revFitter.getTransform()
203
204
205
206 sipReverse = SipReverseTransform.convert(revScaledPoly, wcs.getPixelOrigin(), cdMatrix)
207
208
209
210
211
212
214 gridBBoxPix.grow(self.config.gridBorder)
215
216
217
218
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,
225 revScaledPoly)
226 fwdFitter.fit()
227
228 fwdScaledPoly = fwdFitter.getTransform()
229 sipForward = SipForwardTransform.convert(fwdScaledPoly, wcs.getPixelOrigin(), cdMatrix)
230
231
232
233 wcs = makeWcs(sipForward, sipReverse, wcs.getSkyOrigin())
234
235 if refCat is not None:
236 self.log.debug("Updating centroids in refCat")
238 else:
239 self.log.warning("Updating reference object centroids in match list; refCat is None")
241
242 if sourceCat is not None:
243 self.log.debug("Updating coords in sourceCat")
245 else:
246 self.log.warning("Updating source coords in match list; sourceCat is None")
248
249 self.log.debug("Updating distance in match list")
250 setMatchDistance(matches)
251
252 stats = makeMatchStatisticsInRadians(wcs, matches, lsst.afw.math.MEDIAN)
253 scatterOnSky = stats.getValue()*lsst.geom.radians
254
255 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
256 raise exceptions.AstrometryFitFailure(
257 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
258 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
259
260 return lsst.pipe.base.Struct(
261 wcs=wcs,
262 scatterOnSky=scatterOnSky,
263 )
264
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
Update centroids in a collection of reference objects.
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList, bool include_covariance=true)
Update sky coordinates in a collection of source objects.