Loading [MathJax]/extensions/tex2jax.js
LSST Applications g032c94a9f9+ac98094cfc,g04a91732dc+8f8ef39500,g07dc498a13+4eb283b00e,g0fba68d861+fe52cfea07,g1409bbee79+4eb283b00e,g1a7e361dbc+4eb283b00e,g1fd858c14a+21fedf7ac9,g208c678f98+a86e7897d4,g2c84ff76c0+22a6ac03ed,g35bb328faa+fcb1d3bbc8,g4d2262a081+a494e3f3d6,g4d39ba7253+3dc2658323,g4e0f332c67+c58e4b632d,g53246c7159+fcb1d3bbc8,g60b5630c4e+3dc2658323,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8852436030+24b38c285b,g89139ef638+4eb283b00e,g8d6b6b353c+3dc2658323,g9125e01d80+fcb1d3bbc8,g989de1cb63+4eb283b00e,g9f33ca652e+abcb26939a,ga9baa6287d+3dc2658323,gaaedd4e678+4eb283b00e,gabe3b4be73+1e0a283bba,gb1101e3267+f06a32901e,gb58c049af0+f03b321e39,gb90eeb9370+5737ae2691,gcf25f946ba+24b38c285b,gd315a588df+81e3241efa,gd6cbbdb0b4+75aa4b1db4,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+ac7491eacb,ge278dab8ac+c61fbefdff,ge82c20c137+e12a08b75a,gf4a8514506+3f9a146d29,w.2025.11
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 lsst.geom
25import lsst.afw.math as afwMath
26import lsst.pex.config as pexConfig
27import lsst.pipe.base as pipeBase
28from lsst.meas.algorithms import ReferenceSourceSelectorTask
29from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
30from lsst.utils.timer import timeMethod
31from .matchPessimisticB import MatchPessimisticBTask
32from .display import displayAstrometry
33from . import makeMatchStatistics
34
35
36class RefMatchConfig(pexConfig.Config):
37 matcher = pexConfig.ConfigurableField(
38 target=MatchPessimisticBTask,
39 doc="reference object/source matcher",
40 )
41 matchDistanceSigma = pexConfig.RangeField(
42 doc="the maximum match distance is set to "
43 " mean_match_distance + matchDistanceSigma*std_dev_match_distance; "
44 "ignored if not fitting a WCS",
45 dtype=float,
46 default=2,
47 min=0,
48 )
49 sourceSelector = sourceSelectorRegistry.makeField(
50 doc="How to select sources for cross-matching.",
51 default="science",
52 )
53 referenceSelector = pexConfig.ConfigurableField(
54 target=ReferenceSourceSelectorTask,
55 doc="How to select reference objects for cross-matching."
56 )
57 sourceFluxType = pexConfig.Field(
58 dtype=str,
59 doc="Source flux type to use in source selection.",
60 default='Psf'
61 )
62
63 def setDefaults(self):
64 # Configured to match the deprecated "matcher" selector:
65 # SN > 40, some bad flags, valid centroids.
66 self.sourceSelector["science"].doSignalToNoise = True
67 self.sourceSelector["science"].signalToNoise.minimum = 40
68 self.sourceSelector["science"].signalToNoise.fluxField = f"slot_{self.sourceFluxType}Flux_instFlux"
69 self.sourceSelector["science"].signalToNoise.errField = f"slot_{self.sourceFluxType}Flux_instFluxErr"
70 self.sourceSelector["science"].doFlags = True
71 self.sourceSelector["science"].flags.bad = ["base_PixelFlags_flag_edge",
72 "base_PixelFlags_flag_nodata",
73 "base_PixelFlags_flag_interpolatedCenter",
74 "base_PixelFlags_flag_saturated",
75 "base_SdssCentroid_flag",
76 ]
77
78
79class RefMatchTask(pipeBase.Task):
80 """Match an input source catalog with objects from a reference catalog.
81
82 Parameters
83 ----------
84 refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
85 A reference object loader object; gen3 pipeline tasks will pass `None`
86 and call `setRefObjLoader` in `runQuantum`.
87 **kwargs
88 Additional keyword arguments for pipe_base `lsst.pipe.base.Task`.
89 """
90 ConfigClass = RefMatchConfig
91 _DefaultName = "calibrationBaseClass"
92
93 def __init__(self, refObjLoader=None, **kwargs):
94 pipeBase.Task.__init__(self, **kwargs)
95 if refObjLoader:
96 self.refObjLoader = refObjLoader
97 else:
98 self.refObjLoader = None
99
100 if self.config.sourceSelector.name == 'matcher':
101 if self.config.sourceSelector['matcher'].sourceFluxType != self.config.sourceFluxType:
102 raise RuntimeError("The sourceFluxType in the sourceSelector['matcher'] must match "
103 "the configured sourceFluxType")
104
105 self.makeSubtask("matcher")
106 self.makeSubtask("sourceSelector")
107 self.makeSubtask("referenceSelector")
108
109 def setRefObjLoader(self, refObjLoader):
110 """Sets the reference object loader for the task.
111
112 Parameters
113 ----------
114 refObjLoader
115 An instance of a reference object loader task or class.
116 """
117 self.refObjLoader = refObjLoader
118
119 @timeMethod
120 def loadAndMatch(self, exposure, sourceCat):
121 """Load reference objects overlapping an exposure and match to sources
122 detected on that exposure.
123
124 Parameters
125 ----------
126 exposure : `lsst.afw.image.Exposure`
127 exposure that the sources overlap
128 sourceCat : `lsst.afw.table.SourceCatalog.`
129 catalog of sources detected on the exposure
130
131 Returns
132 -------
133 result : `lsst.pipe.base.Struct`
134 Result struct with Components:
135
136 - ``refCat`` : reference object catalog of objects that overlap the
137 exposure (`lsst.afw.table.SimpleCatalog`)
138 - ``matches`` : Matched sources and references
139 (`list` of `lsst.afw.table.ReferenceMatch`)
140 - ``matchMeta`` : metadata needed to unpersist matches
141 (`lsst.daf.base.PropertyList`)
142
143 Notes
144 -----
145 ignores config.matchDistanceSigma
146 """
147 if self.refObjLoader is None:
148 raise RuntimeError("Running matcher task with no refObjLoader set in __ini__ or setRefObjLoader")
149 import lsstDebug
150 debug = lsstDebug.Info(__name__)
151
152 epoch = exposure.visitInfo.date.toAstropy()
153
154 sourceSelection = self.sourceSelector.run(sourceCat)
155
156 sourceFluxField = "slot_%sFlux_instFlux" % (self.config.sourceFluxType)
157
158 loadRes = self.refObjLoader.loadPixelBox(
159 bbox=exposure.getBBox(),
160 wcs=exposure.wcs,
161 filterName=exposure.filter.bandLabel,
162 epoch=epoch,
163 )
164
165 refSelection = self.referenceSelector.run(loadRes.refCat, exposure=exposure)
166
167 matchMeta = self.refObjLoader.getMetadataBox(
168 bbox=exposure.getBBox(),
169 wcs=exposure.wcs,
170 filterName=exposure.filter.bandLabel,
171 epoch=epoch,
172 )
173
174 matchRes = self.matcher.matchObjectsToSources(
175 refCat=refSelection.sourceCat,
176 sourceCat=sourceSelection.sourceCat,
177 wcs=exposure.wcs,
178 sourceFluxField=sourceFluxField,
179 refFluxField=loadRes.fluxField,
180 matchTolerance=None,
181 bbox=exposure.getBBox(),
182 )
183
184 distStats = self._computeMatchStatsOnSky(matchRes.matches)
185 self.log.info(
186 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; ",
187 len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds()
188 )
189
190 if debug.display:
191 frame = int(debug.frame)
193 refCat=refSelection.sourceCat,
194 sourceCat=sourceSelection.sourceCat,
195 matches=matchRes.matches,
196 exposure=exposure,
197 bbox=exposure.getBBox(),
198 frame=frame,
199 title="Matches",
200 )
201
202 return pipeBase.Struct(
203 refCat=loadRes.refCat,
204 refSelection=refSelection,
205 sourceSelection=sourceSelection,
206 matches=matchRes.matches,
207 matchMeta=matchMeta,
208 )
209
210 def _computeMatchStatsOnSky(self, matchList):
211 """Compute on-sky radial distance statistics for a match list
212
213 Parameters
214 ----------
215 matchList : `list` of `lsst.afw.table.ReferenceMatch`
216 list of matches between reference object and sources;
217 the distance field is the only field read and it must be set to distance in radians
218
219 Returns
220 -------
221 result : `lsst.pipe.base.Struct`
222 Result struct with components:
223
224 - ``distMean`` : clipped mean of on-sky radial separation (`float`)
225 - ``distStdDev`` : clipped standard deviation of on-sky radial
226 separation (`float`)
227 - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma *
228 distStdDev (`float`)
229 """
230 distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP)
231 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians
232 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians
233 return pipeBase.Struct(
234 distMean=distMean,
235 distStdDev=distStdDev,
236 maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev,
237 )
__init__(self, refObjLoader=None, **kwargs)
Definition ref_match.py:93
loadAndMatch(self, exposure, sourceCat)
Definition ref_match.py:120
displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition display.py:34
afw::math::Statistics makeMatchStatistics(std::vector< MatchT > const &matchList, int const flags, afw::math::StatisticsControl const &sctrl=afw::math::StatisticsControl())
Compute statistics of the distance field of a match list.