lsst.meas.astrom main-g5babe484a3+4d4b223381
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 lsst.utils.timer import timeMethod
35from .matchPessimisticB import MatchPessimisticBTask
36from .display import displayAstrometry
37from . import makeMatchStatistics
38
39
40class RefMatchConfig(pexConfig.Config):
41 matcher = pexConfig.ConfigurableField(
42 target=MatchPessimisticBTask,
43 doc="reference object/source matcher",
44 )
45 matchDistanceSigma = pexConfig.RangeField(
46 doc="the maximum match distance is set to "
47 " mean_match_distance + matchDistanceSigma*std_dev_match_distance; "
48 "ignored if not fitting a WCS",
49 dtype=float,
50 default=2,
51 min=0,
52 )
53 sourceSelector = sourceSelectorRegistry.makeField(
54 doc="How to select sources for cross-matching.",
55 default="science",
56 )
57 referenceSelector = pexConfig.ConfigurableField(
58 target=ReferenceSourceSelectorTask,
59 doc="How to select reference objects for cross-matching."
60 )
61 sourceFluxType = pexConfig.Field(
62 dtype=str,
63 doc="Source flux type to use in source selection.",
64 default='Calib'
65 )
66
67 def setDefaults(self):
68 self.sourceSelectorsourceSelector.name = "science"
69 self.sourceSelectorsourceSelector['science'].fluxLimit.fluxField = \
70 'slot_%sFlux_instFlux' % (self.sourceFluxTypesourceFluxType)
71 self.sourceSelectorsourceSelector['science'].signalToNoise.fluxField = \
72 'slot_%sFlux_instFlux' % (self.sourceFluxTypesourceFluxType)
73 self.sourceSelectorsourceSelector['science'].signalToNoise.errField = \
74 'slot_%sFlux_instFluxErr' % (self.sourceFluxTypesourceFluxType)
75
76
77class RefMatchTask(pipeBase.Task):
78 """Match an input source catalog with objects from a reference catalog.
79
80 Parameters
81 ----------
82 refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
83 A reference object loader object
84 **kwargs
85 additional keyword arguments for pipe_base `lsst.pipe.base.Task`
86 """
87 ConfigClass = RefMatchConfig
88 _DefaultName = "calibrationBaseClass"
89
90 def __init__(self, refObjLoader, **kwargs):
91 pipeBase.Task.__init__(self, **kwargs)
92 if refObjLoader:
93 self.refObjLoaderrefObjLoader = refObjLoader
94 else:
95 self.refObjLoaderrefObjLoader = None
96
97 if self.config.sourceSelector.name == 'matcher':
98 if self.config.sourceSelector['matcher'].sourceFluxType != self.config.sourceFluxType:
99 raise RuntimeError("The sourceFluxType in the sourceSelector['matcher'] must match "
100 "the configured sourceFluxType")
101
102 self.makeSubtask("matcher")
103 self.makeSubtask("sourceSelector")
104 self.makeSubtask("referenceSelector")
105
106 def setRefObjLoader(self, refObjLoader):
107 """Sets the reference object loader for the task
108
109 Parameters
110 ----------
111 refObjLoader
112 An instance of a reference object loader task or class
113 """
114 self.refObjLoaderrefObjLoader = refObjLoader
115
116 @timeMethod
117 def loadAndMatch(self, exposure, sourceCat):
118 """Load reference objects overlapping an exposure and match to sources
119 detected on that exposure.
120
121 Parameters
122 ----------
123 exposure : `lsst.afw.image.Exposure`
124 exposure that the sources overlap
125 sourceCat : `lsst.afw.table.SourceCatalog.`
126 catalog of sources detected on the exposure
127
128 Returns
129 -------
130 result : `lsst.pipe.base.Struct`
131 Result struct with Components:
132
133 - ``refCat`` : reference object catalog of objects that overlap the
135 - ``matches`` : Matched sources and references
137 - ``matchMeta`` : metadata needed to unpersist matches
139
140 Notes
141 -----
142 ignores config.matchDistanceSigma
143 """
144 if self.refObjLoaderrefObjLoader is None:
145 raise RuntimeError("Running matcher task with no refObjLoader set in __ini__ or setRefObjLoader")
146 import lsstDebug
147 debug = lsstDebug.Info(__name__)
148
149 expMd = self._getExposureMetadata_getExposureMetadata(exposure)
150
151 sourceSelection = self.sourceSelector.run(sourceCat)
152
153 sourceFluxField = "slot_%sFlux_instFlux" % (self.config.sourceFluxType)
154
155 loadRes = self.refObjLoaderrefObjLoader.loadPixelBox(
156 bbox=expMd.bbox,
157 wcs=expMd.wcs,
158 filterName=expMd.filterName,
159 photoCalib=expMd.photoCalib,
160 epoch=expMd.epoch,
161 )
162
163 refSelection = self.referenceSelector.run(loadRes.refCat)
164
165 matchMeta = self.refObjLoaderrefObjLoader.getMetadataBox(
166 bbox=expMd.bbox,
167 wcs=expMd.wcs,
168 filterName=expMd.filterName,
169 photoCalib=expMd.photoCalib,
170 epoch=expMd.epoch,
171 )
172
173 matchRes = self.matcher.matchObjectsToSources(
174 refCat=refSelection.sourceCat,
175 sourceCat=sourceSelection.sourceCat,
176 wcs=expMd.wcs,
177 sourceFluxField=sourceFluxField,
178 refFluxField=loadRes.fluxField,
179 match_tolerance=None,
180 )
181
182 distStats = self._computeMatchStatsOnSky_computeMatchStatsOnSky(matchRes.matches)
183 self.log.info(
184 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; ",
185 len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds()
186 )
187
188 if debug.display:
189 frame = int(debug.frame)
191 refCat=refSelection.sourceCat,
192 sourceCat=sourceSelection.sourceCat,
193 matches=matchRes.matches,
194 exposure=exposure,
195 bbox=expMd.bbox,
196 frame=frame,
197 title="Matches",
198 )
199
200 return pipeBase.Struct(
201 refCat=loadRes.refCat,
202 refSelection=refSelection,
203 sourceSelection=sourceSelection,
204 matches=matchRes.matches,
205 matchMeta=matchMeta,
206 )
207
208 def _computeMatchStatsOnSky(self, matchList):
209 """Compute on-sky radial distance statistics for a match list
210
211 Parameters
212 ----------
213 matchList : `list` of `lsst.afw.table.ReferenceMatch`
214 list of matches between reference object and sources;
215 the distance field is the only field read and it must be set to distance in radians
216
217 Returns
218 -------
219 result : `lsst.pipe.base.Struct`
220 Result struct with components:
221
222 - ``distMean`` : clipped mean of on-sky radial separation (`float`)
223 - ``distStdDev`` : clipped standard deviation of on-sky radial
224 separation (`float`)
225 - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma *
226 distStdDev (`float`)
227 """
228 distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP)
229 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians
230 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians
231 return pipeBase.Struct(
232 distMean=distMean,
233 distStdDev=distStdDev,
234 maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev,
235 )
236
237 def _getExposureMetadata(self, exposure):
238 """Extract metadata from an exposure.
239
240 Parameters
241 ----------
242 exposure : `lsst.afw.image.Exposure`
243
244 Returns
245 -------
246 result : `lsst.pipe.base.Struct`
247 Result struct with components:
248
249 - ``bbox`` : parent bounding box (`lsst.geom.Box2I`)
250 - ``wcs`` : exposure WCS (`lsst.afw.geom.SkyWcs`)
251 - ``photoCalib`` : photometric calibration (`lsst.afw.image.PhotoCalib`)
252 - ``filterName`` : name of filter band (`str`)
253 - ``epoch`` : date of exposure (`astropy.time.Time`)
254
255 """
256 exposureInfo = exposure.getInfo()
257 filterLabel = exposureInfo.getFilterLabel()
258 filterName = filterLabel.bandLabel if filterLabel is not None else None
259 epoch = None
260 if exposure.getInfo().hasVisitInfo():
261 epochTaiMjd = exposure.getInfo().getVisitInfo().getDate().get(system=DateTime.MJD,
262 scale=DateTime.TAI)
263 epoch = astropy.time.Time(epochTaiMjd, scale="tai", format="mjd")
264
265 return pipeBase.Struct(
266 bbox=exposure.getBBox(),
267 wcs=exposureInfo.getWcs(),
268 photoCalib=exposureInfo.getPhotoCalib(),
269 filterName=filterName,
270 epoch=epoch,
271 )
def _computeMatchStatsOnSky(self, matchList)
Definition: ref_match.py:208
def loadAndMatch(self, exposure, sourceCat)
Definition: ref_match.py:117
def __init__(self, refObjLoader, **kwargs)
Definition: ref_match.py:90
def _getExposureMetadata(self, exposure)
Definition: ref_match.py:237
def setRefObjLoader(self, refObjLoader)
Definition: ref_match.py:106
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition: display.py:34