22__all__ = [
"FitSipDistortionTask",
"FitSipDistortionConfig"]
31from lsst.utils.timer
import timeMethod
33from ._measAstromLib
import (OutlierRejectionControl,
34 ScaledPolynomialTransformFitter,
35 SipForwardTransform, SipReverseTransform,
36 makeMatchStatisticsInRadians, makeWcs)
38from .setMatchDistance
import setMatchDistance
42 """Config for FitSipDistortionTask"""
44 doc=
"Order of SIP polynomial",
50 doc=
"Number of rejection iterations",
56 doc=
"Number of standard deviations for clipping level",
62 doc=
"Minimum number of matches to reject when sigma-clipping",
67 doc=
"Maximum number of matches to reject when sigma-clipping",
72 doc=
"Maximum median scatter of a WCS fit beyond which the fit fails (arcsec); "
73 "be generous, as this is only intended to catch catastrophic failures",
79 doc=
"RMS uncertainty in reference catalog positions, in pixels. Will be added "
80 "in quadrature with measured uncertainties in the fit.",
85 doc=
"Number of X grid points used to invert the SIP reverse transform.",
90 doc=
"Number of Y grid points used to invert the SIP reverse transform.",
95 doc=
"When setting the gird region, how much to extend the image "
96 "bounding box (in pixels) before transforming it to intermediate "
97 "world coordinates using the initial WCS.",
104 """Fit a TAN-SIP WCS given a list of reference object/source matches.
106 ConfigClass = FitSipDistortionConfig
107 _DefaultName = "fitWcs"
110 lsst.pipe.base.Task.__init__(self, **kwargs)
117 def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
118 """Fit a TAN-SIP WCS from a list of reference object/source matches.
123 A sequence of reference object/source matches.
124 The following fields are read:
125 - match.first (reference object) coord
126 - match.second (source) centroid
128 The following fields are written:
129 - match.first (reference object) centroid
130 - match.second (source) centroid
131 - match.distance (on sky separation, in radians)
134 An initial WCS whose CD matrix
is used
as the final CD matrix.
136 The region over which the WCS will be valid (PARENT pixel coordinates);
137 if `
None`
or an empty box then computed
from matches
139 Reference object catalog,
or `
None`.
140 If provided then all centroids are updated
with the new WCS,
141 otherwise only the centroids
for ref objects
in matches are updated.
142 Required fields are
"centroid_x",
"centroid_y",
"coord_ra",
and "coord_dec".
144 Source catalog,
or `
None`.
145 If provided then coords are updated
with the new WCS;
146 otherwise only the coords
for sources
in matches are updated.
147 Required input fields are
"slot_Centroid_x",
"slot_Centroid_y",
148 "slot_Centroid_xErr",
"slot_Centroid_yErr",
and optionally
149 "slot_Centroid_x_y_Cov". The
"coord_ra" and "coord_dec" fields
150 will be updated but are
not used
as input.
152 An Exposure
or other displayable image on which matches can be
153 overplotted. Ignored (
and may be `
None`)
if display-based debugging
154 is not enabled via lsstDebug.
158 An lsst.pipe.base.Struct
with the following fields:
162 The median on-sky separation between reference objects
and
172 for match
in matches:
173 bbox.include(match.second.getCentroid())
185 revFitter = ScaledPolynomialTransformFitter.fromMatches(self.config.order, matches, wcs,
186 self.config.refUncertainty)
188 for nIter
in range(self.config.numRejIter):
189 revFitter.updateModel()
190 intrinsicScatter = revFitter.updateIntrinsicScatter()
193 "Iteration %s: intrinsic scatter is %4.3f pixels, "
194 "rejected %d outliers at %3.2f sigma.",
195 nIter+1, intrinsicScatter, nRejected, clippedSigma
198 displayFrame = self.
display(revFitter, exposure=exposure, bbox=bbox,
199 frame=displayFrame, displayPause=displayPause)
201 revScaledPoly = revFitter.getTransform()
205 sipReverse = SipReverseTransform.convert(revScaledPoly, wcs.getPixelOrigin(), cdMatrix)
213 gridBBoxPix.grow(self.config.gridBorder)
219 for point
in gridBBoxPix.getCorners():
221 gridBBoxIwc.include(cdMatrix(point))
222 fwdFitter = ScaledPolynomialTransformFitter.fromGrid(self.config.order, gridBBoxIwc,
223 self.config.nGridX, self.config.nGridY,
227 fwdScaledPoly = fwdFitter.getTransform()
228 sipForward = SipForwardTransform.convert(fwdScaledPoly, wcs.getPixelOrigin(), cdMatrix)
232 wcs = makeWcs(sipForward, sipReverse, wcs.getSkyOrigin())
234 if refCat
is not None:
235 self.log.debug(
"Updating centroids in refCat")
238 self.log.warning(
"Updating reference object centroids in match list; refCat is None")
241 if sourceCat
is not None:
242 self.log.debug(
"Updating coords in sourceCat")
245 self.log.warning(
"Updating source coords in match list; sourceCat is None")
248 self.log.debug(
"Updating distance in match list")
249 setMatchDistance(matches)
251 stats = makeMatchStatisticsInRadians(wcs, matches, lsst.afw.math.MEDIAN)
252 scatterOnSky = stats.getValue()*lsst.geom.radians
254 if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
255 raise lsst.pipe.base.TaskError(
256 "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
257 (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))
259 return lsst.pipe.base.Struct(
261 scatterOnSky=scatterOnSky,
264 def display(self, revFitter, exposure=None, bbox=None, frame=0, pause=True):
265 """Display positions and outlier status overlaid on an image.
267 This method is called by fitWcs when display debugging
is enabled. It
268 always drops into pdb before returning to allow interactive inspection,
269 and hence it should never be called
in non-interactive contexts.
273 revFitter : :cpp:
class:`lsst::meas::astrom::ScaledPolynomialTransformFitter`
274 Fitter object initialized
with `fromMatches`
for fitting a
"reverse"
275 distortion: the mapping
from intermediate world coordinates to
277 exposure : :cpp:
class:`lsst::afw::image::Exposure`
278 An Exposure
or other displayable image on which matches can be
280 bbox : :cpp:
class:`lsst::afw::geom::Box2I`
281 Bounding box of the region on which matches should be plotted.
283 data = revFitter.getData()
284 disp = lsst.afw.display.getDisplay(frame=frame)
285 if exposure
is not None:
287 elif bbox
is not None:
288 disp.mtv(exposure=lsst.afw.image.ExposureF(bbox))
290 raise TypeError(
"At least one of 'exposure' and 'bbox' must be provided.")
291 data = revFitter.getData()
293 srcErrKey = lsst.afw.table.CovarianceMatrix2fKey(data.schema[
"src"], [
"x",
"y"])
296 rejectedKey = data.schema.find(
"rejected").key
297 with disp.Buffering():
299 colors = ((lsst.afw.display.RED, lsst.afw.display.GREEN)
300 if not record.get(rejectedKey)
else
301 (lsst.afw.display.MAGENTA, lsst.afw.display.CYAN))
302 rx, ry = record.get(refKey)
303 disp.dot(
"x", rx, ry, size=10, ctype=colors[0])
304 mx, my = record.get(modelKey)
305 disp.dot(
"o", mx, my, size=10, ctype=colors[0])
306 disp.line([(rx, ry), (mx, my)], ctype=colors[0])
307 sx, sy = record.get(srcKey)
308 sErr = record.get(srcErrKey)
310 disp.dot(sEllipse, sx, sy, ctype=colors[1])
311 if pause
or pause
is None:
312 print(
"Dropping into debugger to allow inspection of display. Type 'continue' when done.")
320 """Generate a guess Wcs from the astrometric matches
322 We create a Wcs anchored at the center of the matches, with the scale
323 of the input Wcs. This
is necessary because the Wcs may have a very
324 approximation position (
as is common
with telescoped-generated Wcs).
325 We
're using the best of each: positions from the matches, and scale
330 matches : list of :cpp:
class:`lsst::afw::table::ReferenceMatch`
331 A sequence of reference object/source matches.
332 The following fields are read:
334 - match.first (reference object) coord
335 - match.second (source) centroid
337 wcs : :cpp:
class:`lsst::afw::geom::SkyWcs`
338 An initial WCS whose CD matrix
is used
as the CD matrix of the
350 crval += mm.first.getCoord().getVector()
351 crpix /= len(matches)
352 crval /= len(matches)
353 cd = wcs.getCdMatrix()
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
An ellipse core with quadrupole moments as parameters.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
A class representing an angle.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
makeInitialWcs(self, matches, wcs)
fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None)
display(self, revFitter, exposure=None, bbox=None, frame=0, pause=True)
Vector3d is a vector in ℝ³ with components stored in double precision.
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
Update centroids in a collection of reference objects.
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
Update sky coordinates in a collection of source objects.
Lightweight representation of a geometric match between two records.