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