82 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
83 """Fit a TAN-SIP WCS from a list of reference object/source matches
87 matches : `list` of `lsst.afw.table.ReferenceMatch`
88 The following fields are read:
90 - match.first (reference object) coord
91 - match.second (source) centroid
93 The following fields are written:
95 - match.first (reference object) centroid,
96 - match.second (source) centroid
97 - match.distance (on sky separation, in radians)
99 initWcs : `lsst.afw.geom.SkyWcs`
101 bbox : `lsst.geom.Box2I`
102 the region over which the WCS will be valid (an lsst:afw::geom::Box2I);
103 if None or an empty box then computed from matches
104 refCat : `lsst.afw.table.SimpleCatalog`
105 reference object catalog, or None.
106 If provided then all centroids are updated with the new WCS,
107 otherwise only the centroids for ref objects in matches are updated.
108 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
109 sourceCat : `lsst.afw.table.SourceCatalog`
110 source catalog, or None.
111 If provided then coords are updated with the new WCS;
112 otherwise only the coords for sources in matches are updated.
113 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec".
114 exposure : `lsst.afw.image.Exposure`
115 Ignored; present for consistency with FitSipDistortionTask.
119 result : `lsst.pipe.base.Struct`
120 with the following fields:
122 - ``wcs`` : the fit WCS (`lsst.afw.geom.SkyWcs`)
123 - ``scatterOnSky`` : median on-sky separation between reference
124 objects and sources in "matches" (`lsst.afw.geom.Angle`)
133 rejected = np.zeros(len(matches), dtype=bool)
134 for rej
in range(self.config.numRejIter):
135 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
136 wcs = sipObject.getNewWcs()
138 if rejected.sum() == len(rejected):
141 "Iteration %d of astrometry fitting: rejected %d outliers, out of %d total matches.",
142 rej, rejected.sum(), len(rejected)
145 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
146 self.
plotFit(matches, wcs, rejected)
148 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
149 wcs = sipObject.getNewWcs()
151 print(
"Plotting final fit")
152 self.
plotFit(matches, wcs, rejected)
154 if refCat
is not None:
155 self.log.debug(
"Updating centroids in refCat")
158 self.log.warning(
"Updating reference object centroids in match list; refCat is None")
161 if sourceCat
is not None:
162 self.log.debug(
"Updating coords in sourceCat")
165 self.log.warning(
"Updating source coords in match list; sourceCat is None")
168 self.log.debug(
"Updating distance in match list")
169 setMatchDistance(matches)
171 scatterOnSky = sipObject.getScatterOnSky()
173 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
175 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
176 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
178 return pipeBase.Struct(
180 scatterOnSky=scatterOnSky,
237 """Flag deviant matches
239 We return a boolean numpy array indicating whether the corresponding
240 match should be rejected. The previous list of rejections is used
241 so we can calculate uncontaminated statistics.
245 matches : `list` of `lsst.afw.table.ReferenceMatch`
246 List of sources matched to references.
247 wcs : `lsst.afw.geom.SkyWcs`
249 rejected : array-like of `bool`
250 Array of matches rejected from the fit. Unused.
254 rejectedMatches : `ndarray` of type `bool`
255 Matched objects found to be outside of tolerance.
257 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
258 dx = np.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
259 dy = np.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
260 good = np.logical_not(rejected)
261 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
266 We create four plots, for all combinations of (dx, dy) against
267 (x, y). Good points are black, while rejected points are red.
271 matches : `list` of `lsst.afw.table.ReferenceMatch`
272 List of sources matched to references.
273 wcs : `lsst.afw.geom.SkyWcs`
275 rejected : array-like of `bool`
276 Array of matches rejected from the fit.
279 import matplotlib.pyplot
as plt
280 except ImportError
as e:
281 self.log.warning(
"Unable to import matplotlib: %s", e)
284 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
285 x1 = np.array([ff.getX()
for ff
in fit])
286 y1 = np.array([ff.getY()
for ff
in fit])
287 x2 = np.array([m.second.getCentroid().getX()
for m
in matches])
288 y2 = np.array([m.second.getCentroid().getY()
for m
in matches])
293 good = np.logical_not(rejected)
295 figure = plt.figure()
296 axes = figure.add_subplot(2, 2, 1)
297 axes.plot(x2[good], dx[good],
'ko')
298 axes.plot(x2[rejected], dx[rejected],
'ro')
300 axes.set_ylabel(
"dx")
302 axes = figure.add_subplot(2, 2, 2)
303 axes.plot(x2[good], dy[good],
'ko')
304 axes.plot(x2[rejected], dy[rejected],
'ro')
306 axes.set_ylabel(
"dy")
308 axes = figure.add_subplot(2, 2, 3)
309 axes.plot(y2[good], dx[good],
'ko')
310 axes.plot(y2[rejected], dx[rejected],
'ro')
312 axes.set_ylabel(
"dx")
314 axes = figure.add_subplot(2, 2, 4)
315 axes.plot(y2[good], dy[good],
'ko')
316 axes.plot(y2[rejected], dy[rejected],
'ro')
318 axes.set_ylabel(
"dy")