LSST Applications 27.0.0,g0265f82a02+469cd937ee,g02d81e74bb+21ad69e7e1,g1470d8bcf6+cbe83ee85a,g2079a07aa2+e67c6346a6,g212a7c68fe+04a9158687,g2305ad1205+94392ce272,g295015adf3+81dd352a9d,g2bbee38e9b+469cd937ee,g337abbeb29+469cd937ee,g3939d97d7f+72a9f7b576,g487adcacf7+71499e7cba,g50ff169b8f+5929b3527e,g52b1c1532d+a6fc98d2e7,g591dd9f2cf+df404f777f,g5a732f18d5+be83d3ecdb,g64a986408d+21ad69e7e1,g858d7b2824+21ad69e7e1,g8a8a8dda67+a6fc98d2e7,g99cad8db69+f62e5b0af5,g9ddcbc5298+d4bad12328,ga1e77700b3+9c366c4306,ga8c6da7877+71e4819109,gb0e22166c9+25ba2f69a1,gb6a65358fc+469cd937ee,gbb8dafda3b+69d3c0e320,gc07e1c2157+a98bf949bb,gc120e1dc64+615ec43309,gc28159a63d+469cd937ee,gcf0d15dbbd+72a9f7b576,gdaeeff99f8+a38ce5ea23,ge6526c86ff+3a7c1ac5f1,ge79ae78c31+469cd937ee,gee10cc3b42+a6fc98d2e7,gf1cff7945b+21ad69e7e1,gfbcc870c63+9a11dc8c8f
LSST Data Management Base Package
Loading...
Searching...
No Matches
ref_match.py
Go to the documentation of this file.
1# This file is part of meas_astrom.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ['RefMatchConfig', 'RefMatchTask']
23
24import astropy.time
25
26import lsst.geom
27from lsst.daf.base import DateTime
28import lsst.afw.math as afwMath
29import lsst.pex.config as pexConfig
30import lsst.pipe.base as pipeBase
31from lsst.meas.algorithms import ReferenceSourceSelectorTask
32from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
33from lsst.utils.timer import timeMethod
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['science'].fluxLimit.fluxField = \
68 'slot_%sFlux_instFlux' % (self.sourceFluxType)
69 self.sourceSelector['science'].signalToNoise.fluxField = \
70 'slot_%sFlux_instFlux' % (self.sourceFluxType)
71 self.sourceSelector['science'].signalToNoise.errField = \
72 'slot_%sFlux_instFluxErr' % (self.sourceFluxType)
73
74
75class RefMatchTask(pipeBase.Task):
76 """Match an input source catalog with objects from a reference catalog.
77
78 Parameters
79 ----------
80 refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
81 A reference object loader object; gen3 pipeline tasks will pass `None`
82 and call `setRefObjLoader` in `runQuantum`.
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=None, **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 @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
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`)
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 epoch=expMd.epoch,
159 )
160
161 refSelection = self.referenceSelector.run(loadRes.refCat)
162
163 matchMeta = self.refObjLoader.getMetadataBox(
164 bbox=expMd.bbox,
165 wcs=expMd.wcs,
166 filterName=expMd.filterName,
167 epoch=expMd.epoch,
168 )
169
170 matchRes = self.matcher.matchObjectsToSources(
171 refCat=refSelection.sourceCat,
172 sourceCat=sourceSelection.sourceCat,
173 wcs=expMd.wcs,
174 sourceFluxField=sourceFluxField,
175 refFluxField=loadRes.fluxField,
176 match_tolerance=None,
177 )
178
179 distStats = self._computeMatchStatsOnSky(matchRes.matches)
180 self.log.info(
181 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; ",
182 len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds()
183 )
184
185 if debug.display:
186 frame = int(debug.frame)
187 displayAstrometry(
188 refCat=refSelection.sourceCat,
189 sourceCat=sourceSelection.sourceCat,
190 matches=matchRes.matches,
191 exposure=exposure,
192 bbox=expMd.bbox,
193 frame=frame,
194 title="Matches",
195 )
196
197 return pipeBase.Struct(
198 refCat=loadRes.refCat,
199 refSelection=refSelection,
200 sourceSelection=sourceSelection,
201 matches=matchRes.matches,
202 matchMeta=matchMeta,
203 )
204
205 def _computeMatchStatsOnSky(self, matchList):
206 """Compute on-sky radial distance statistics for a match list
207
208 Parameters
209 ----------
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
213
214 Returns
215 -------
216 result : `lsst.pipe.base.Struct`
217 Result struct with components:
218
219 - ``distMean`` : clipped mean of on-sky radial separation (`float`)
220 - ``distStdDev`` : clipped standard deviation of on-sky radial
221 separation (`float`)
222 - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma *
223 distStdDev (`float`)
224 """
225 distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP)
226 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians
227 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians
228 return pipeBase.Struct(
229 distMean=distMean,
230 distStdDev=distStdDev,
231 maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev,
232 )
233
234 def _getExposureMetadata(self, exposure):
235 """Extract metadata from an exposure.
236
237 Parameters
238 ----------
239 exposure : `lsst.afw.image.Exposure`
240
241 Returns
242 -------
243 result : `lsst.pipe.base.Struct`
244 Result struct with components:
245
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 band (`str`)
250 - ``epoch`` : date of exposure (`astropy.time.Time`)
251 """
252 filterLabel = exposure.info.getFilter()
253 filterName = filterLabel.bandLabel if filterLabel is not None else None
254 epoch = None
255 if exposure.info.hasVisitInfo():
256 epochTaiMjd = exposure.visitInfo.date.get(system=DateTime.MJD, scale=DateTime.TAI)
257 epoch = astropy.time.Time(epochTaiMjd, scale="tai", format="mjd")
258
259 return pipeBase.Struct(
260 bbox=exposure.getBBox(),
261 wcs=exposure.info.getWcs(),
262 photoCalib=exposure.info.getPhotoCalib(),
263 filterName=filterName,
264 epoch=epoch,
265 )
__init__(self, refObjLoader=None, **kwargs)
Definition ref_match.py:89
loadAndMatch(self, exposure, sourceCat)
Definition ref_match.py:116