23 __all__ = [
"AstrometryConfig",
"AstrometryTask"]
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 This task is broken into two main subasks: matching and WCS fitting which 67 are very interactive. The matching here can be considered in part a first 68 pass WCS fitter due to the fitter's sensitivity to outliers. 72 refObjLoader : `lsst.meas.algorithms.ReferenceLoader` 73 A reference object loader object 74 schema : `lsst.afw.table.Schema` 75 Used to set "calib_astrometry_used" flag in output source catalog. 77 additional keyword arguments for pipe_base 78 `lsst.pipe.base.Task.__init__` 80 ConfigClass = AstrometryConfig
81 _DefaultName =
"astrometricSolver" 83 def __init__(self, refObjLoader, schema=None, **kwargs):
84 RefMatchTask.__init__(self, refObjLoader, **kwargs)
86 if schema
is not None:
87 self.
usedKey = schema.addField(
"calib_astrometry_used", type=
"Flag",
88 doc=
"set if source was used in astrometric calibration")
92 self.makeSubtask(
"wcsFitter")
95 def run(self, sourceCat, exposure):
96 """Load reference objects, match sources and optionally fit a WCS. 98 This is a thin layer around solve or loadAndMatch, depending on 103 exposure : `lsst.afw.image.Exposure` 104 exposure whose WCS is to be fit 105 The following are read only: 108 - calib (may be absent) 109 - filter (may be unset) 110 - detector (if wcs is pure tangent; may be absent) 112 The following are updated: 114 - wcs (the initial value is used as an initial guess, and is 117 sourceCat : `lsst.afw.table.SourceCatalog` 118 catalog of sources detected on the exposure 122 result : `lsst.pipe.base.Struct` 125 - ``refCat`` : reference object catalog of objects that overlap the 126 exposure (with some margin) (`lsst.afw.table.SimpleCatalog`). 127 - ``matches`` : astrometric matches 128 (`list` of `lsst.afw.table.ReferenceMatch`). 129 - ``scatterOnSky`` : median on-sky separation between reference 130 objects and sources in "matches" 131 (`lsst.afw.geom.Angle`) or `None` if config.forceKnownWcs True 132 - ``matchMeta`` : metadata needed to unpersist matches 133 (`lsst.daf.base.PropertyList`) 136 raise RuntimeError(
"Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
137 if self.config.forceKnownWcs:
138 res = self.
loadAndMatch(exposure=exposure, sourceCat=sourceCat)
139 res.scatterOnSky =
None 141 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
145 def solve(self, exposure, sourceCat):
146 """Load reference objects overlapping an exposure, match to sources and 151 result : `lsst.pipe.base.Struct` 152 Result struct with components: 154 - ``refCat`` : reference object catalog of objects that overlap the 155 exposure (with some margin) (`lsst::afw::table::SimpleCatalog`). 156 - ``matches`` : astrometric matches 157 (`list` of `lsst.afw.table.ReferenceMatch`). 158 - ``scatterOnSky`` : median on-sky separation between reference 159 objects and sources in "matches" (`lsst.geom.Angle`) 160 - ``matchMeta`` : metadata needed to unpersist matches 161 (`lsst.daf.base.PropertyList`) 165 ignores config.forceKnownWcs 168 raise RuntimeError(
"Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
177 filterName=expMd.filterName,
184 filterName=expMd.filterName,
190 frame =
int(debug.frame)
192 refCat=loadRes.refCat,
197 title=
"Reference catalog",
202 match_tolerance =
None 203 for i
in range(self.config.maxIter):
207 refCat=loadRes.refCat,
209 refFluxField=loadRes.fluxField,
213 match_tolerance=match_tolerance,
215 except Exception
as e:
218 self.log.
info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
224 match_tolerance = tryRes.match_tolerance
227 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; " 228 "max match distance = %0.3f arcsec",
229 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
230 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
232 maxMatchDist = tryMatchDist.maxMatchDist
235 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
237 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; " 238 "that's good enough",
239 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
241 match_tolerance.maxMatchDist = maxMatchDist
244 "Matched and fit WCS in %d iterations; " 245 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
246 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
247 tryMatchDist.distStdDev.asArcseconds()))
248 for m
in res.matches:
250 m.second.set(self.
usedKey,
True)
251 exposure.setWcs(res.wcs)
253 return pipeBase.Struct(
254 refCat=loadRes.refCat,
256 scatterOnSky=res.scatterOnSky,
261 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance,
263 """Match sources to reference objects and fit a WCS. 267 refCat : `lsst.afw.table.SimpleCatalog` 268 catalog of reference objects 269 sourceCat : `lsst.afw.table.SourceCatalog` 270 catalog of sources detected on the exposure 272 field of refCat to use for flux 273 bbox : `lsst.geom.Box2I` 274 bounding box of exposure 275 wcs : `lsst.afw.geom.SkyWcs` 276 initial guess for WCS of exposure 277 match_tolerance : `lsst.meas.astrom.MatchTolerance` 278 a MatchTolerance object (or None) specifying 279 internal tolerances to the matcher. See the MatchTolerance 280 definition in the respective matcher for the class definition. 281 exposure : `lsst.afw.image.Exposure` 282 exposure whose WCS is to be fit, or None; used only for the debug 287 result : `lsst.pipe.base.Struct` 288 Result struct with components: 290 - ``matches``: astrometric matches 291 (`list` of `lsst.afw.table.ReferenceMatch`). 292 - ``wcs``: the fit WCS (lsst.afw.geom.SkyWcs). 293 - ``scatterOnSky`` : median on-sky separation between reference 294 objects and sources in "matches" (`lsst.afw.geom.Angle`). 298 matchRes = self.matcher.matchObjectsToSources(
302 refFluxField=refFluxField,
303 match_tolerance=match_tolerance,
305 self.log.
debug(
"Found %s matches", len(matchRes.matches))
307 frame =
int(debug.frame)
310 sourceCat=matchRes.usableSourceCat,
311 matches=matchRes.matches,
318 self.log.
debug(
"Fitting WCS")
319 fitRes = self.wcsFitter.fitWcs(
320 matches=matchRes.matches,
328 scatterOnSky = fitRes.scatterOnSky
330 frame =
int(debug.frame)
333 sourceCat=matchRes.usableSourceCat,
334 matches=matchRes.matches,
338 title=
"Fit TAN-SIP WCS",
341 return pipeBase.Struct(
342 matches=matchRes.matches,
344 scatterOnSky=scatterOnSky,
345 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)