23 __all__ = [
"AstrometryConfig", 
"AstrometryTask"]
 
   26 import lsst.pex.config 
as pexConfig
 
   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,