81 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
82 """Fit a TAN-SIP WCS from a list of reference object/source matches
83
84 Parameters
85 ----------
86 matches : `list` of `lsst.afw.table.ReferenceMatch`
87 The following fields are read:
88
89 - match.first (reference object) coord
90 - match.second (source) centroid
91
92 The following fields are written:
93
94 - match.first (reference object) centroid,
95 - match.second (source) centroid
96 - match.distance (on sky separation, in radians)
97
98 initWcs : `lsst.afw.geom.SkyWcs`
99 initial WCS
100 bbox : `lsst.geom.Box2I`
101 the region over which the WCS will be valid (an lsst:afw::geom::Box2I);
102 if None or an empty box then computed from matches
103 refCat : `lsst.afw.table.SimpleCatalog`
104 reference object catalog, or None.
105 If provided then all centroids are updated with the new WCS,
106 otherwise only the centroids for ref objects in matches are updated.
107 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
108 sourceCat : `lsst.afw.table.SourceCatalog`
109 source catalog, or None.
110 If provided then coords are updated with the new WCS;
111 otherwise only the coords for sources in matches are updated.
112 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec".
113 exposure : `lsst.afw.image.Exposure`
114 Ignored; present for consistency with FitSipDistortionTask.
115
116 Returns
117 -------
118 result : `lsst.pipe.base.Struct`
119 with the following fields:
120
121 - ``wcs`` : the fit WCS (`lsst.afw.geom.SkyWcs`)
122 - ``scatterOnSky`` : median on-sky separation between reference
123 objects and sources in "matches" (`lsst.afw.geom.Angle`)
124 """
125 if bbox is None:
127
128 import lsstDebug
130
131 wcs = self.initialWcs(matches, initWcs)
132 rejected = np.zeros(len(matches), dtype=bool)
133 for rej in range(self.config.numRejIter):
134 sipObject = self._fitWcs([mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
135 wcs = sipObject.getNewWcs()
136 rejected = self.rejectMatches(matches, wcs, rejected)
137 if rejected.sum() == len(rejected):
138 raise RuntimeError("All matches rejected in iteration %d" % (rej + 1,))
139 self.log.debug(
140 "Iteration %d of astrometry fitting: rejected %d outliers, out of %d total matches.",
141 rej, rejected.sum(), len(rejected)
142 )
143 if debug.plot:
144 print("Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
145 self.plotFit(matches, wcs, rejected)
146
147 sipObject = self._fitWcs([mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
148 wcs = sipObject.getNewWcs()
149 if debug.plot:
150 print("Plotting final fit")
151 self.plotFit(matches, wcs, rejected)
152
153 if refCat is not None:
154 self.log.debug("Updating centroids in refCat")
156 else:
157 self.log.warning("Updating reference object centroids in match list; refCat is None")
159
160 if sourceCat is not None:
161 self.log.debug("Updating coords in sourceCat")
163 else:
164 self.log.warning("Updating source coords in match list; sourceCat is None")
166
167 self.log.debug("Updating distance in match list")
168 setMatchDistance(matches)
169
170 scatterOnSky = sipObject.getScatterOnSky()
171
172 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
173 raise pipeBase.TaskError(
174 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
175 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
176
177 return pipeBase.Struct(
178 wcs=wcs,
179 scatterOnSky=scatterOnSky,
180 )
181
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.