2 __all__ = [
"FitTanSipWcsTask",
"FitTanSipWcsConfig"]
11 import lsst.pipe.base
as pipeBase
12 from .setMatchDistance
import setMatchDistance
13 from .sip
import makeCreateWcsWithSip
17 order = pexConfig.RangeField(
18 doc=
"order of SIP polynomial",
23 numIter = pexConfig.RangeField(
24 doc=
"number of iterations of fitter (which fits X and Y separately, and so benefits from " +
30 numRejIter = pexConfig.RangeField(
31 doc=
"number of rejection iterations",
36 rejSigma = pexConfig.RangeField(
37 doc=
"Number of standard deviations for clipping level",
42 maxScatterArcsec = pexConfig.RangeField(
43 doc=
"maximum median scatter of a WCS fit beyond which the fit fails (arcsec); " +
44 "be generous, as this is only intended to catch catastrophic failures",
60 """!Fit a TAN-SIP WCS given a list of reference object/source matches 62 @anchor FitTanSipWcsTask_ 64 @section meas_astrom_fitTanSipWcs_Contents Contents 66 - @ref meas_astrom_fitTanSipWcs_Purpose 67 - @ref meas_astrom_fitTanSipWcs_Initialize 68 - @ref meas_astrom_fitTanSipWcs_IO 69 - @ref meas_astrom_fitTanSipWcs_Schema 70 - @ref meas_astrom_fitTanSipWcs_Config 71 - @ref meas_astrom_fitTanSipWcs_Example 72 - @ref meas_astrom_fitTanSipWcs_Debug 74 @section meas_astrom_fitTanSipWcs_Purpose Description 76 Fit a TAN-SIP WCS given a list of reference object/source matches. 77 See CreateWithSip.h for information about the fitting algorithm. 79 @section meas_astrom_fitTanSipWcs_Initialize Task initialisation 83 @section meas_astrom_fitTanSipWcs_IO Invoking the Task 87 @section meas_astrom_fitTanSipWcs_Config Configuration parameters 89 See @ref FitTanSipWcsConfig 91 @section meas_astrom_fitTanSipWcs_Example A complete example of using FitTanSipWcsTask 93 FitTanSipWcsTask is a subtask of AstrometryTask, which is called by PhotoCalTask. 94 See \ref pipe_tasks_photocal_Example. 96 @section meas_astrom_fitTanSipWcs_Debug Debug variables 98 FitTanSipWcsTask does not support any debug variables. 100 ConfigClass = FitTanSipWcsConfig
101 _DefaultName =
"fitWcs" 104 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
105 """!Fit a TAN-SIP WCS from a list of reference object/source matches 107 @param[in,out] matches a list of lsst::afw::table::ReferenceMatch 108 The following fields are read: 109 - match.first (reference object) coord 110 - match.second (source) centroid 111 The following fields are written: 112 - match.first (reference object) centroid, 113 - match.second (source) centroid 114 - match.distance (on sky separation, in radians) 115 @param[in] initWcs initial WCS 116 @param[in] bbox the region over which the WCS will be valid (an lsst:afw::geom::Box2I); 117 if None or an empty box then computed from matches 118 @param[in,out] refCat reference object catalog, or None. 119 If provided then all centroids are updated with the new WCS, 120 otherwise only the centroids for ref objects in matches are updated. 121 Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec". 122 @param[in,out] sourceCat source catalog, or None. 123 If provided then coords are updated with the new WCS; 124 otherwise only the coords for sources in matches are updated. 125 Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec". 126 @param[in] exposure Ignored; present for consistency with FitSipDistortionTask. 128 @return an lsst.pipe.base.Struct with the following fields: 129 - wcs the fit WCS as an lsst.afw.geom.Wcs 130 - scatterOnSky median on-sky separation between reference objects and sources in "matches", 131 as an lsst.afw.geom.Angle 140 rejected = np.zeros(len(matches), dtype=bool)
141 for rej
in range(self.config.numRejIter):
142 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
143 wcs = sipObject.getNewWcs()
145 if rejected.sum() == len(rejected):
146 raise RuntimeError(
"All matches rejected in iteration %d" % (rej + 1,))
148 "Iteration {0} of astrometry fitting: rejected {1} outliers, " 149 "out of {2} total matches.".format(
150 rej, rejected.sum(), len(rejected)
154 print(
"Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
155 self.
plotFit(matches, wcs, rejected)
157 sipObject = self.
_fitWcs([mm
for i, mm
in enumerate(matches)
if not rejected[i]], wcs)
158 wcs = sipObject.getNewWcs()
160 print(
"Plotting final fit")
161 self.
plotFit(matches, wcs, rejected)
163 if refCat
is not None:
164 self.log.debug(
"Updating centroids in refCat")
167 self.log.warn(
"Updating reference object centroids in match list; refCat is None")
170 if sourceCat
is not None:
171 self.log.debug(
"Updating coords in sourceCat")
174 self.log.warn(
"Updating source coords in match list; sourceCat is None")
177 self.log.debug(
"Updating distance in match list")
180 scatterOnSky = sipObject.getScatterOnSky()
182 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
183 raise pipeBase.TaskError(
184 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
185 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
187 return pipeBase.Struct(
189 scatterOnSky=scatterOnSky,
193 """Generate a guess Wcs from the astrometric matches 195 We create a Wcs anchored at the center of the matches, with the scale 196 of the input Wcs. This is necessary because matching returns only 197 matches with no estimated Wcs, and the input Wcs is a wild guess. 198 We're using the best of each: positions from the matches, and scale 205 crval += mm.first.getCoord().getVector()
206 crpix /= len(matches)
207 crval /= len(matches)
210 cdMatrix=wcs.getCdMatrix())
213 def _fitWcs(self, matches, wcs):
214 """Fit a Wcs based on the matches and a guess Wcs""" 215 for i
in range(self.config.numIter):
217 wcs = sipObject.getNewWcs()
221 """Flag deviant matches 223 We return a boolean numpy array indicating whether the corresponding 224 match should be rejected. The previous list of rejections is used 225 so we can calculate uncontaminated statistics. 227 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
228 dx = np.array([ff.getX() - mm.second.getCentroid().getX()
for ff, mm
in zip(fit, matches)])
229 dy = np.array([ff.getY() - mm.second.getCentroid().getY()
for ff, mm
in zip(fit, matches)])
230 good = np.logical_not(rejected)
231 return (dx > self.config.rejSigma*dx[good].
std()) | (dy > self.config.rejSigma*dy[good].
std())
236 We create four plots, for all combinations of (dx, dy) against 237 (x, y). Good points are black, while rejected points are red. 240 import matplotlib.pyplot
as plt
241 except ImportError
as e:
242 self.log.warn(
"Unable to import matplotlib: %s", e)
245 fit = [wcs.skyToPixel(m.first.getCoord())
for m
in matches]
246 x1 = np.array([ff.getX()
for ff
in fit])
247 y1 = np.array([ff.getY()
for ff
in fit])
248 x2 = np.array([m.second.getCentroid().getX()
for m
in matches])
249 y2 = np.array([m.second.getCentroid().getY()
for m
in matches])
254 good = np.logical_not(rejected)
256 figure = plt.figure()
257 axes = figure.add_subplot(2, 2, 1)
258 axes.plot(x2[good], dx[good],
'ko')
259 axes.plot(x2[rejected], dx[rejected],
'ro')
261 axes.set_ylabel(
"dx")
263 axes = figure.add_subplot(2, 2, 2)
264 axes.plot(x2[good], dy[good],
'ko')
265 axes.plot(x2[rejected], dy[rejected],
'ro')
267 axes.set_ylabel(
"dy")
269 axes = figure.add_subplot(2, 2, 3)
270 axes.plot(y2[good], dx[good],
'ko')
271 axes.plot(y2[rejected], dx[rejected],
'ro')
273 axes.set_ylabel(
"dx")
275 axes = figure.add_subplot(2, 2, 4)
276 axes.plot(y2[good], dy[good],
'ko')
277 axes.plot(y2[rejected], dy[rejected],
'ro')
279 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)
def _fitWcs(self, matches, wcs)
std::shared_ptr< SkyWcs > makeSkyWcs(Point2D const &crpix, SpherePoint const &crval, Eigen::Matrix2d const &cdMatrix, std::string const &projection="TAN")
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.