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
84
85 Parameters
86 ----------
87 matches : `list` of `lsst.afw.table.ReferenceMatch`
88 The following fields are read:
89
90 - match.first (reference object) coord
91 - match.second (source) centroid
92
93 The following fields are written:
94
95 - match.first (reference object) centroid,
96 - match.second (source) centroid
97 - match.distance (on sky separation, in radians)
98
99 initWcs : `lsst.afw.geom.SkyWcs`
100 initial WCS
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.
116
117 Returns
118 -------
119 result : `lsst.pipe.base.Struct`
120 with the following fields:
121
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`)
125 """
126 if bbox is None:
128
129 import lsstDebug
131
132 wcs = self.initialWcs(matches, initWcs)
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()
137 rejected = self.rejectMatches(matches, wcs, rejected)
138 if rejected.sum() == len(rejected):
139 raise exceptions.AstrometryFitFailure(f"All matches rejected in fitter iteration {rej+1}")
140 self.log.debug(
141 "Iteration %d of astrometry fitting: rejected %d outliers, out of %d total matches.",
142 rej, rejected.sum(), len(rejected)
143 )
144 if debug.plot:
145 print("Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
146 self.plotFit(matches, wcs, rejected)
147
148 sipObject = self._fitWcs([mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
149 wcs = sipObject.getNewWcs()
150 if debug.plot:
151 print("Plotting final fit")
152 self.plotFit(matches, wcs, rejected)
153
154 if refCat is not None:
155 self.log.debug("Updating centroids in refCat")
157 else:
158 self.log.warning("Updating reference object centroids in match list; refCat is None")
160
161 if sourceCat is not None:
162 self.log.debug("Updating coords in sourceCat")
164 else:
165 self.log.warning("Updating source coords in match list; sourceCat is None")
167
168 self.log.debug("Updating distance in match list")
169 setMatchDistance(matches)
170
171 scatterOnSky = sipObject.getScatterOnSky()
172
173 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
174 raise exceptions.AstrometryFitFailure(
175 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
176 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
177
178 return pipeBase.Struct(
179 wcs=wcs,
180 scatterOnSky=scatterOnSky,
181 )
182
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.