23 __all__ = [
'RefMatchConfig',
'RefMatchTask']
30 import lsst.pex.config
as pexConfig
31 import lsst.pipe.base
as pipeBase
32 from lsst.meas.algorithms
import ReferenceSourceSelectorTask
33 from lsst.meas.algorithms.sourceSelector
import sourceSelectorRegistry
34 from .matchPessimisticB
import MatchPessimisticBTask
35 from .display
import displayAstrometry
36 from .
import makeMatchStatistics
40 matcher = pexConfig.ConfigurableField(
41 target=MatchPessimisticBTask,
42 doc=
"reference object/source matcher",
44 matchDistanceSigma = pexConfig.RangeField(
45 doc=
"the maximum match distance is set to "
46 " mean_match_distance + matchDistanceSigma*std_dev_match_distance; "
47 "ignored if not fitting a WCS",
52 sourceSelector = sourceSelectorRegistry.makeField(
53 doc=
"How to select sources for cross-matching.",
56 referenceSelector = pexConfig.ConfigurableField(
57 target=ReferenceSourceSelectorTask,
58 doc=
"How to select reference objects for cross-matching."
60 sourceFluxType = pexConfig.Field(
62 doc=
"Source flux type to use in source selection.",
77 """Match an input source catalog with objects from a reference catalog.
81 refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
82 A reference object loader object
84 additional keyword arguments for pipe_base `lsst.pipe.base.Task`
86 ConfigClass = RefMatchConfig
87 _DefaultName =
"calibrationBaseClass"
90 pipeBase.Task.__init__(self, **kwargs)
96 if self.config.sourceSelector.name ==
'matcher':
97 if self.config.sourceSelector[
'matcher'].sourceFluxType != self.config.sourceFluxType:
98 raise RuntimeError(
"The sourceFluxType in the sourceSelector['matcher'] must match "
99 "the configured sourceFluxType")
101 self.makeSubtask(
"matcher")
102 self.makeSubtask(
"sourceSelector")
103 self.makeSubtask(
"referenceSelector")
106 """Sets the reference object loader for the task
111 An instance of a reference object loader task or class
117 """Load reference objects overlapping an exposure and match to sources
118 detected on that exposure.
122 exposure : `lsst.afw.image.Exposure`
123 exposure that the sources overlap
124 sourceCat : `lsst.afw.table.SourceCatalog.`
125 catalog of sources detected on the exposure
129 result : `lsst.pipe.base.Struct`
130 Result struct with Components:
132 - ``refCat`` : reference object catalog of objects that overlap the
133 exposure (`lsst.afw.table.SimpleCatalog`)
134 - ``matches`` : Matched sources and references
135 (`list` of `lsst.afw.table.ReferenceMatch`)
136 - ``matchMeta`` : metadata needed to unpersist matches
137 (`lsst.daf.base.PropertyList`)
141 ignores config.matchDistanceSigma
144 raise RuntimeError(
"Running matcher task with no refObjLoader set in __ini__ or setRefObjLoader")
150 sourceSelection = self.sourceSelector.run(sourceCat)
152 sourceFluxField =
"slot_%sFlux_instFlux" % (self.config.sourceFluxType)
157 filterName=expMd.filterName,
158 photoCalib=expMd.photoCalib,
161 refSelection = self.referenceSelector.run(loadRes.refCat)
166 filterName=expMd.filterName,
167 photoCalib=expMd.photoCalib,
170 matchRes = self.matcher.matchObjectsToSources(
171 refCat=refSelection.sourceCat,
172 sourceCat=sourceSelection.sourceCat,
174 sourceFluxField=sourceFluxField,
175 refFluxField=loadRes.fluxField,
176 match_tolerance=
None,
181 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " %
182 (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds())
186 frame = int(debug.frame)
188 refCat=refSelection.sourceCat,
189 sourceCat=sourceSelection.sourceCat,
190 matches=matchRes.matches,
197 return pipeBase.Struct(
198 refCat=loadRes.refCat,
199 refSelection=refSelection,
200 sourceSelection=sourceSelection,
201 matches=matchRes.matches,
205 def _computeMatchStatsOnSky(self, matchList):
206 """Compute on-sky radial distance statistics for a match list
210 matchList : `list` of `lsst.afw.table.ReferenceMatch`
211 list of matches between reference object and sources;
212 the distance field is the only field read and it must be set to distance in radians
216 result : `lsst.pipe.base.Struct`
217 Result struct with components:
219 - ``distMean`` : clipped mean of on-sky radial separation (`float`)
220 - ``distStdDev`` : clipped standard deviation of on-sky radial
222 - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma *
226 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians
227 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians
228 return pipeBase.Struct(
230 distStdDev=distStdDev,
231 maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev,
234 def _getExposureMetadata(self, exposure):
235 """Extract metadata from an exposure.
239 exposure : `lsst.afw.image.Exposure`
243 result : `lsst.pipe.base.Struct`
244 Result struct with components:
246 - ``bbox`` : parent bounding box (`lsst.geom.Box2I`)
247 - ``wcs`` : exposure WCS (`lsst.afw.geom.SkyWcs`)
248 - ``photoCalib`` : photometric calibration (`lsst.afw.image.PhotoCalib`)
249 - ``filterName`` : name of filter (`str`)
250 - ``epoch`` : date of exposure (`astropy.time.Time`)
253 exposureInfo = exposure.getInfo()
254 filterName = exposureInfo.getFilter().getName()
or None
255 if filterName ==
"_unknown_":
258 if exposure.getInfo().hasVisitInfo():
259 epochTaiMjd = exposure.getInfo().getVisitInfo().getDate().get(system=DateTime.MJD,
261 epoch = astropy.time.Time(epochTaiMjd, scale=
"tai", format=
"mjd")
263 return pipeBase.Struct(
264 bbox=exposure.getBBox(),
265 wcs=exposureInfo.getWcs(),
266 photoCalib=exposureInfo.getPhotoCalib(),
267 filterName=filterName,