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):
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".
131 @return an lsst.pipe.base.Struct with the following fields:
132 - wcs the fit WCS as an lsst.afw.image.Wcs
133 - scatterOnSky median on-sky separation between reference objects and sources in "matches",
134 as an lsst.afw.geom.Angle
143 rejected = np.zeros(len(matches), dtype=bool)
144 for rej
in range(self.config.numRejIter):
145 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
146 wcs = sipObject.getNewWcs()
148 if rejected.sum() == len(rejected):
149 raise RuntimeError(
"All matches rejected in iteration %d" % (rej + 1,))
151 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
152 self.
plotFit(matches, wcs, rejected)
154 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
155 wcs = sipObject.getNewWcs()
157 print(
"Plotting final fit")
158 self.
plotFit(matches, wcs, rejected)
160 if refCat
is not None:
161 self.log.debug(
"Updating centroids in refCat")
162 afwTable.updateRefCentroids(wcs, refList=refCat)
164 self.log.warn(
"Updating reference object centroids in match list; refCat is None")
165 afwTable.updateRefCentroids(wcs, refList=[match.first
for match
in matches])
167 if sourceCat
is not None:
168 self.log.debug(
"Updating coords in sourceCat")
169 afwTable.updateSourceCoords(wcs, sourceList=sourceCat)
171 self.log.warn(
"Updating source coords in match list; sourceCat is None")
172 afwTable.updateSourceCoords(wcs, sourceList=[match.second
for match
in matches])
174 self.log.debug(
"Updating distance in match list")
177 scatterOnSky = sipObject.getScatterOnSky()
179 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
180 raise pipeBase.TaskError(
181 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
182 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
184 return pipeBase.Struct(
186 scatterOnSky=scatterOnSky,
190 """Generate a guess Wcs from the astrometric matches
192 We create a Wcs anchored at the center of the matches, with the scale
193 of the input Wcs. This is necessary because matching returns only
194 matches with no estimated Wcs, and the input Wcs is a wild guess.
195 We're using the best of each: positions from the matches, and scale
203 crpix /= len(matches)
204 crval /= len(matches)
210 """Fit a Wcs based on the matches and a guess Wcs"""
211 for i
in range(self.config.numIter):
213 wcs = sipObject.getNewWcs()
217 """Flag deviant matches
219 We return a boolean numpy array indicating whether the corresponding
220 match should be rejected. The previous list of rejections is used
221 so we can calculate uncontaminated statistics.
223 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
224 dx = np.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
225 dy = np.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
226 good = np.logical_not(rejected)
227 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
232 We create four plots, for all combinations of (dx, dy) against
233 (x, y). Good points are black, while rejected points are red.
235 import matplotlib.pyplot
as plt
237 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
238 x1 = np.array([ff.getX()
for ff
in fit])
239 y1 = np.array([ff.getY()
for ff
in fit])
240 x2 = np.array([m.second.getCentroid().getX()
for m
in matches])
241 y2 = np.array([m.second.getCentroid().getY()
for m
in matches])
246 good = np.logical_not(rejected)
248 figure = plt.figure()
249 axes = figure.add_subplot(2, 2, 1)
250 axes.plot(x2[good], dx[good],
'ko')
251 axes.plot(x2[rejected], dx[rejected],
'ro')
253 axes.set_ylabel(
"dx")
255 axes = figure.add_subplot(2, 2, 2)
256 axes.plot(x2[good], dy[good],
'ko')
257 axes.plot(x2[rejected], dy[rejected],
'ro')
259 axes.set_ylabel(
"dy")
261 axes = figure.add_subplot(2, 2, 3)
262 axes.plot(y2[good], dx[good],
'ko')
263 axes.plot(y2[rejected], dx[rejected],
'ro')
265 axes.set_ylabel(
"dx")
267 axes = figure.add_subplot(2, 2, 4)
268 axes.plot(y2[good], dy[good],
'ko')
269 axes.plot(y2[rejected], dy[rejected],
'ro')
271 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)