2 __all__ = [
"FitTanSipWcsTask",
"FitTanSipWcsConfig"]
12 import lsst.pipe.base
as pipeBase
13 from .setMatchDistance
import setMatchDistance
14 from .sip
import makeCreateWcsWithSip
18 order = pexConfig.RangeField(
19 doc=
"order of SIP polynomial",
24 numIter = pexConfig.RangeField(
25 doc=
"number of iterations of fitter (which fits X and Y separately, and so benefits from " 31 numRejIter = pexConfig.RangeField(
32 doc=
"number of rejection iterations",
37 rejSigma = pexConfig.RangeField(
38 doc=
"Number of standard deviations for clipping level",
43 maxScatterArcsec = pexConfig.RangeField(
44 doc=
"maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " 45 "be generous, as this is only intended to catch catastrophic failures",
61 r"""!Fit a TAN-SIP WCS given a list of reference object/source matches 63 @anchor FitTanSipWcsTask_ 65 @section meas_astrom_fitTanSipWcs_Contents Contents 67 - @ref meas_astrom_fitTanSipWcs_Purpose 68 - @ref meas_astrom_fitTanSipWcs_Initialize 69 - @ref meas_astrom_fitTanSipWcs_IO 70 - @ref meas_astrom_fitTanSipWcs_Schema 71 - @ref meas_astrom_fitTanSipWcs_Config 72 - @ref meas_astrom_fitTanSipWcs_Example 73 - @ref meas_astrom_fitTanSipWcs_Debug 75 @section meas_astrom_fitTanSipWcs_Purpose Description 77 Fit a TAN-SIP WCS given a list of reference object/source matches. 78 See CreateWithSip.h for information about the fitting algorithm. 80 @section meas_astrom_fitTanSipWcs_Initialize Task initialisation 84 @section meas_astrom_fitTanSipWcs_IO Invoking the Task 88 @section meas_astrom_fitTanSipWcs_Config Configuration parameters 90 See @ref FitTanSipWcsConfig 92 @section meas_astrom_fitTanSipWcs_Example A complete example of using FitTanSipWcsTask 94 FitTanSipWcsTask is a subtask of AstrometryTask, which is called by PhotoCalTask. 95 See \ref pipe_tasks_photocal_Example. 97 @section meas_astrom_fitTanSipWcs_Debug Debug variables 99 FitTanSipWcsTask does not support any debug variables. 101 ConfigClass = FitTanSipWcsConfig
102 _DefaultName =
"fitWcs" 105 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
106 """!Fit a TAN-SIP WCS from a list of reference object/source matches 108 @param[in,out] matches a list of lsst::afw::table::ReferenceMatch 109 The following fields are read: 110 - match.first (reference object) coord 111 - match.second (source) centroid 112 The following fields are written: 113 - match.first (reference object) centroid, 114 - match.second (source) centroid 115 - match.distance (on sky separation, in radians) 116 @param[in] initWcs initial WCS 117 @param[in] bbox the region over which the WCS will be valid (an lsst:afw::geom::Box2I); 118 if None or an empty box then computed from matches 119 @param[in,out] refCat reference object catalog, or None. 120 If provided then all centroids are updated with the new WCS, 121 otherwise only the centroids for ref objects in matches are updated. 122 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec". 123 @param[in,out] sourceCat source catalog, or None. 124 If provided then coords are updated with the new WCS; 125 otherwise only the coords for sources in matches are updated. 126 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec". 127 @param[in] exposure Ignored; present for consistency with FitSipDistortionTask. 129 @return an lsst.pipe.base.Struct with the following fields: 130 - wcs the fit WCS as an lsst.afw.geom.Wcs 131 - scatterOnSky median on-sky separation between reference objects and sources in "matches", 132 as an lsst.afw.geom.Angle 141 rejected = np.zeros(len(matches), dtype=bool)
142 for rej
in range(self.config.numRejIter):
143 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
144 wcs = sipObject.getNewWcs()
146 if rejected.sum() == len(rejected):
147 raise RuntimeError(
"All matches rejected in iteration %d" % (rej + 1,))
149 "Iteration {0} of astrometry fitting: rejected {1} outliers, " 150 "out of {2} total matches.".format(
151 rej, rejected.sum(), len(rejected)
155 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
156 self.
plotFit(matches, wcs, rejected)
158 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
159 wcs = sipObject.getNewWcs()
161 print(
"Plotting final fit")
162 self.
plotFit(matches, wcs, rejected)
164 if refCat
is not None:
165 self.log.debug(
"Updating centroids in refCat")
168 self.log.warn(
"Updating reference object centroids in match list; refCat is None")
171 if sourceCat
is not None:
172 self.log.debug(
"Updating coords in sourceCat")
175 self.log.warn(
"Updating source coords in match list; sourceCat is None")
178 self.log.debug(
"Updating distance in match list")
181 scatterOnSky = sipObject.getScatterOnSky()
183 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
184 raise pipeBase.TaskError(
185 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
186 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
188 return pipeBase.Struct(
190 scatterOnSky=scatterOnSky,
194 """Generate a guess Wcs from the astrometric matches 196 We create a Wcs anchored at the center of the matches, with the scale 197 of the input Wcs. This is necessary because matching returns only 198 matches with no estimated Wcs, and the input Wcs is a wild guess. 199 We're using the best of each: positions from the matches, and scale 206 crval += mm.first.getCoord().getVector()
207 crpix /= len(matches)
208 crval /= len(matches)
211 cdMatrix=wcs.getCdMatrix())
214 def _fitWcs(self, matches, wcs):
215 """Fit a Wcs based on the matches and a guess Wcs""" 216 for i
in range(self.config.numIter):
218 wcs = sipObject.getNewWcs()
222 """Flag deviant matches 224 We return a boolean numpy array indicating whether the corresponding 225 match should be rejected. The previous list of rejections is used 226 so we can calculate uncontaminated statistics. 228 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
229 dx = np.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
230 dy = np.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
231 good = np.logical_not(rejected)
232 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
237 We create four plots, for all combinations of (dx, dy) against 238 (x, y). Good points are black, while rejected points are red. 241 import matplotlib.pyplot
as plt
242 except ImportError
as e:
243 self.log.warn(
"Unable to import matplotlib: %s", e)
246 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
247 x1 = np.array([ff.getX()
for ff
in fit])
248 y1 = np.array([ff.getY()
for ff
in fit])
249 x2 = np.array([m.second.getCentroid().getX()
for m
in matches])
250 y2 = np.array([m.second.getCentroid().getY()
for m
in matches])
255 good = np.logical_not(rejected)
257 figure = plt.figure()
258 axes = figure.add_subplot(2, 2, 1)
259 axes.plot(x2[good], dx[good],
'ko')
260 axes.plot(x2[rejected], dx[rejected],
'ro')
262 axes.set_ylabel(
"dx")
264 axes = figure.add_subplot(2, 2, 2)
265 axes.plot(x2[good], dy[good],
'ko')
266 axes.plot(x2[rejected], dy[rejected],
'ro')
268 axes.set_ylabel(
"dy")
270 axes = figure.add_subplot(2, 2, 3)
271 axes.plot(y2[good], dx[good],
'ko')
272 axes.plot(y2[rejected], dx[rejected],
'ro')
274 axes.set_ylabel(
"dx")
276 axes = figure.add_subplot(2, 2, 4)
277 axes.plot(y2[good], dy[good],
'ko')
278 axes.plot(y2[rejected], dy[rejected],
'ro')
280 axes.set_ylabel(
"dy")
def plotFit(self, matches, wcs, rejected)
def initialWcs(self, matches, wcs)
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
def rejectMatches(self, matches, wcs, rejected)
def _fitWcs(self, matches, wcs)
def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None)
Fit a TAN-SIP WCS from a list of reference object/source matches.
def setMatchDistance(matches)
CreateWcsWithSip< MatchT > makeCreateWcsWithSip(std::vector< MatchT > const &matches, afw::geom::SkyWcs const &linearWcs, int const order, geom::Box2I const &bbox=geom::Box2I(), int const ngrid=0)
Factory function for CreateWcsWithSip.
Fit a TAN-SIP WCS given a list of reference object/source matches.