1 from __future__
import absolute_import, division, print_function
2 from builtins
import zip
3 from builtins
import range
13 from .setMatchDistance
import setMatchDistance
14 from .sip
import makeCreateWcsWithSip
16 __all__ = [
"FitTanSipWcsTask",
"FitTanSipWcsConfig"]
20 order = pexConfig.RangeField(
21 doc=
"order of SIP polynomial",
26 numIter = pexConfig.RangeField(
27 doc=
"number of iterations of fitter (which fits X and Y separately, and so benefits from " +
33 numRejIter = pexConfig.RangeField(
34 doc=
"number of rejection iterations",
39 rejSigma = pexConfig.RangeField(
40 doc=
"Number of standard deviations for clipping level",
45 maxScatterArcsec = pexConfig.RangeField(
46 doc=
"maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " +
47 "be generous, as this is only intended to catch catastrophic failures",
63 """!Fit a TAN-SIP WCS given a list of reference object/source matches
65 @anchor FitTanSipWcsTask_
67 @section meas_astrom_fitTanSipWcs_Contents Contents
69 - @ref meas_astrom_fitTanSipWcs_Purpose
70 - @ref meas_astrom_fitTanSipWcs_Initialize
71 - @ref meas_astrom_fitTanSipWcs_IO
72 - @ref meas_astrom_fitTanSipWcs_Schema
73 - @ref meas_astrom_fitTanSipWcs_Config
74 - @ref meas_astrom_fitTanSipWcs_Example
75 - @ref meas_astrom_fitTanSipWcs_Debug
77 @section meas_astrom_fitTanSipWcs_Purpose Description
79 Fit a TAN-SIP WCS given a list of reference object/source matches.
80 See CreateWithSip.h for information about the fitting algorithm.
82 @section meas_astrom_fitTanSipWcs_Initialize Task initialisation
86 @section meas_astrom_fitTanSipWcs_IO Invoking the Task
90 @section meas_astrom_fitTanSipWcs_Config Configuration parameters
92 See @ref FitTanSipWcsConfig
94 @section meas_astrom_fitTanSipWcs_Example A complete example of using FitTanSipWcsTask
96 FitTanSipWcsTask is a subtask of AstrometryTask, which is called by PhotoCalTask.
97 See \ref meas_photocal_photocal_Example.
99 @section meas_astrom_fitTanSipWcs_Debug Debug variables
101 FitTanSipWcsTask does not support any debug variables.
103 ConfigClass = FitTanSipWcsConfig
104 _DefaultName =
"fitWcs"
107 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
108 """!Fit a TAN-SIP WCS from a list of reference object/source matches
110 @param[in,out] matches a list of reference object/source matches
111 (an lsst::afw::table::ReferenceMatchVector)
112 The following fields are read:
113 - match.first (reference object) coord
114 - match.second (source) centroid
115 The following fields are written:
116 - match.first (reference object) centroid,
117 - match.second (source) centroid
118 - match.distance (on sky separation, in radians)
119 @param[in] initWcs initial WCS
120 @param[in] bbox the region over which the WCS will be valid (an lsst:afw::geom::Box2I);
121 if None or an empty box then computed from matches
122 @param[in,out] refCat reference object catalog, or None.
123 If provided then all centroids are updated with the new WCS,
124 otherwise only the centroids for ref objects in matches are updated.
125 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
126 @param[in,out] sourceCat source catalog, or None.
127 If provided then coords are updated with the new WCS;
128 otherwise only the coords for sources in matches are updated.
129 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec".
130 @param[in] exposure Ignored; present for consistency with FitSipDistortionTask.
132 @return an lsst.pipe.base.Struct with the following fields:
133 - wcs the fit WCS as an lsst.afw.image.Wcs
134 - scatterOnSky median on-sky separation between reference objects and sources in "matches",
135 as an lsst.afw.geom.Angle
144 rejected = np.zeros(len(matches), dtype=bool)
145 for rej
in range(self.config.numRejIter):
146 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
147 wcs = sipObject.getNewWcs()
149 if rejected.sum() == len(rejected):
150 raise RuntimeError(
"All matches rejected in iteration %d" % (rej + 1,))
152 "Iteration {0} of astrometry fitting: rejected {1} outliers, "
153 "out of {2} total matches.".
format(
154 rej, rejected.sum(), len(rejected)
158 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
159 self.
plotFit(matches, wcs, rejected)
161 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
162 wcs = sipObject.getNewWcs()
164 print(
"Plotting final fit")
165 self.
plotFit(matches, wcs, rejected)
167 if refCat
is not None:
168 self.log.debug(
"Updating centroids in refCat")
169 afwTable.updateRefCentroids(wcs, refList=refCat)
171 self.log.warn(
"Updating reference object centroids in match list; refCat is None")
172 afwTable.updateRefCentroids(wcs, refList=[match.first
for match
in matches])
174 if sourceCat
is not None:
175 self.log.debug(
"Updating coords in sourceCat")
176 afwTable.updateSourceCoords(wcs, sourceList=sourceCat)
178 self.log.warn(
"Updating source coords in match list; sourceCat is None")
179 afwTable.updateSourceCoords(wcs, sourceList=[match.second
for match
in matches])
181 self.log.debug(
"Updating distance in match list")
184 scatterOnSky = sipObject.getScatterOnSky()
186 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
187 raise pipeBase.TaskError(
188 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
189 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
191 return pipeBase.Struct(
193 scatterOnSky=scatterOnSky,
197 """Generate a guess Wcs from the astrometric matches
199 We create a Wcs anchored at the center of the matches, with the scale
200 of the input Wcs. This is necessary because matching returns only
201 matches with no estimated Wcs, and the input Wcs is a wild guess.
202 We're using the best of each: positions from the matches, and scale
210 crpix /= len(matches)
211 crval /= len(matches)
217 """Fit a Wcs based on the matches and a guess Wcs"""
218 for i
in range(self.config.numIter):
220 wcs = sipObject.getNewWcs()
224 """Flag deviant matches
226 We return a boolean numpy array indicating whether the corresponding
227 match should be rejected. The previous list of rejections is used
228 so we can calculate uncontaminated statistics.
230 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
231 dx = np.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
232 dy = np.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
233 good = np.logical_not(rejected)
234 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
239 We create four plots, for all combinations of (dx, dy) against
240 (x, y). Good points are black, while rejected points are red.
243 import matplotlib.pyplot
as plt
244 except ImportError
as e:
245 self.log.warn(
"Unable to import matplotlib: %s", e)
248 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
249 x1 = np.array([ff.getX()
for ff
in fit])
250 y1 = np.array([ff.getY()
for ff
in fit])
251 x2 = np.array([m.second.getCentroid().getX()
for m
in matches])
252 y2 = np.array([m.second.getCentroid().getY()
for m
in matches])
257 good = np.logical_not(rejected)
259 figure = plt.figure()
260 axes = figure.add_subplot(2, 2, 1)
261 axes.plot(x2[good], dx[good],
'ko')
262 axes.plot(x2[rejected], dx[rejected],
'ro')
264 axes.set_ylabel(
"dx")
266 axes = figure.add_subplot(2, 2, 2)
267 axes.plot(x2[good], dy[good],
'ko')
268 axes.plot(x2[rejected], dy[rejected],
'ro')
270 axes.set_ylabel(
"dy")
272 axes = figure.add_subplot(2, 2, 3)
273 axes.plot(y2[good], dx[good],
'ko')
274 axes.plot(y2[rejected], dx[rejected],
'ro')
276 axes.set_ylabel(
"dx")
278 axes = figure.add_subplot(2, 2, 4)
279 axes.plot(y2[good], dy[good],
'ko')
280 axes.plot(y2[rejected], dy[rejected],
'ro')
282 axes.set_ylabel(
"dy")
def fitWcs
Fit a TAN-SIP WCS from a list of reference object/source matches.
Implementation of the WCS standard for a any projection.
A coordinate class intended to represent absolute positions.
An integer coordinate rectangle.
CreateWcsWithSip< MatchT > makeCreateWcsWithSip(std::vector< MatchT > const &matches, afw::image::Wcs const &linearWcs, int const order, afw::geom::Box2I const &bbox=afw::geom::Box2I(), int const ngrid=0)
Factory function for CreateWcsWithSip.
Fit a TAN-SIP WCS given a list of reference object/source matches.
A class to handle Icrs coordinates (inherits from Coord)