1 from __future__
import absolute_import, division, print_function
3 __all__ = [
"FitTanSipWcsTask",
"FitTanSipWcsConfig"]
5 from builtins
import zip
6 from builtins
import range
13 import lsst.pipe.base
as pipeBase
14 from .setMatchDistance
import setMatchDistance
15 from .sip
import makeCreateWcsWithSip
19 order = pexConfig.RangeField(
20 doc=
"order of SIP polynomial",
25 numIter = pexConfig.RangeField(
26 doc=
"number of iterations of fitter (which fits X and Y separately, and so benefits from " +
32 numRejIter = pexConfig.RangeField(
33 doc=
"number of rejection iterations",
38 rejSigma = pexConfig.RangeField(
39 doc=
"Number of standard deviations for clipping level",
44 maxScatterArcsec = pexConfig.RangeField(
45 doc=
"maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " +
46 "be generous, as this is only intended to catch catastrophic failures",
62 """!Fit a TAN-SIP WCS given a list of reference object/source matches 64 @anchor FitTanSipWcsTask_ 66 @section meas_astrom_fitTanSipWcs_Contents Contents 68 - @ref meas_astrom_fitTanSipWcs_Purpose 69 - @ref meas_astrom_fitTanSipWcs_Initialize 70 - @ref meas_astrom_fitTanSipWcs_IO 71 - @ref meas_astrom_fitTanSipWcs_Schema 72 - @ref meas_astrom_fitTanSipWcs_Config 73 - @ref meas_astrom_fitTanSipWcs_Example 74 - @ref meas_astrom_fitTanSipWcs_Debug 76 @section meas_astrom_fitTanSipWcs_Purpose Description 78 Fit a TAN-SIP WCS given a list of reference object/source matches. 79 See CreateWithSip.h for information about the fitting algorithm. 81 @section meas_astrom_fitTanSipWcs_Initialize Task initialisation 85 @section meas_astrom_fitTanSipWcs_IO Invoking the Task 89 @section meas_astrom_fitTanSipWcs_Config Configuration parameters 91 See @ref FitTanSipWcsConfig 93 @section meas_astrom_fitTanSipWcs_Example A complete example of using FitTanSipWcsTask 95 FitTanSipWcsTask is a subtask of AstrometryTask, which is called by PhotoCalTask. 96 See \ref pipe_tasks_photocal_Example. 98 @section meas_astrom_fitTanSipWcs_Debug Debug variables 100 FitTanSipWcsTask does not support any debug variables. 102 ConfigClass = FitTanSipWcsConfig
103 _DefaultName =
"fitWcs" 106 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
107 """!Fit a TAN-SIP WCS from a list of reference object/source matches 109 @param[in,out] matches a list of lsst::afw::table::ReferenceMatch 110 The following fields are read: 111 - match.first (reference object) coord 112 - match.second (source) centroid 113 The following fields are written: 114 - match.first (reference object) centroid, 115 - match.second (source) centroid 116 - match.distance (on sky separation, in radians) 117 @param[in] initWcs initial WCS 118 @param[in] bbox the region over which the WCS will be valid (an lsst:afw::geom::Box2I); 119 if None or an empty box then computed from matches 120 @param[in,out] refCat reference object catalog, or None. 121 If provided then all centroids are updated with the new WCS, 122 otherwise only the centroids for ref objects in matches are updated. 123 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec". 124 @param[in,out] sourceCat source catalog, or None. 125 If provided then coords are updated with the new WCS; 126 otherwise only the coords for sources in matches are updated. 127 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec". 128 @param[in] exposure Ignored; present for consistency with FitSipDistortionTask. 130 @return an lsst.pipe.base.Struct with the following fields: 131 - wcs the fit WCS as an lsst.afw.geom.Wcs 132 - scatterOnSky median on-sky separation between reference objects and sources in "matches", 133 as an lsst.afw.geom.Angle 142 rejected = np.zeros(len(matches), dtype=bool)
143 for rej
in range(self.config.numRejIter):
144 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
145 wcs = sipObject.getNewWcs()
147 if rejected.sum() == len(rejected):
148 raise RuntimeError(
"All matches rejected in iteration %d" % (rej + 1,))
150 "Iteration {0} of astrometry fitting: rejected {1} outliers, " 151 "out of {2} total matches.".format(
152 rej, rejected.sum(), len(rejected)
156 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
157 self.
plotFit(matches, wcs, rejected)
159 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
160 wcs = sipObject.getNewWcs()
162 print(
"Plotting final fit")
163 self.
plotFit(matches, wcs, rejected)
165 if refCat
is not None:
166 self.log.debug(
"Updating centroids in refCat")
169 self.log.warn(
"Updating reference object centroids in match list; refCat is None")
172 if sourceCat
is not None:
173 self.log.debug(
"Updating coords in sourceCat")
176 self.log.warn(
"Updating source coords in match list; sourceCat is None")
179 self.log.debug(
"Updating distance in match list")
182 scatterOnSky = sipObject.getScatterOnSky()
184 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
185 raise pipeBase.TaskError(
186 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
187 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
189 return pipeBase.Struct(
191 scatterOnSky=scatterOnSky,
195 """Generate a guess Wcs from the astrometric matches 197 We create a Wcs anchored at the center of the matches, with the scale 198 of the input Wcs. This is necessary because matching returns only 199 matches with no estimated Wcs, and the input Wcs is a wild guess. 200 We're using the best of each: positions from the matches, and scale 208 crpix /= len(matches)
209 crval /= len(matches)
212 cdMatrix=wcs.getCdMatrix())
215 def _fitWcs(self, matches, wcs):
216 """Fit a Wcs based on the matches and a guess Wcs""" 217 for i
in range(self.config.numIter):
219 wcs = sipObject.getNewWcs()
223 """Flag deviant matches 225 We return a boolean numpy array indicating whether the corresponding 226 match should be rejected. The previous list of rejections is used 227 so we can calculate uncontaminated statistics. 229 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
230 dx = np.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
231 dy = np.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
232 good = np.logical_not(rejected)
233 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
238 We create four plots, for all combinations of (dx, dy) against 239 (x, y). Good points are black, while rejected points are red. 242 import matplotlib.pyplot
as plt
243 except ImportError
as e:
244 self.log.warn(
"Unable to import matplotlib: %s", e)
247 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
248 x1 = np.array([ff.getX()
for ff
in fit])
249 y1 = np.array([ff.getY()
for ff
in fit])
250 x2 = np.array([m.second.getCentroid().getX()
for m
in matches])
251 y2 = np.array([m.second.getCentroid().getY()
for m
in matches])
256 good = np.logical_not(rejected)
258 figure = plt.figure()
259 axes = figure.add_subplot(2, 2, 1)
260 axes.plot(x2[good], dx[good],
'ko')
261 axes.plot(x2[rejected], dx[rejected],
'ro')
263 axes.set_ylabel(
"dx")
265 axes = figure.add_subplot(2, 2, 2)
266 axes.plot(x2[good], dy[good],
'ko')
267 axes.plot(x2[rejected], dy[rejected],
'ro')
269 axes.set_ylabel(
"dy")
271 axes = figure.add_subplot(2, 2, 3)
272 axes.plot(y2[good], dx[good],
'ko')
273 axes.plot(y2[rejected], dx[rejected],
'ro')
275 axes.set_ylabel(
"dx")
277 axes = figure.add_subplot(2, 2, 4)
278 axes.plot(y2[good], dy[good],
'ko')
279 axes.plot(y2[rejected], dy[rejected],
'ro')
281 axes.set_ylabel(
"dy")
def plotFit(self, matches, wcs, rejected)
CreateWcsWithSip< MatchT > makeCreateWcsWithSip(std::vector< MatchT > const &matches, afw::geom::SkyWcs const &linearWcs, int const order, afw::geom::Box2I const &bbox=afw::geom::Box2I(), int const ngrid=0)
Factory function for CreateWcsWithSip.
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)
std::shared_ptr< SkyWcs > makeSkyWcs(Point2D const &crpix, SpherePoint const &crval, Eigen::Matrix2d const &cdMatrix, std::string const &projection="TAN")
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)
Fit a TAN-SIP WCS given a list of reference object/source matches.