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 """Config for AstrometryTask. 36 wcsFitter = pexConfig.ConfigurableField(
37 target=FitTanSipWcsTask,
40 forceKnownWcs = pexConfig.Field(
42 doc=
"If True then load reference objects and match sources but do not fit a WCS; " 43 "this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
46 maxIter = pexConfig.RangeField(
47 doc=
"maximum number of iterations of match sources and fit WCS" 48 "ignored if not fitting a WCS",
53 minMatchDistanceArcSec = pexConfig.RangeField(
54 doc=
"the match distance below which further iteration is pointless (arcsec); " 55 "ignored if not fitting a WCS",
63 """Match an input source catalog with objects from a reference catalog and 66 # TODO: DM-16868 remove explicit and unused schema from class input. 70 refObjLoader : `lsst.meas.algorithms.ReferenceLoader` 71 A reference object loader object 72 schema : `lsst.afw.table.Schema` 73 ignored; available for compatibility with an older astrometry task 75 additional keyword arguments for pipe_base 76 `lsst.pipe.base.Task.__init__` 78 ConfigClass = AstrometryConfig
79 _DefaultName =
"astrometricSolver" 81 def __init__(self, refObjLoader, schema=None, **kwargs):
82 RefMatchTask.__init__(self, refObjLoader, schema=schema, **kwargs)
84 if schema
is not None:
85 self.
usedKey = schema.addField(
"calib_astrometry_used", type=
"Flag",
86 doc=
"set if source was used in astrometric calibration")
90 self.makeSubtask(
"wcsFitter")
93 def run(self, sourceCat, exposure):
94 """Load reference objects, match sources and optionally fit a WCS. 96 This is a thin layer around solve or loadAndMatch, depending on 101 exposure : `lsst.afw.image.Exposure` 102 exposure whose WCS is to be fit 103 The following are read only: 106 - calib (may be absent) 107 - filter (may be unset) 108 - detector (if wcs is pure tangent; may be absent) 110 The following are updated: 112 - wcs (the initial value is used as an initial guess, and is 115 sourceCat : `lsst.afw.table.SourceCatalog` 116 catalog of sources detected on the exposure 120 result : `lsst.pipe.base.Struct` 123 - ``refCat`` : reference object catalog of objects that overlap the 124 exposure (with some margin) (`lsst.afw.table.SimpleCatalog`). 125 - ``matches`` : astrometric matches 126 (`list` of `lsst.afw.table.ReferenceMatch`). 127 - ``scatterOnSky`` : median on-sky separation between reference 128 objects and sources in "matches" 129 (`lsst.afw.geom.Angle`) or `None` if config.forceKnownWcs True 130 - ``matchMeta`` : metadata needed to unpersist matches 131 (`lsst.daf.base.PropertyList`) 134 raise RuntimeError(
"Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
135 if self.config.forceKnownWcs:
136 res = self.
loadAndMatch(exposure=exposure, sourceCat=sourceCat)
137 res.scatterOnSky =
None 139 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
143 def solve(self, exposure, sourceCat):
144 """Load reference objects overlapping an exposure, match to sources and 149 result : `lsst.pipe.base.Struct` 150 Result struct with components: 152 - ``refCat`` : reference object catalog of objects that overlap the 153 exposure (with some margin) (`lsst::afw::table::SimpleCatalog`). 154 - ``matches`` : astrometric matches 155 (`list` of `lsst.afw.table.ReferenceMatch`). 156 - ``scatterOnSky`` : median on-sky separation between reference 157 objects and sources in "matches" (`lsst.geom.Angle`) 158 - ``matchMeta`` : metadata needed to unpersist matches 159 (`lsst.daf.base.PropertyList`) 163 ignores config.forceKnownWcs 166 raise RuntimeError(
"Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
175 filterName=expMd.filterName,
182 filterName=expMd.filterName,
188 frame = int(debug.frame)
190 refCat=loadRes.refCat,
195 title=
"Reference catalog",
200 match_tolerance =
None 201 for i
in range(self.config.maxIter):
205 refCat=loadRes.refCat,
207 refFluxField=loadRes.fluxField,
211 match_tolerance=match_tolerance,
213 except Exception
as e:
216 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
222 match_tolerance = tryRes.match_tolerance
225 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; " 226 "max match distance = %0.3f arcsec",
227 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
228 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
230 maxMatchDist = tryMatchDist.maxMatchDist
233 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
235 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; " 236 "that's good enough",
237 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
239 match_tolerance.maxMatchDist = maxMatchDist
242 "Matched and fit WCS in %d iterations; " 243 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
244 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
245 tryMatchDist.distStdDev.asArcseconds()))
246 for m
in res.matches:
248 m.second.set(self.
usedKey,
True)
249 exposure.setWcs(res.wcs)
251 return pipeBase.Struct(
252 refCat=loadRes.refCat,
254 scatterOnSky=res.scatterOnSky,
259 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance,
261 """Match sources to reference objects and fit a WCS. 265 refCat : `lsst.afw.table.SimpleCatalog` 266 catalog of reference objects 267 sourceCat : `lsst.afw.table.SourceCatalog` 268 catalog of sources detected on the exposure 270 field of refCat to use for flux 271 bbox : `lsst.geom.Box2I` 272 bounding box of exposure 273 wcs : `lsst.afw.geom.SkyWcs` 274 initial guess for WCS of exposure 275 match_tolerance : `lsst.meas.astrom.MatchTolerance` 276 a MatchTolerance object (or None) specifying 277 internal tolerances to the matcher. See the MatchTolerance 278 definition in the respective matcher for the class definition. 279 exposure : `lsst.afw.image.Exposure` 280 exposure whose WCS is to be fit, or None; used only for the debug 285 result : `lsst.pipe.base.Struct` 286 Result struct with components: 288 - ``matches``: astrometric matches 289 (`list` of `lsst.afw.table.ReferenceMatch`). 290 - ``wcs``: the fit WCS (lsst.afw.geom.SkyWcs). 291 - ``scatterOnSky`` : median on-sky separation between reference 292 objects and sources in "matches" (`lsst.afw.geom.Angle`). 296 matchRes = self.matcher.matchObjectsToSources(
300 refFluxField=refFluxField,
301 match_tolerance=match_tolerance,
303 self.log.debug(
"Found %s matches", len(matchRes.matches))
305 frame = int(debug.frame)
308 sourceCat=matchRes.usableSourceCat,
309 matches=matchRes.matches,
316 self.log.debug(
"Fitting WCS")
317 fitRes = self.wcsFitter.fitWcs(
318 matches=matchRes.matches,
326 scatterOnSky = fitRes.scatterOnSky
328 frame = int(debug.frame)
331 sourceCat=matchRes.usableSourceCat,
332 matches=matchRes.matches,
336 title=
"Fit TAN-SIP WCS",
339 return pipeBase.Struct(
340 matches=matchRes.matches,
342 scatterOnSky=scatterOnSky,
343 match_tolerance=matchRes.match_tolerance,
def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance, exposure=None)
def solve(self, exposure, sourceCat)
def _computeMatchStatsOnSky(self, matchList)
def _getExposureMetadata(self, exposure)
def run(self, sourceCat, exposure)
def __init__(self, refObjLoader, schema=None, kwargs)
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
def loadAndMatch(self, exposure, sourceCat)