1 from __future__
import absolute_import, division, print_function
10 from .setMatchDistance
import setMatchDistance
11 from .sip
import makeCreateWcsWithSip
13 __all__ = [
"FitTanSipWcsTask",
"FitTanSipWcsConfig"]
16 order = pexConfig.RangeField(
17 doc =
"order of SIP polynomial",
22 numIter = pexConfig.RangeField(
23 doc =
"number of iterations of fitter (which fits X and Y separately, and so benefits from " + \
29 numRejIter = pexConfig.RangeField(
30 doc =
"number of rejection iterations",
35 rejSigma = pexConfig.RangeField(
36 doc =
"Number of standard deviations for clipping level",
41 maxScatterArcsec = pexConfig.RangeField(
42 doc =
"maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " +
43 "be generous, as this is only intended to catch catastrophic failures",
58 """!Fit a TAN-SIP WCS given a list of reference object/source matches
60 @anchor FitTanSipWcsTask_
62 @section meas_astrom_fitTanSipWcs_Contents Contents
64 - @ref meas_astrom_fitTanSipWcs_Purpose
65 - @ref meas_astrom_fitTanSipWcs_Initialize
66 - @ref meas_astrom_fitTanSipWcs_IO
67 - @ref meas_astrom_fitTanSipWcs_Schema
68 - @ref meas_astrom_fitTanSipWcs_Config
69 - @ref meas_astrom_fitTanSipWcs_Example
70 - @ref meas_astrom_fitTanSipWcs_Debug
72 @section meas_astrom_fitTanSipWcs_Purpose Description
74 Fit a TAN-SIP WCS given a list of reference object/source matches.
75 See CreateWithSip.h for information about the fitting algorithm.
77 @section meas_astrom_fitTanSipWcs_Initialize Task initialisation
81 @section meas_astrom_fitTanSipWcs_IO Invoking the Task
85 @section meas_astrom_fitTanSipWcs_Config Configuration parameters
87 See @ref FitTanSipWcsConfig
89 @section meas_astrom_fitTanSipWcs_Example A complete example of using FitTanSipWcsTask
91 FitTanSipWcsTask is a subtask of AstrometryTask, which is called by PhotoCalTask.
92 See \ref meas_photocal_photocal_Example.
94 @section meas_astrom_fitTanSipWcs_Debug Debug variables
96 FitTanSipWcsTask does not support any debug variables.
98 ConfigClass = FitTanSipWcsConfig
99 _DefaultName =
"fitWcs"
102 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None):
103 """!Fit a TAN-SIP WCS from a list of reference object/source matches
105 @param[in,out] matches a list of reference object/source matches
106 (an lsst::afw::table::ReferenceMatchVector)
107 The following fields are read:
108 - match.first (reference object) coord
109 - match.second (source) centroid
110 The following fields are written:
111 - match.first (reference object) centroid,
112 - match.second (source) centroid
113 - match.distance (on sky separation, in radians)
114 @param[in] initWcs initial WCS
115 @param[in] bbox the region over which the WCS will be valid (an lsst:afw::geom::Box2I);
116 if None or an empty box then computed from matches
117 @param[in,out] refCat reference object catalog, or None.
118 If provided then all centroids are updated with the new WCS,
119 otherwise only the centroids for ref objects in matches are updated.
120 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
121 @param[in,out] sourceCat source catalog, or None.
122 If provided then coords are updated with the new WCS;
123 otherwise only the coords for sources in matches are updated.
124 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec".
126 @return an lsst.pipe.base.Struct with the following fields:
127 - wcs the fit WCS as an lsst.afw.image.Wcs
128 - scatterOnSky median on-sky separation between reference objects and sources in "matches",
129 as an lsst.afw.geom.Angle
138 rejected = numpy.zeros(len(matches), dtype=bool)
139 for rej
in range(self.config.numRejIter):
140 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
141 wcs = sipObject.getNewWcs()
143 if rejected.sum() == len(rejected):
144 raise RuntimeError(
"All matches rejected in iteration %d" % (rej + 1,))
146 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
147 self.
plotFit(matches, wcs, rejected)
149 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
150 wcs = sipObject.getNewWcs()
152 print(
"Plotting final fit")
153 self.
plotFit(matches, wcs, rejected)
155 if refCat
is not None:
156 self.log.info(
"Updating centroids in refCat")
159 self.log.warning(
"Updating reference object centroids in match list; refCat is None")
162 if sourceCat
is not None:
163 self.log.info(
"Updating coords in sourceCat")
166 self.log.warning(
"Updating source coords in match list; sourceCat is None")
169 self.log.info(
"Updating distance in match list")
172 scatterOnSky = sipObject.getScatterOnSky()
174 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
175 raise pipeBase.TaskError(
176 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
177 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
179 return pipeBase.Struct(
181 scatterOnSky = scatterOnSky,
185 """Generate a guess Wcs from the astrometric matches
187 We create a Wcs anchored at the center of the matches, with the scale
188 of the input Wcs. This is necessary because matching returns only
189 matches with no estimated Wcs, and the input Wcs is a wild guess.
190 We're using the best of each: positions from the matches, and scale
198 crpix /= len(matches)
199 crval /= len(matches)
205 """Fit a Wcs based on the matches and a guess Wcs"""
206 for i
in range(self.config.numIter):
208 wcs = sipObject.getNewWcs()
212 """Flag deviant matches
214 We return a boolean numpy array indicating whether the corresponding
215 match should be rejected. The previous list of rejections is used
216 so we can calculate uncontaminated statistics.
218 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
219 dx = numpy.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
220 dy = numpy.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
221 good = numpy.logical_not(rejected)
222 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
226 """Update centroids in a collection of reference objects, given a WCS
230 schema = refList[0].schema
233 for refObj
in refList:
234 refObj.set(centroidKey, wcs.skyToPixel(refObj.get(coordKey)))
238 """Update coords in a collection of sources, given a WCS
240 if len(sourceList) < 1:
242 schema = sourceList[1].schema
244 for src
in sourceList:
245 src.set(srcCoordKey, wcs.pixelToSky(src.getCentroid()))
250 We create four plots, for all combinations of (dx, dy) against
251 (x, y). Good points are black, while rejected points are red.
253 import matplotlib.pyplot
as plt
255 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
256 x1 = numpy.array([ff.getX()
for ff
in fit])
257 y1 = numpy.array([ff.getY()
for ff
in fit])
258 x2 = numpy.array([m.second.getCentroid().getX()
for m
in matches])
259 y2 = numpy.array([m.second.getCentroid().getY()
for m
in matches])
264 good = numpy.logical_not(rejected)
266 figure = plt.figure()
267 axes = figure.add_subplot(2, 2, 1)
268 axes.plot(x2[good], dx[good],
'ko')
269 axes.plot(x2[rejected], dx[rejected],
'ro')
271 axes.set_ylabel(
"dx")
273 axes = figure.add_subplot(2, 2, 2)
274 axes.plot(x2[good], dy[good],
'ko')
275 axes.plot(x2[rejected], dy[rejected],
'ro')
277 axes.set_ylabel(
"dy")
279 axes = figure.add_subplot(2, 2, 3)
280 axes.plot(y2[good], dx[good],
'ko')
281 axes.plot(y2[rejected], dx[rejected],
'ro')
283 axes.set_ylabel(
"dx")
285 axes = figure.add_subplot(2, 2, 4)
286 axes.plot(y2[good], dy[good],
'ko')
287 axes.plot(y2[rejected], dy[rejected],
'ro')
289 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)
A FunctorKey used to get or set celestial coordiantes from a pair of Angle keys.