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",
74 """Match an input source catalog with objects from a reference catalog and 77 This task is broken into two main subasks: matching and WCS fitting which 78 are very interactive. The matching here can be considered in part a first 79 pass WCS fitter due to the fitter's sensitivity to outliers. 83 refObjLoader : `lsst.meas.algorithms.ReferenceLoader` 84 A reference object loader object 85 schema : `lsst.afw.table.Schema` 86 Used to set "calib_astrometry_used" flag in output source catalog. 88 additional keyword arguments for pipe_base 89 `lsst.pipe.base.Task.__init__` 91 ConfigClass = AstrometryConfig
92 _DefaultName =
"astrometricSolver" 94 def __init__(self, refObjLoader, schema=None, **kwargs):
95 RefMatchTask.__init__(self, refObjLoader, **kwargs)
97 if schema
is not None:
98 self.
usedKey = schema.addField(
"calib_astrometry_used", type=
"Flag",
99 doc=
"set if source was used in astrometric calibration")
103 self.makeSubtask(
"wcsFitter")
106 def run(self, sourceCat, exposure):
107 """Load reference objects, match sources and optionally fit a WCS. 109 This is a thin layer around solve or loadAndMatch, depending on 110 config.forceKnownWcs. 114 exposure : `lsst.afw.image.Exposure` 115 exposure whose WCS is to be fit 116 The following are read only: 119 - photoCalib (may be absent) 120 - filter (may be unset) 121 - detector (if wcs is pure tangent; may be absent) 123 The following are updated: 125 - wcs (the initial value is used as an initial guess, and is 128 sourceCat : `lsst.afw.table.SourceCatalog` 129 catalog of sources detected on the exposure 133 result : `lsst.pipe.base.Struct` 136 - ``refCat`` : reference object catalog of objects that overlap the 137 exposure (with some margin) (`lsst.afw.table.SimpleCatalog`). 138 - ``matches`` : astrometric matches 139 (`list` of `lsst.afw.table.ReferenceMatch`). 140 - ``scatterOnSky`` : median on-sky separation between reference 141 objects and sources in "matches" 142 (`lsst.afw.geom.Angle`) or `None` if config.forceKnownWcs True 143 - ``matchMeta`` : metadata needed to unpersist matches 144 (`lsst.daf.base.PropertyList`) 147 raise RuntimeError(
"Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
148 if self.config.forceKnownWcs:
149 res = self.
loadAndMatch(exposure=exposure, sourceCat=sourceCat)
150 res.scatterOnSky =
None 152 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
156 def solve(self, exposure, sourceCat):
157 """Load reference objects overlapping an exposure, match to sources and 162 result : `lsst.pipe.base.Struct` 163 Result struct with components: 165 - ``refCat`` : reference object catalog of objects that overlap the 166 exposure (with some margin) (`lsst::afw::table::SimpleCatalog`). 167 - ``matches`` : astrometric matches 168 (`list` of `lsst.afw.table.ReferenceMatch`). 169 - ``scatterOnSky`` : median on-sky separation between reference 170 objects and sources in "matches" (`lsst.geom.Angle`) 171 - ``matchMeta`` : metadata needed to unpersist matches 172 (`lsst.daf.base.PropertyList`) 176 ignores config.forceKnownWcs 179 raise RuntimeError(
"Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
185 sourceSelection = self.sourceSelector.
run(sourceCat)
187 self.log.info(
"Purged %d sources, leaving %d good sources" %
188 (len(sourceCat) - len(sourceSelection.sourceCat),
189 len(sourceSelection.sourceCat)))
194 filterName=expMd.filterName,
195 photoCalib=expMd.photoCalib,
199 refSelection = self.referenceSelector.
run(loadRes.refCat)
204 filterName=expMd.filterName,
205 photoCalib=expMd.photoCalib,
210 frame = int(debug.frame)
212 refCat=refSelection.sourceCat,
213 sourceCat=sourceSelection.sourceCat,
217 title=
"Reference catalog",
222 match_tolerance =
None 223 for i
in range(self.config.maxIter):
227 refCat=refSelection.sourceCat,
228 sourceCat=sourceSelection.sourceCat,
229 refFluxField=loadRes.fluxField,
233 match_tolerance=match_tolerance,
235 except Exception
as e:
238 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
244 match_tolerance = tryRes.match_tolerance
247 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; " 248 "max match distance = %0.3f arcsec",
249 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
250 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
252 maxMatchDist = tryMatchDist.maxMatchDist
255 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
257 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; " 258 "that's good enough",
259 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
261 match_tolerance.maxMatchDist = maxMatchDist
264 "Matched and fit WCS in %d iterations; " 265 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
266 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
267 tryMatchDist.distStdDev.asArcseconds()))
268 for m
in res.matches:
270 m.second.set(self.
usedKey,
True)
271 exposure.setWcs(res.wcs)
273 return pipeBase.Struct(
274 refCat=refSelection.sourceCat,
276 scatterOnSky=res.scatterOnSky,
281 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance,
283 """Match sources to reference objects and fit a WCS. 287 refCat : `lsst.afw.table.SimpleCatalog` 288 catalog of reference objects 289 sourceCat : `lsst.afw.table.SourceCatalog` 290 catalog of sources detected on the exposure 292 field of refCat to use for flux 293 bbox : `lsst.geom.Box2I` 294 bounding box of exposure 295 wcs : `lsst.afw.geom.SkyWcs` 296 initial guess for WCS of exposure 297 match_tolerance : `lsst.meas.astrom.MatchTolerance` 298 a MatchTolerance object (or None) specifying 299 internal tolerances to the matcher. See the MatchTolerance 300 definition in the respective matcher for the class definition. 301 exposure : `lsst.afw.image.Exposure` 302 exposure whose WCS is to be fit, or None; used only for the debug 307 result : `lsst.pipe.base.Struct` 308 Result struct with components: 310 - ``matches``: astrometric matches 311 (`list` of `lsst.afw.table.ReferenceMatch`). 312 - ``wcs``: the fit WCS (lsst.afw.geom.SkyWcs). 313 - ``scatterOnSky`` : median on-sky separation between reference 314 objects and sources in "matches" (`lsst.afw.geom.Angle`). 319 sourceFluxField =
"slot_%sFlux_instFlux" % (self.config.sourceFluxType)
321 matchRes = self.matcher.matchObjectsToSources(
325 sourceFluxField=sourceFluxField,
326 refFluxField=refFluxField,
327 match_tolerance=match_tolerance,
329 self.log.debug(
"Found %s matches", len(matchRes.matches))
331 frame = int(debug.frame)
334 sourceCat=matchRes.usableSourceCat,
335 matches=matchRes.matches,
342 self.log.debug(
"Fitting WCS")
343 fitRes = self.wcsFitter.fitWcs(
344 matches=matchRes.matches,
352 scatterOnSky = fitRes.scatterOnSky
354 frame = int(debug.frame)
357 sourceCat=matchRes.usableSourceCat,
358 matches=matchRes.matches,
362 title=
"Fit TAN-SIP WCS",
365 return pipeBase.Struct(
366 matches=matchRes.matches,
368 scatterOnSky=scatterOnSky,
369 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)