LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
LSST Data Management Base Package
ref_match.py
Go to the documentation of this file.
1 #
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 
25 import astropy.time
26 
27 import lsst.geom
28 from lsst.daf.base import DateTime
29 import lsst.afw.math as afwMath
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
37 
38 
39 class 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.sourceSelectorsourceSelector.name = "science"
68  self.sourceSelectorsourceSelector['science'].fluxLimit.fluxField = \
69  'slot_%sFlux_instFlux' % (self.sourceFluxTypesourceFluxType)
70  self.sourceSelectorsourceSelector['science'].signalToNoise.fluxField = \
71  'slot_%sFlux_instFlux' % (self.sourceFluxTypesourceFluxType)
72  self.sourceSelectorsourceSelector['science'].signalToNoise.errField = \
73  'slot_%sFlux_instFluxErr' % (self.sourceFluxTypesourceFluxType)
74 
75 
76 class 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.refObjLoaderrefObjLoader = refObjLoader
93  else:
94  self.refObjLoaderrefObjLoader = 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.refObjLoaderrefObjLoader = 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
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.refObjLoaderrefObjLoader 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_getExposureMetadata(exposure)
149 
150  sourceSelection = self.sourceSelector.run(sourceCat)
151 
152  sourceFluxField = "slot_%sFlux_instFlux" % (self.config.sourceFluxType)
153 
154  loadRes = self.refObjLoaderrefObjLoader.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.refObjLoaderrefObjLoader.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_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)
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
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition: display.py:34
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)