22 from __future__
import absolute_import, division, print_function
23 from builtins
import range
27 from .ref_match
import RefMatchTask, RefMatchConfig
28 from .fitTanSipWcs
import FitTanSipWcsTask
29 from .display
import displayAstrometry
30 from .createMatchMetadata
import createMatchMetadata
34 wcsFitter = pexConfig.ConfigurableField(
35 target=FitTanSipWcsTask,
38 forceKnownWcs = pexConfig.Field(
40 doc=
"If True then load reference objects and match sources but do not fit a WCS; " +
41 " this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
44 maxIter = pexConfig.RangeField(
45 doc=
"maximum number of iterations of match sources and fit WCS" +
46 "ignored if not fitting a WCS",
51 minMatchDistanceArcSec = pexConfig.RangeField(
52 doc=
"the match distance below which further iteration is pointless (arcsec); "
53 "ignored if not fitting a WCS",
69 """!Match an input source catalog with objects from a reference catalog and solve for the WCS
71 @anchor AstrometryTask_
73 @section meas_astrom_astrometry_Contents Contents
75 - @ref meas_astrom_astrometry_Purpose
76 - @ref meas_astrom_astrometry_Initialize
77 - @ref meas_astrom_astrometry_IO
78 - @ref meas_astrom_astrometry_Config
79 - @ref meas_astrom_astrometry_Example
80 - @ref meas_astrom_astrometry_Debug
82 @section meas_astrom_astrometry_Purpose Description
84 Match input sourceCat with a reference catalog and solve for the Wcs
86 There are three steps, each performed by different subtasks:
87 - Find position reference stars that overlap the exposure
88 - Match sourceCat to position reference stars
89 - Fit a WCS based on the matches
91 @section meas_astrom_astrometry_Initialize Task initialisation
95 @section meas_astrom_astrometry_IO Invoking the Task
101 @section meas_astrom_astrometry_Config Configuration parameters
103 See @ref AstrometryConfig
105 @section meas_astrom_astrometry_Example A complete example of using AstrometryTask
107 See \ref meas_photocal_photocal_Example.
109 @section meas_astrom_astrometry_Debug Debug variables
111 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
112 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
115 The available variables in AstrometryTask are:
117 <DT> @c display (bool)
118 <DD> If True display information at three stages: after finding reference objects,
119 after matching sources to reference objects, and after fitting the WCS; defaults to False
121 <DD> ds9 frame to use to display the reference objects; the next two frames are used
122 to display the match list and the results of the final WCS; defaults to 0
125 To investigate the @ref meas_astrom_astrometry_Debug, put something like
129 debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
130 if name == "lsst.meas.astrom.astrometry":
135 lsstDebug.Info = DebugInfo
137 into your debug.py file and run this task with the @c --debug flag.
139 ConfigClass = AstrometryConfig
140 _DefaultName =
"astrometricSolver"
142 def __init__(self, refObjLoader, schema=None, **kwargs):
143 """!Construct an AstrometryTask
145 @param[in] refObjLoader A reference object loader object
146 @param[in] schema ignored; available for compatibility with an older astrometry task
147 @param[in] kwargs additional keyword arguments for pipe_base Task.\_\_init\_\_
149 RefMatchTask.__init__(self, refObjLoader, schema=schema, **kwargs)
150 self.makeSubtask(
"wcsFitter")
153 def run(self, sourceCat, exposure):
154 """!Load reference objects, match sources and optionally fit a WCS
156 This is a thin layer around solve or loadAndMatch, depending on config.forceKnownWcs
158 @param[in,out] exposure exposure whose WCS is to be fit
159 The following are read only:
161 - calib (may be absent)
162 - filter (may be unset)
163 - detector (if wcs is pure tangent; may be absent)
164 The following are updated:
165 - wcs (the initial value is used as an initial guess, and is required)
166 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
167 @return an lsst.pipe.base.Struct with these fields:
168 - refCat reference object catalog of objects that overlap the exposure (with some margin)
169 (an lsst::afw::table::SimpleCatalog)
170 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
171 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
172 (an lsst.afw.geom.Angle), or None if config.forceKnownWcs True
173 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
175 if self.config.forceKnownWcs:
176 res = self.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
177 res.scatterOnSky =
None
179 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
183 def solve(self, exposure, sourceCat):
184 """!Load reference objects overlapping an exposure, match to sources and fit a WCS
186 @return an lsst.pipe.base.Struct with these fields:
187 - refCat reference object catalog of objects that overlap the exposure (with some margin)
188 (an lsst::afw::table::SimpleCatalog)
189 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
190 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
191 (an lsst.afw.geom.Angle)
192 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
194 @note ignores config.forceKnownWcs
200 expMd = self._getExposureMetadata(exposure)
202 loadRes = self.refObjLoader.loadPixelBox(
205 filterName=expMd.filterName,
209 frame = int(debug.frame)
211 refCat=loadRes.refCat,
216 title=
"Reference catalog",
222 for i
in range(self.config.maxIter):
226 refCat=loadRes.refCat,
228 refFluxField=loadRes.fluxField,
232 maxMatchDist=maxMatchDist,
234 except Exception
as e:
237 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
243 tryMatchDist = self._computeMatchStatsOnSky(tryRes.matches)
245 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; "
246 "max match distance = %0.3f arcsec",
247 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
248 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
249 if maxMatchDist
is not None:
250 if tryMatchDist.maxMatchDist >= maxMatchDist:
252 "Iteration %d had no better maxMatchDist; using previous iteration", iterNum)
256 maxMatchDist = tryMatchDist.maxMatchDist
259 if tryMatchDist.maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
261 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
262 "that's good enough",
263 tryMatchDist.maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
267 "Matched and fit WCS in %d iterations; "
268 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
269 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
270 tryMatchDist.distStdDev.asArcseconds()))
272 exposure.setWcs(res.wcs)
274 return pipeBase.Struct(
275 refCat=loadRes.refCat,
277 scatterOnSky=res.scatterOnSky,
282 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, maxMatchDist=None,
284 """!Match sources to reference objects and fit a WCS
286 @param[in] refCat catalog of reference objects
287 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
288 @param[in] refFluxField field of refCat to use for flux
289 @param[in] bbox bounding box of exposure (an lsst.afw.geom.Box2I)
290 @param[in] wcs initial guess for WCS of exposure (an lsst.afw.image.Wcs)
291 @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
292 (an lsst.afw.geom.Angle); if None then use the matcher's default
293 @param[in] exposure exposure whose WCS is to be fit, or None; used only for the debug display
295 @return an lsst.pipe.base.Struct with these fields:
296 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
297 - wcs the fit WCS (an lsst.afw.image.Wcs)
298 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
299 (an lsst.afw.geom.Angle)
303 matchRes = self.matcher.matchObjectsToSources(
307 refFluxField=refFluxField,
308 maxMatchDist=maxMatchDist,
310 self.log.debug(
"Found %s matches", len(matchRes.matches))
312 frame = int(debug.frame)
315 sourceCat=matchRes.usableSourceCat,
316 matches=matchRes.matches,
323 self.log.debug(
"Fitting WCS")
324 fitRes = self.wcsFitter.fitWcs(
325 matches=matchRes.matches,
333 scatterOnSky = fitRes.scatterOnSky
335 frame = int(debug.frame)
338 sourceCat=matchRes.usableSourceCat,
339 matches=matchRes.matches,
343 title=
"Fit TAN-SIP WCS",
346 return pipeBase.Struct(
347 matches=matchRes.matches,
349 scatterOnSky=scatterOnSky,
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...