lsst.meas.astrom gaf95d0f0f9+e66c719481
Loading...
Searching...
No Matches
ref_match.py
Go to the documentation of this file.
2# LSST Data Management System
3# Copyright 2008-2016 AURA/LSST.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22
23__all__ = ['RefMatchConfig', 'RefMatchTask']
24
25import astropy.time
26
27import lsst.geom
28from lsst.daf.base import DateTime
29import lsst.afw.math as afwMath
30import lsst.pex.config as pexConfig
31import lsst.pipe.base as pipeBase
32from lsst.meas.algorithms import ReferenceSourceSelectorTask
33from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
34from .matchPessimisticB import MatchPessimisticBTask
35from .display import displayAstrometry
36from . import makeMatchStatistics
37
38
39class RefMatchConfig(pexConfig.Config):
40 matcher = pexConfig.ConfigurableField(
41 target=MatchPessimisticBTask,
42 doc="reference object/source matcher",
43 )
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",
48 dtype=float,
49 default=2,
50 min=0,
51 )
52 sourceSelector = sourceSelectorRegistry.makeField(
53 doc="How to select sources for cross-matching.",
54 default="science",
55 )
56 referenceSelector = pexConfig.ConfigurableField(
57 target=ReferenceSourceSelectorTask,
58 doc="How to select reference objects for cross-matching."
59 )
60 sourceFluxType = pexConfig.Field(
61 dtype=str,
62 doc="Source flux type to use in source selection.",
63 default='Calib'
64 )
65
66 def setDefaults(self):
67 self.sourceSelector.name = "science"
68 self.sourceSelector['science'].fluxLimit.fluxField = \
69 'slot_%sFlux_instFlux' % (self.sourceFluxType)
70 self.sourceSelector['science'].signalToNoise.fluxField = \
71 'slot_%sFlux_instFlux' % (self.sourceFluxType)
72 self.sourceSelector['science'].signalToNoise.errField = \
73 'slot_%sFlux_instFluxErr' % (self.sourceFluxType)
74
75
76class RefMatchTask(pipeBase.Task):
77 """Match an input source catalog with objects from a reference catalog.
78
79 Parameters
80 ----------
81 refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
82 A reference object loader object
83 **kwargs
84 additional keyword arguments for pipe_base `lsst.pipe.base.Task`
85 """
86 ConfigClass = RefMatchConfig
87 _DefaultName = "calibrationBaseClass"
88
89 def __init__(self, refObjLoader, **kwargs):
90 pipeBase.Task.__init__(self, **kwargs)
91 if refObjLoader:
92 self.refObjLoader = refObjLoader
93 else:
94 self.refObjLoader = None
95
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")
100
101 self.makeSubtask("matcher")
102 self.makeSubtask("sourceSelector")
103 self.makeSubtask("referenceSelector")
104
105 def setRefObjLoader(self, refObjLoader):
106 """Sets the reference object loader for the task
107
108 Parameters
109 ----------
110 refObjLoader
111 An instance of a reference object loader task or class
112 """
113 self.refObjLoader = refObjLoader
114
115 @pipeBase.timeMethod
116 def loadAndMatch(self, exposure, sourceCat):
117 """Load reference objects overlapping an exposure and match to sources
118 detected on that exposure.
119
120 Parameters
121 ----------
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
126
127 Returns
128 -------
129 result : `lsst.pipe.base.Struct`
130 Result struct with Components:
131
132 - ``refCat`` : reference object catalog of objects that overlap the
134 - ``matches`` : Matched sources and references
136 - ``matchMeta`` : metadata needed to unpersist matches
138
139 Notes
140 -----
141 ignores config.matchDistanceSigma
142 """
143 if self.refObjLoader is None:
144 raise RuntimeError("Running matcher task with no refObjLoader set in __ini__ or setRefObjLoader")
145 import lsstDebug
146 debug = lsstDebug.Info(__name__)
147
148 expMd = self._getExposureMetadata(exposure)
149
150 sourceSelection = self.sourceSelector.run(sourceCat)
151
152 sourceFluxField = "slot_%sFlux_instFlux" % (self.config.sourceFluxType)
153
154 loadRes = self.refObjLoader.loadPixelBox(
155 bbox=expMd.bbox,
156 wcs=expMd.wcs,
157 filterName=expMd.filterName,
158 photoCalib=expMd.photoCalib,
159 epoch=expMd.epoch,
160 )
161
162 refSelection = self.referenceSelector.run(loadRes.refCat)
163
164 matchMeta = self.refObjLoader.getMetadataBox(
165 bbox=expMd.bbox,
166 wcs=expMd.wcs,
167 filterName=expMd.filterName,
168 photoCalib=expMd.photoCalib,
169 epoch=expMd.epoch,
170 )
171
172 matchRes = self.matcher.matchObjectsToSources(
173 refCat=refSelection.sourceCat,
174 sourceCat=sourceSelection.sourceCat,
175 wcs=expMd.wcs,
176 sourceFluxField=sourceFluxField,
177 refFluxField=loadRes.fluxField,
178 match_tolerance=None,
179 )
180
181 distStats = self._computeMatchStatsOnSky(matchRes.matches)
182 self.log.info(
183 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " %
184 (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds())
185 )
186
187 if debug.display:
188 frame = int(debug.frame)
189 displayAstrometry(
190 refCat=refSelection.sourceCat,
191 sourceCat=sourceSelection.sourceCat,
192 matches=matchRes.matches,
193 exposure=exposure,
194 bbox=expMd.bbox,
195 frame=frame,
196 title="Matches",
197 )
198
199 return pipeBase.Struct(
200 refCat=loadRes.refCat,
201 refSelection=refSelection,
202 sourceSelection=sourceSelection,
203 matches=matchRes.matches,
204 matchMeta=matchMeta,
205 )
206
207 def _computeMatchStatsOnSky(self, matchList):
208 """Compute on-sky radial distance statistics for a match list
209
210 Parameters
211 ----------
212 matchList : `list` of `lsst.afw.table.ReferenceMatch`
213 list of matches between reference object and sources;
214 the distance field is the only field read and it must be set to distance in radians
215
216 Returns
217 -------
218 result : `lsst.pipe.base.Struct`
219 Result struct with components:
220
221 - ``distMean`` : clipped mean of on-sky radial separation (`float`)
222 - ``distStdDev`` : clipped standard deviation of on-sky radial
223 separation (`float`)
224 - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma *
225 distStdDev (`float`)
226 """
227 distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP)
228 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians
229 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians
230 return pipeBase.Struct(
231 distMean=distMean,
232 distStdDev=distStdDev,
233 maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev,
234 )
235
236 def _getExposureMetadata(self, exposure):
237 """Extract metadata from an exposure.
238
239 Parameters
240 ----------
241 exposure : `lsst.afw.image.Exposure`
242
243 Returns
244 -------
245 result : `lsst.pipe.base.Struct`
246 Result struct with components:
247
248 - ``bbox`` : parent bounding box (`lsst.geom.Box2I`)
249 - ``wcs`` : exposure WCS (`lsst.afw.geom.SkyWcs`)
250 - ``photoCalib`` : photometric calibration (`lsst.afw.image.PhotoCalib`)
251 - ``filterName`` : name of filter band (`str`)
252 - ``epoch`` : date of exposure (`astropy.time.Time`)
253
254 """
255 exposureInfo = exposure.getInfo()
256 filterLabel = exposureInfo.getFilterLabel()
257 filterName = filterLabel.bandLabel if filterLabel is not None else None
258 epoch = None
259 if exposure.getInfo().hasVisitInfo():
260 epochTaiMjd = exposure.getInfo().getVisitInfo().getDate().get(system=DateTime.MJD,
261 scale=DateTime.TAI)
262 epoch = astropy.time.Time(epochTaiMjd, scale="tai", format="mjd")
263
264 return pipeBase.Struct(
265 bbox=exposure.getBBox(),
266 wcs=exposureInfo.getWcs(),
267 photoCalib=exposureInfo.getPhotoCalib(),
268 filterName=filterName,
269 epoch=epoch,
270 )
def _computeMatchStatsOnSky(self, matchList)
Definition: ref_match.py:207
def loadAndMatch(self, exposure, sourceCat)
Definition: ref_match.py:116
def __init__(self, refObjLoader, **kwargs)
Definition: ref_match.py:89
def _getExposureMetadata(self, exposure)
Definition: ref_match.py:236
def setRefObjLoader(self, refObjLoader)
Definition: ref_match.py:105