22 from __future__
import absolute_import, division, print_function
24 __all__ = [
"AstrometryConfig",
"AstrometryTask"]
26 from builtins
import range
28 import lsst.pex.config
as pexConfig
29 import lsst.pipe.base
as pipeBase
30 import lsst.afw.geom
as afwGeom
31 from .ref_match
import RefMatchTask, RefMatchConfig
32 from .fitTanSipWcs
import FitTanSipWcsTask
33 from .display
import displayAstrometry
37 wcsFitter = pexConfig.ConfigurableField(
38 target=FitTanSipWcsTask,
41 forceKnownWcs = pexConfig.Field(
43 doc=
"If True then load reference objects and match sources but do not fit a WCS; " +
44 " this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
47 maxIter = pexConfig.RangeField(
48 doc=
"maximum number of iterations of match sources and fit WCS" +
49 "ignored if not fitting a WCS",
54 minMatchDistanceArcSec = pexConfig.RangeField(
55 doc=
"the match distance below which further iteration is pointless (arcsec); "
56 "ignored if not fitting a WCS",
72 """!Match an input source catalog with objects from a reference catalog and solve for the WCS
74 @anchor AstrometryTask_
76 @section meas_astrom_astrometry_Contents Contents
78 - @ref meas_astrom_astrometry_Purpose
79 - @ref meas_astrom_astrometry_Initialize
80 - @ref meas_astrom_astrometry_IO
81 - @ref meas_astrom_astrometry_Config
82 - @ref meas_astrom_astrometry_Example
83 - @ref meas_astrom_astrometry_Debug
85 @section meas_astrom_astrometry_Purpose Description
87 Match input sourceCat with a reference catalog and solve for the Wcs
89 There are three steps, each performed by different subtasks:
90 - Find position reference stars that overlap the exposure
91 - Match sourceCat to position reference stars
92 - Fit a WCS based on the matches
94 @section meas_astrom_astrometry_Initialize Task initialisation
98 @section meas_astrom_astrometry_IO Invoking the Task
102 @copydoc loadAndMatch
104 @section meas_astrom_astrometry_Config Configuration parameters
106 See @ref AstrometryConfig
108 @section meas_astrom_astrometry_Example A complete example of using AstrometryTask
110 See \ref pipe_tasks_photocal_Example.
112 @section meas_astrom_astrometry_Debug Debug variables
114 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
115 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
118 The available variables in AstrometryTask are:
120 <DT> @c display (bool)
121 <DD> If True display information at three stages: after finding reference objects,
122 after matching sources to reference objects, and after fitting the WCS; defaults to False
124 <DD> ds9 frame to use to display the reference objects; the next two frames are used
125 to display the match list and the results of the final WCS; defaults to 0
128 To investigate the @ref meas_astrom_astrometry_Debug, put something like
132 debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
133 if name == "lsst.meas.astrom.astrometry":
138 lsstDebug.Info = DebugInfo
140 into your debug.py file and run this task with the @c --debug flag.
142 ConfigClass = AstrometryConfig
143 _DefaultName =
"astrometricSolver"
145 def __init__(self, refObjLoader, schema=None, **kwargs):
146 """!Construct an AstrometryTask
148 @param[in] refObjLoader A reference object loader object
149 @param[in] schema ignored; available for compatibility with an older astrometry task
150 @param[in] kwargs additional keyword arguments for pipe_base Task.\_\_init\_\_
152 RefMatchTask.__init__(self, refObjLoader, schema=schema, **kwargs)
154 if schema
is not None:
155 self.
usedKey = schema.addField(
"calib_astrometryUsed", type=
"Flag",
156 doc=
"set if source was used in astrometric calibration")
160 self.makeSubtask(
"wcsFitter")
163 def run(self, sourceCat, exposure):
164 """!Load reference objects, match sources and optionally fit a WCS
166 This is a thin layer around solve or loadAndMatch, depending on config.forceKnownWcs
168 @param[in,out] exposure exposure whose WCS is to be fit
169 The following are read only:
171 - calib (may be absent)
172 - filter (may be unset)
173 - detector (if wcs is pure tangent; may be absent)
174 The following are updated:
175 - wcs (the initial value is used as an initial guess, and is required)
176 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
177 @return an lsst.pipe.base.Struct with these fields:
178 - refCat reference object catalog of objects that overlap the exposure (with some margin)
179 (an lsst::afw::table::SimpleCatalog)
180 - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch
181 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
182 (an lsst.afw.geom.Angle), or None if config.forceKnownWcs True
183 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
185 if self.config.forceKnownWcs:
186 res = self.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
187 res.scatterOnSky =
None
189 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
193 def solve(self, exposure, sourceCat):
194 """!Load reference objects overlapping an exposure, match to sources and fit a WCS
196 @return an lsst.pipe.base.Struct with these fields:
197 - refCat reference object catalog of objects that overlap the exposure (with some margin)
198 (an lsst::afw::table::SimpleCatalog)
199 - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch
200 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
201 (an lsst.afw.geom.Angle)
202 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
204 @note ignores config.forceKnownWcs
207 debug = lsstDebug.Info(__name__)
209 expMd = self._getExposureMetadata(exposure)
211 loadRes = self.refObjLoader.loadPixelBox(
214 filterName=expMd.filterName,
217 matchMeta = self.refObjLoader.getMetadataBox(
220 filterName=expMd.filterName,
225 frame = int(debug.frame)
227 refCat=loadRes.refCat,
232 title=
"Reference catalog",
237 match_tolerance =
None
238 for i
in range(self.config.maxIter):
242 refCat=loadRes.refCat,
244 refFluxField=loadRes.fluxField,
248 match_tolerance=match_tolerance,
250 except Exception
as e:
253 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
259 match_tolerance = tryRes.match_tolerance
260 tryMatchDist = self._computeMatchStatsOnSky(tryRes.matches)
262 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; "
263 "max match distance = %0.3f arcsec",
264 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
265 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
267 maxMatchDist = tryMatchDist.maxMatchDist
270 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
272 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
273 "that's good enough",
274 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
276 match_tolerance.maxMatchDist = maxMatchDist
279 "Matched and fit WCS in %d iterations; "
280 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
281 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
282 tryMatchDist.distStdDev.asArcseconds()))
284 for m
in res.matches:
286 m.second.set(self.
usedKey,
True)
287 exposure.setWcs(res.wcs)
289 return pipeBase.Struct(
290 refCat=loadRes.refCat,
292 scatterOnSky=res.scatterOnSky,
297 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance,
299 """!Match sources to reference objects and fit a WCS
301 @param[in] refCat catalog of reference objects
302 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
303 @param[in] refFluxField field of refCat to use for flux
304 @param[in] bbox bounding box of exposure (an lsst.afw.geom.Box2I)
305 @param[in] wcs initial guess for WCS of exposure (an lsst.afw.image.Wcs)
306 @param[in] match_tolerance a MatchTolerance object (or None) specifying
307 internal tolerances to the matcher. See the MatchTolerance
308 definition in the respective matcher for the class definition.
309 @param[in] exposure exposure whose WCS is to be fit, or None; used only for the debug display
311 @return an lsst.pipe.base.Struct with these fields:
312 - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch
313 - wcs the fit WCS (an lsst.afw.image.Wcs)
314 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
315 (an lsst.afw.geom.Angle)
318 debug = lsstDebug.Info(__name__)
319 matchRes = self.matcher.matchObjectsToSources(
323 refFluxField=refFluxField,
324 match_tolerance=match_tolerance,
326 self.log.debug(
"Found %s matches", len(matchRes.matches))
328 frame = int(debug.frame)
331 sourceCat=matchRes.usableSourceCat,
332 matches=matchRes.matches,
339 self.log.debug(
"Fitting WCS")
340 fitRes = self.wcsFitter.fitWcs(
341 matches=matchRes.matches,
349 scatterOnSky = fitRes.scatterOnSky
351 frame = int(debug.frame)
354 sourceCat=matchRes.usableSourceCat,
355 matches=matchRes.matches,
359 title=
"Fit TAN-SIP WCS",
362 return pipeBase.Struct(
363 matches=matchRes.matches,
365 scatterOnSky=scatterOnSky,
366 match_tolerance=matchRes.match_tolerance,
def _matchAndFitWcs
Match sources to reference objects and fit a WCS.
def __init__
Construct an AstrometryTask.
def run
Load reference objects, match sources and optionally fit a WCS.
def solve
Load reference objects overlapping an exposure, match to sources and fit a WCS.
Match an input source catalog with objects from a reference catalog and solve for the WCS...