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",
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,
229 goodSourceCat=sourceSelection.sourceCat,
230 refFluxField=loadRes.fluxField,
234 match_tolerance=match_tolerance,
236 except Exception
as e:
239 self.log.
info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
245 match_tolerance = tryRes.match_tolerance
248 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; " 249 "max match distance = %0.3f arcsec",
250 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
251 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
253 maxMatchDist = tryMatchDist.maxMatchDist
256 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
258 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; " 259 "that's good enough",
260 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
262 match_tolerance.maxMatchDist = maxMatchDist
265 "Matched and fit WCS in %d iterations; " 266 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
267 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
268 tryMatchDist.distStdDev.asArcseconds()))
269 for m
in res.matches:
271 m.second.set(self.
usedKey,
True)
272 exposure.setWcs(res.wcs)
274 return pipeBase.Struct(
275 refCat=refSelection.sourceCat,
277 scatterOnSky=res.scatterOnSky,
282 def _matchAndFitWcs(self, refCat, sourceCat, goodSourceCat, refFluxField, bbox, wcs, match_tolerance,
284 """Match sources to reference objects and fit a WCS. 288 refCat : `lsst.afw.table.SimpleCatalog` 289 catalog of reference objects 290 sourceCat : `lsst.afw.table.SourceCatalog` 291 catalog of sources detected on the exposure 292 goodSourceCat : `lsst.afw.table.SourceCatalog` 293 catalog of down-selected good sources detected on the exposure 295 field of refCat to use for flux 296 bbox : `lsst.geom.Box2I` 297 bounding box of exposure 298 wcs : `lsst.afw.geom.SkyWcs` 299 initial guess for WCS of exposure 300 match_tolerance : `lsst.meas.astrom.MatchTolerance` 301 a MatchTolerance object (or None) specifying 302 internal tolerances to the matcher. See the MatchTolerance 303 definition in the respective matcher for the class definition. 304 exposure : `lsst.afw.image.Exposure` 305 exposure whose WCS is to be fit, or None; used only for the debug 310 result : `lsst.pipe.base.Struct` 311 Result struct with components: 313 - ``matches``: astrometric matches 314 (`list` of `lsst.afw.table.ReferenceMatch`). 315 - ``wcs``: the fit WCS (lsst.afw.geom.SkyWcs). 316 - ``scatterOnSky`` : median on-sky separation between reference 317 objects and sources in "matches" (`lsst.afw.geom.Angle`). 322 sourceFluxField =
"slot_%sFlux_instFlux" % (self.config.sourceFluxType)
324 matchRes = self.matcher.matchObjectsToSources(
326 sourceCat=goodSourceCat,
328 sourceFluxField=sourceFluxField,
329 refFluxField=refFluxField,
330 match_tolerance=match_tolerance,
332 self.log.
debug(
"Found %s matches", len(matchRes.matches))
334 frame =
int(debug.frame)
337 sourceCat=matchRes.usableSourceCat,
338 matches=matchRes.matches,
345 self.log.
debug(
"Fitting WCS")
346 fitRes = self.wcsFitter.fitWcs(
347 matches=matchRes.matches,
355 scatterOnSky = fitRes.scatterOnSky
357 frame =
int(debug.frame)
360 sourceCat=matchRes.usableSourceCat,
361 matches=matchRes.matches,
365 title=
"Fit TAN-SIP WCS",
368 return pipeBase.Struct(
369 matches=matchRes.matches,
371 scatterOnSky=scatterOnSky,
372 match_tolerance=matchRes.match_tolerance,
def solve(self, exposure, sourceCat)
def _computeMatchStatsOnSky(self, matchList)
def _getExposureMetadata(self, exposure)
def run(self, sourceCat, exposure)
def _matchAndFitWcs(self, refCat, sourceCat, goodSourceCat, refFluxField, bbox, wcs, match_tolerance, exposure=None)
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)