23 __all__ = [
"AstrometryConfig",
"AstrometryTask"]
27 import lsst.pipe.base
as pipeBase
28 from .ref_match
import RefMatchTask, RefMatchConfig
29 from .fitTanSipWcs
import FitTanSipWcsTask
30 from .display
import displayAstrometry
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 pipe_tasks_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)
151 if schema
is not None:
152 self.
usedKey = schema.addField(
"calib_astrometry_used", type=
"Flag",
153 doc=
"set if source was used in astrometric calibration")
157 self.makeSubtask(
"wcsFitter")
160 def run(self, sourceCat, exposure):
161 """!Load reference objects, match sources and optionally fit a WCS 163 This is a thin layer around solve or loadAndMatch, depending on config.forceKnownWcs 165 @param[in,out] exposure exposure whose WCS is to be fit 166 The following are read only: 168 - calib (may be absent) 169 - filter (may be unset) 170 - detector (if wcs is pure tangent; may be absent) 171 The following are updated: 172 - wcs (the initial value is used as an initial guess, and is required) 173 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog) 174 @return an lsst.pipe.base.Struct with these fields: 175 - refCat reference object catalog of objects that overlap the exposure (with some margin) 176 (an lsst::afw::table::SimpleCatalog) 177 - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch 178 - scatterOnSky median on-sky separation between reference objects and sources in "matches" 179 (an lsst.afw.geom.Angle), or None if config.forceKnownWcs True 180 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList) 182 if self.config.forceKnownWcs:
183 res = self.
loadAndMatch(exposure=exposure, sourceCat=sourceCat)
184 res.scatterOnSky =
None 186 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
190 def solve(self, exposure, sourceCat):
191 """!Load reference objects overlapping an exposure, match to sources and fit a WCS 193 @return an lsst.pipe.base.Struct with these fields: 194 - refCat reference object catalog of objects that overlap the exposure (with some margin) 195 (an lsst::afw::table::SimpleCatalog) 196 - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch 197 - scatterOnSky median on-sky separation between reference objects and sources in "matches" 198 (an lsst.afw.geom.Angle) 199 - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList) 201 @note ignores config.forceKnownWcs 211 filterName=expMd.filterName,
217 filterName=expMd.filterName,
222 frame = int(debug.frame)
224 refCat=loadRes.refCat,
229 title=
"Reference catalog",
234 match_tolerance =
None 235 for i
in range(self.config.maxIter):
239 refCat=loadRes.refCat,
241 refFluxField=loadRes.fluxField,
245 match_tolerance=match_tolerance,
247 except Exception
as e:
250 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
256 match_tolerance = tryRes.match_tolerance
259 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; " 260 "max match distance = %0.3f arcsec",
261 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
262 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
264 maxMatchDist = tryMatchDist.maxMatchDist
267 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
269 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; " 270 "that's good enough",
271 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
273 match_tolerance.maxMatchDist = maxMatchDist
276 "Matched and fit WCS in %d iterations; " 277 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
278 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
279 tryMatchDist.distStdDev.asArcseconds()))
280 for m
in res.matches:
282 m.second.set(self.
usedKey,
True)
283 exposure.setWcs(res.wcs)
285 return pipeBase.Struct(
286 refCat=loadRes.refCat,
288 scatterOnSky=res.scatterOnSky,
293 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance,
295 """!Match sources to reference objects and fit a WCS 297 @param[in] refCat catalog of reference objects 298 @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog) 299 @param[in] refFluxField field of refCat to use for flux 300 @param[in] bbox bounding box of exposure (an lsst.afw.geom.Box2I) 301 @param[in] wcs initial guess for WCS of exposure (an lsst.afw.geom.Wcs) 302 @param[in] match_tolerance a MatchTolerance object (or None) specifying 303 internal tolerances to the matcher. See the MatchTolerance 304 definition in the respective matcher for the class definition. 305 @param[in] exposure exposure whose WCS is to be fit, or None; used only for the debug display 307 @return an lsst.pipe.base.Struct with these fields: 308 - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch 309 - wcs the fit WCS (an lsst.afw.geom.Wcs) 310 - scatterOnSky median on-sky separation between reference objects and sources in "matches" 311 (an lsst.afw.geom.Angle) 315 matchRes = self.matcher.matchObjectsToSources(
319 refFluxField=refFluxField,
320 match_tolerance=match_tolerance,
322 self.log.debug(
"Found %s matches", len(matchRes.matches))
324 frame = int(debug.frame)
327 sourceCat=matchRes.usableSourceCat,
328 matches=matchRes.matches,
335 self.log.debug(
"Fitting WCS")
336 fitRes = self.wcsFitter.fitWcs(
337 matches=matchRes.matches,
345 scatterOnSky = fitRes.scatterOnSky
347 frame = int(debug.frame)
350 sourceCat=matchRes.usableSourceCat,
351 matches=matchRes.matches,
355 title=
"Fit TAN-SIP WCS",
358 return pipeBase.Struct(
359 matches=matchRes.matches,
361 scatterOnSky=scatterOnSky,
362 match_tolerance=matchRes.match_tolerance,
def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance, exposure=None)
Match sources to reference objects and fit a WCS.
def solve(self, exposure, sourceCat)
Load reference objects overlapping an exposure, match to sources and fit a WCS.
def _computeMatchStatsOnSky(self, matchList)
def _getExposureMetadata(self, exposure)
Extract metadata from an exposure.
def run(self, sourceCat, exposure)
Load reference objects, match sources and optionally fit a WCS.
Match an input source catalog with objects from a reference catalog.
def __init__(self, refObjLoader, schema=None, kwargs)
Construct an AstrometryTask.
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
def loadAndMatch(self, exposure, sourceCat)
Load reference objects overlapping an exposure and match to sources detected on that exposure...
Match an input source catalog with objects from a reference catalog and solve for the WCS...