lsst.meas.astrom  16.0-19-ge47c064
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 .matchOptimisticB import MatchOptimisticBTask
34 from .display import displayAstrometry
35 from . import makeMatchStatistics
36 
37 
38 class RefMatchConfig(pexConfig.Config):
39  matcher = pexConfig.ConfigurableField(
40  target=MatchOptimisticBTask,
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  self.refObjLoader = refObjLoader
75  self.makeSubtask("matcher")
76  self.makeSubtask("sourceSelection")
77  self.makeSubtask("referenceSelection")
78 
79  @pipeBase.timeMethod
80  def loadAndMatch(self, exposure, sourceCat):
81  """Load reference objects overlapping an exposure and match to sources
82  detected on that exposure.
83 
84  Parameters
85  ----------
86  exposure : `lsst.afw.image.Exposure`
87  exposure that the sources overlap
88  sourceCat : `lsst.afw.table.SourceCatalog.`
89  catalog of sources detected on the exposure
90 
91  Returns
92  -------
93  result : `lsst.pipe.base.Struct`
94  Result struct with Components:
95 
96  - ``refCat`` : reference object catalog of objects that overlap the
97  exposure (`lsst.afw.table.SimpleCatalog`)
98  - ``matches`` : Matched sources and references
99  (`list` of `lsst.afw.table.ReferenceMatch`)
100  - ``matchMeta`` : metadata needed to unpersist matches
101  (`lsst.daf.base.PropertyList`)
102 
103  Notes
104  -----
105  ignores config.matchDistanceSigma
106  """
107  import lsstDebug
108  debug = lsstDebug.Info(__name__)
109 
110  expMd = self._getExposureMetadata(exposure)
111 
112  sourceSelection = self.sourceSelection.run(sourceCat)
113 
114  loadRes = self.refObjLoader.loadPixelBox(
115  bbox=expMd.bbox,
116  wcs=expMd.wcs,
117  filterName=expMd.filterName,
118  calib=expMd.calib,
119  )
120 
121  refSelection = self.referenceSelection.run(loadRes.refCat)
122 
123  matchMeta = self.refObjLoader.getMetadataBox(
124  bbox=expMd.bbox,
125  wcs=expMd.wcs,
126  filterName=expMd.filterName,
127  calib=expMd.calib,
128  )
129 
130  matchRes = self.matcher.matchObjectsToSources(
131  refCat=refSelection.sourceCat,
132  sourceCat=sourceSelection.sourceCat,
133  wcs=expMd.wcs,
134  refFluxField=loadRes.fluxField,
135  match_tolerance=None,
136  )
137 
138  distStats = self._computeMatchStatsOnSky(matchRes.matches)
139  self.log.info(
140  "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " %
141  (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds())
142  )
143 
144  if debug.display:
145  frame = int(debug.frame)
147  refCat=refSelection.sourceCat,
148  sourceCat=sourceSelection.sourceCat,
149  matches=matchRes.matches,
150  exposure=exposure,
151  bbox=expMd.bbox,
152  frame=frame,
153  title="Matches",
154  )
155 
156  return pipeBase.Struct(
157  refCat=loadRes.refCat,
158  refSelection=refSelection,
159  sourceSelection=sourceSelection,
160  matches=matchRes.matches,
161  matchMeta=matchMeta,
162  )
163 
164  def _computeMatchStatsOnSky(self, matchList):
165  """Compute on-sky radial distance statistics for a match list
166 
167  Parameters
168  ----------
169  matchList : `list` of `lsst.afw.table.ReferenceMatch`
170  list of matches between reference object and sources;
171  the distance field is the only field read and it must be set to distance in radians
172 
173  Returns
174  -------
175  result : `lsst.pipe.base.Struct`
176  Result struct with components:
177 
178  - ``distMean`` : clipped mean of on-sky radial separation (`float`)
179  - ``distStdDev`` : clipped standard deviation of on-sky radial
180  separation (`float`)
181  - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma *
182  distStdDev (`float`)
183  """
184  distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP)
185  distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians
186  distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians
187  return pipeBase.Struct(
188  distMean=distMean,
189  distStdDev=distStdDev,
190  maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev,
191  )
192 
193  def _getExposureMetadata(self, exposure):
194  """Extract metadata from an exposure.
195 
196  Parameters
197  ----------
198  exposure : `lsst.afw.image.Exposure`
199 
200  Returns
201  -------
202  result : `lsst.pipe.base.Struct`
203  Result struct with components:
204 
205  - ``bbox`` : parent bounding box (`lsst.geom.Box2I`)
206  - ``wcs`` : exposure WCS (`lsst.afw.geom.SkyWcs`)
207  - ``calib`` : calibration (`lsst.afw.image.Calib`)
208  - ``filterName`` : name of filter (`str`)
209  - ``epoch`` : date of exposure (`astropy.time.Time`)
210 
211  """
212  exposureInfo = exposure.getInfo()
213  filterName = exposureInfo.getFilter().getName() or None
214  if filterName == "_unknown_":
215  filterName = None
216  epoch = None
217  if exposure.getInfo().hasVisitInfo():
218  epochTaiMjd = exposure.getInfo().getVisitInfo().getDate().get(system=DateTime.MJD,
219  scale=DateTime.TAI)
220  epoch = astropy.time.Time(epochTaiMjd, scale="tai", format="mjd")
221 
222  return pipeBase.Struct(
223  bbox=exposure.getBBox(),
224  wcs=exposureInfo.getWcs(),
225  calib=exposureInfo.getCalib() if exposureInfo.hasCalib() else None,
226  filterName=filterName,
227  epoch=epoch,
228  )
def _computeMatchStatsOnSky(self, matchList)
Definition: ref_match.py:164
def __init__(self, refObjLoader, schema=None, kwargs)
Definition: ref_match.py:72
def _getExposureMetadata(self, exposure)
Definition: ref_match.py:193
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition: display.py:35
def loadAndMatch(self, exposure, sourceCat)
Definition: ref_match.py:80