22 from __future__
import absolute_import, division, print_function
23 from builtins
import range
30 from .matchOptimisticB
import MatchOptimisticBTask
31 from .fitTanSipWcs
import FitTanSipWcsTask
32 from .display
import displayAstrometry
33 from .astromLib
import makeMatchStatistics
34 from .createMatchMetadata
import createMatchMetadata
38 matcher = pexConfig.ConfigurableField(
39 target=MatchOptimisticBTask,
40 doc=
"reference object/source matcher",
42 wcsFitter = pexConfig.ConfigurableField(
43 target=FitTanSipWcsTask,
46 forceKnownWcs = pexConfig.Field(
48 doc=
"If True then load reference objects and match sources but do not fit a WCS; " +
49 " this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
52 maxIter = pexConfig.RangeField(
53 doc=
"maximum number of iterations of match sources and fit WCS" +
54 "ignored if not fitting a WCS",
59 matchDistanceSigma = pexConfig.RangeField(
60 doc=
"the maximum match distance is set to "
61 " mean_match_distance + matchDistanceSigma*std_dev_match_distance; " +
62 "ignored if not fitting a WCS",
67 minMatchDistanceArcSec = pexConfig.RangeField(
68 doc=
"the match distance below which further iteration is pointless (arcsec); "
69 "ignored if not fitting a WCS",
85 """!Match an input source catalog with objects from a reference catalog and solve for the WCS
87 @anchor AstrometryTask_
89 @section meas_astrom_astrometry_Contents Contents
91 - @ref meas_astrom_astrometry_Purpose
92 - @ref meas_astrom_astrometry_Initialize
93 - @ref meas_astrom_astrometry_IO
94 - @ref meas_astrom_astrometry_Config
95 - @ref meas_astrom_astrometry_Example
96 - @ref meas_astrom_astrometry_Debug
98 @section meas_astrom_astrometry_Purpose Description
100 Match input sourceCat with a reference catalog and solve for the Wcs
102 There are three steps, each performed by different subtasks:
103 - Find position reference stars that overlap the exposure
104 - Match sourceCat to position reference stars
105 - Fit a WCS based on the matches
107 @section meas_astrom_astrometry_Initialize Task initialisation
109 @copydoc \_\_init\_\_
111 @section meas_astrom_astrometry_IO Invoking the Task
115 @copydoc loadAndMatch
117 @section meas_astrom_astrometry_Config Configuration parameters
119 See @ref AstrometryConfig
121 @section meas_astrom_astrometry_Example A complete example of using AstrometryTask
123 See \ref meas_photocal_photocal_Example.
125 @section meas_astrom_astrometry_Debug Debug variables
127 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
128 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
131 The available variables in AstrometryTask are:
133 <DT> @c display (bool)
134 <DD> If True display information at three stages: after finding reference objects,
135 after matching sources to reference objects, and after fitting the WCS; defaults to False
137 <DD> ds9 frame to use to display the reference objects; the next two frames are used
138 to display the match list and the results of the final WCS; defaults to 0
141 To investigate the @ref meas_astrom_astrometry_Debug, put something like
145 debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
146 if name == "lsst.meas.astrom.astrometry":
151 lsstDebug.Info = DebugInfo
153 into your debug.py file and run this task with the @c --debug flag.
155 ConfigClass = AstrometryConfig
156 _DefaultName =
"astrometricSolver"
158 def __init__(self, refObjLoader, schema=None, **kwargs):
159 """!Construct an AstrometryTask
161 @param[in] refObjLoader A reference object loader object
162 @param[in] schema ignored; available for compatibility with an older astrometry task
163 @param[in] kwargs additional keyword arguments for pipe_base Task.\_\_init\_\_
165 pipeBase.Task.__init__(self, **kwargs)
167 self.makeSubtask(
"matcher")
168 self.makeSubtask(
"wcsFitter")
171 def run(self, exposure, sourceCat):
172 """!Load reference objects, match sources and optionally fit a WCS
174 This is a thin layer around solve or loadAndMatch, depending on config.forceKnownWcs
176 @param[in,out] exposure exposure whose WCS is to be fit
177 The following are read only:
179 - calib (may be absent)
180 - filter (may be unset)
181 - detector (if wcs is pure tangent; may be absent)
182 The following are updated:
183 - wcs (the initial value is used as an initial guess, and is required)
184 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
185 @return an lsst.pipe.base.Struct with these fields:
186 - refCat reference object catalog of objects that overlap the exposure (with some margin)
187 (an lsst::afw::table::SimpleCatalog)
188 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
189 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
190 (an lsst.afw.geom.Angle), or None if config.forceKnownWcs True
191 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
193 if self.config.forceKnownWcs:
194 res = self.
loadAndMatch(exposure=exposure, sourceCat=sourceCat)
195 res.scatterOnSky =
None
197 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
202 """!Load reference objects overlapping an exposure and match to sources detected on that exposure
204 @param[in] exposure exposure that the sources overlap
205 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
207 @return an lsst.pipe.base.Struct with these fields:
208 - refCat reference object catalog of objects that overlap the exposure (with some margin)
209 (an lsst::afw::table::SimpleCatalog)
210 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
211 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
213 @note ignores config.forceKnownWcs, config.maxIter, config.matchDistanceSigma
214 and config.minMatchDistanceArcSec
222 loadRes = self.refObjLoader.loadPixelBox(
225 filterName=expMd.filterName,
229 matchRes = self.matcher.matchObjectsToSources(
230 refCat=loadRes.refCat,
233 refFluxField=loadRes.fluxField,
239 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " %
240 (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds())
244 frame = int(debug.frame)
246 refCat=loadRes.refCat,
248 matches=matchRes.matches,
255 return pipeBase.Struct(
256 refCat=loadRes.refCat,
257 matches=matchRes.matches,
262 def solve(self, exposure, sourceCat):
263 """!Load reference objects overlapping an exposure, match to sources and fit a WCS
265 @return an lsst.pipe.base.Struct with these fields:
266 - refCat reference object catalog of objects that overlap the exposure (with some margin)
267 (an lsst::afw::table::SimpleCatalog)
268 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
269 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
270 (an lsst.afw.geom.Angle)
271 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
273 @note ignores config.forceKnownWcs
281 loadRes = self.refObjLoader.loadPixelBox(
284 filterName=expMd.filterName,
288 frame = int(debug.frame)
290 refCat=loadRes.refCat,
295 title=
"Reference catalog",
301 for i
in range(self.config.maxIter):
305 refCat=loadRes.refCat,
307 refFluxField=loadRes.fluxField,
311 maxMatchDist=maxMatchDist,
313 except Exception
as e:
316 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
324 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; "
325 "max match distance = %0.3f arcsec",
326 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
327 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
328 if maxMatchDist
is not None:
329 if tryMatchDist.maxMatchDist >= maxMatchDist:
331 "Iteration %d had no better maxMatchDist; using previous iteration", iterNum)
335 maxMatchDist = tryMatchDist.maxMatchDist
338 if tryMatchDist.maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
340 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
341 "that's good enough",
342 tryMatchDist.maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
346 "Matched and fit WCS in %d iterations; "
347 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
348 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
349 tryMatchDist.distStdDev.asArcseconds()))
351 exposure.setWcs(res.wcs)
353 return pipeBase.Struct(
354 refCat=loadRes.refCat,
356 scatterOnSky=res.scatterOnSky,
361 """Compute on-sky radial distance statistics for a match list
363 @param[in] matchList list of matches between reference object and sources;
364 the distance field is the only field read and it must be set to distance in radians
366 @return a pipe_base Struct containing these fields:
367 - distMean clipped mean of on-sky radial separation
368 - distStdDev clipped standard deviation of on-sky radial separation
369 - maxMatchDist distMean + self.config.matchDistanceSigma*distStdDev
372 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*afwGeom.radians
373 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*afwGeom.radians
374 return pipeBase.Struct(
376 distStdDev=distStdDev,
377 maxMatchDist=distMean + self.config.matchDistanceSigma*distStdDev,
381 """!Extract metadata from an exposure
383 @return an lsst.pipe.base.Struct containing the following exposure metadata:
384 - bbox: parent bounding box
385 - wcs: WCS (an lsst.afw.image.Wcs)
386 - calib calibration (an lsst.afw.image.Calib), or None if unknown
387 - filterName: name of filter, or None if unknown
389 exposureInfo = exposure.getInfo()
390 filterName = exposureInfo.getFilter().getName()
or None
391 if filterName ==
"_unknown_":
393 return pipeBase.Struct(
394 bbox=exposure.getBBox(),
396 calib=exposureInfo.getCalib()
if exposureInfo.hasCalib()
else None,
397 filterName=filterName,
401 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, maxMatchDist=None,
403 """!Match sources to reference objects and fit a WCS
405 @param[in] refCat catalog of reference objects
406 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
407 @param[in] refFluxField field of refCat to use for flux
408 @param[in] bbox bounding box of exposure (an lsst.afw.geom.Box2I)
409 @param[in] wcs initial guess for WCS of exposure (an lsst.afw.image.Wcs)
410 @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
411 (an lsst.afw.geom.Angle); if None then use the matcher's default
412 @param[in] exposure exposure whose WCS is to be fit, or None; used only for the debug display
414 @return an lsst.pipe.base.Struct with these fields:
415 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
416 - wcs the fit WCS (an lsst.afw.image.Wcs)
417 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
418 (an lsst.afw.geom.Angle)
422 matchRes = self.matcher.matchObjectsToSources(
426 refFluxField=refFluxField,
427 maxMatchDist=maxMatchDist,
429 self.log.debug(
"Found %s matches", len(matchRes.matches))
431 frame = int(debug.frame)
434 sourceCat=matchRes.usableSourceCat,
435 matches=matchRes.matches,
442 self.log.debug(
"Fitting WCS")
443 fitRes = self.wcsFitter.fitWcs(
444 matches=matchRes.matches,
451 scatterOnSky = fitRes.scatterOnSky
453 frame = int(debug.frame)
456 sourceCat=matchRes.usableSourceCat,
457 matches=matchRes.matches,
461 title=
"Fit TAN-SIP WCS",
464 return pipeBase.Struct(
465 matches=matchRes.matches,
467 scatterOnSky=scatterOnSky,
def _matchAndFitWcs
Match sources to reference objects and fit a WCS.
def _getExposureMetadata
Extract metadata from an exposure.
def __init__
Construct an AstrometryTask.
def getDistortedWcs
Get a WCS from an exposureInfo, with distortion terms if possible.
def run
Load reference objects, match sources and optionally fit a WCS.
def _computeMatchStatsOnSky
afw::math::Statistics makeMatchStatistics(std::vector< MatchT > const &matchList, int const flags, afw::math::StatisticsControl const &sctrl)
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...
def loadAndMatch
Load reference objects overlapping an exposure and match to sources detected on that exposure...