lsst.meas.astrom  14.0-7-g0d69b06+3
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 from __future__ import absolute_import, division, print_function
23 
24 __all__ = ['RefMatchConfig', 'RefMatchTask']
25 
26 import lsst.afw.geom as afwGeom
27 import lsst.afw.math as afwMath
28 import lsst.pex.config as pexConfig
29 import lsst.pipe.base as pipeBase
30 from lsst.meas.algorithms import ScienceSourceSelectorTask, ReferenceSourceSelectorTask
31 from .matchOptimisticB import MatchOptimisticBTask
32 from .display import displayAstrometry
33 from . import makeMatchStatistics
34 
35 
36 class RefMatchConfig(pexConfig.Config):
37  matcher = pexConfig.ConfigurableField(
38  target=MatchOptimisticBTask,
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  sourceSelection = pexConfig.ConfigurableField(target=ScienceSourceSelectorTask,
50  doc="Selection of science sources")
51  referenceSelection = pexConfig.ConfigurableField(target=ReferenceSourceSelectorTask,
52  doc="Selection of reference sources")
53 
54 # The following block adds links to this task from the Task Documentation page.
55 
61 
62 
63 class RefMatchTask(pipeBase.Task):
64  """!Match an input source catalog with objects from a reference catalog
65 
66  @anchor RefMatchTask_
67  """
68  ConfigClass = RefMatchConfig
69  _DefaultName = "calibrationBaseClass"
70 
71  def __init__(self, refObjLoader, schema=None, **kwargs):
72  """!Construct a RefMatchTask
73 
74  @param[in] refObjLoader A reference object loader object
75  @param[in] schema ignored; available for compatibility with an older astrometry task
76  @param[in] kwargs additional keyword arguments for pipe_base Task.\_\_init\_\_
77  """
78  pipeBase.Task.__init__(self, **kwargs)
79  self.refObjLoader = refObjLoader
80  self.makeSubtask("matcher")
81  self.makeSubtask("sourceSelection")
82  self.makeSubtask("referenceSelection")
83 
84  @pipeBase.timeMethod
85  def loadAndMatch(self, exposure, sourceCat):
86  """!Load reference objects overlapping an exposure and match to sources detected on that exposure
87 
88  @param[in] exposure exposure that the sources overlap
89  @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
90 
91  @return an lsst.pipe.base.Struct with these fields:
92  - refCat reference object catalog of objects that overlap the exposure (with some margin)
93  (an lsst::afw::table::SimpleCatalog)
94  - matches a list of lsst.afw.table.ReferenceMatch
95  - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
96 
97  @note ignores config.matchDistanceSigma
98  """
99  import lsstDebug
100  debug = lsstDebug.Info(__name__)
101 
102  expMd = self._getExposureMetadata(exposure)
103 
104  sourceSelection = self.sourceSelection.selectSources(sourceCat)
105 
106  loadRes = self.refObjLoader.loadPixelBox(
107  bbox=expMd.bbox,
108  wcs=expMd.wcs,
109  filterName=expMd.filterName,
110  calib=expMd.calib,
111  )
112 
113  refSelection = self.referenceSelection.selectSources(loadRes.refCat)
114 
115  matchMeta = self.refObjLoader.getMetadataBox(
116  bbox=expMd.bbox,
117  wcs=expMd.wcs,
118  filterName=expMd.filterName,
119  calib=expMd.calib,
120  )
121 
122  matchRes = self.matcher.matchObjectsToSources(
123  refCat=refSelection.sourceCat,
124  sourceCat=sourceSelection.sourceCat,
125  wcs=expMd.wcs,
126  refFluxField=loadRes.fluxField,
127  match_tolerance=None,
128  )
129 
130  distStats = self._computeMatchStatsOnSky(matchRes.matches)
131  self.log.info(
132  "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " %
133  (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds())
134  )
135 
136  if debug.display:
137  frame = int(debug.frame)
139  refCat=refSelection.sourceCat,
140  sourceCat=sourceSelection.sourceCat,
141  matches=matchRes.matches,
142  exposure=exposure,
143  bbox=expMd.bbox,
144  frame=frame,
145  title="Matches",
146  )
147 
148  return pipeBase.Struct(
149  refCat=loadRes.refCat,
150  refSelection=refSelection,
151  sourceSelection=sourceSelection,
152  matches=matchRes.matches,
153  matchMeta=matchMeta,
154  )
155 
156  def _computeMatchStatsOnSky(self, matchList):
157  """Compute on-sky radial distance statistics for a match list
158 
159  @param[in] matchList list of matches between reference object and sources;
160  the distance field is the only field read and it must be set to distance in radians
161 
162  @return a pipe_base Struct containing these fields:
163  - distMean clipped mean of on-sky radial separation
164  - distStdDev clipped standard deviation of on-sky radial separation
165  - maxMatchDist distMean + self.config.matchDistanceSigma*distStdDev
166  """
167  distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP)
168  distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*afwGeom.radians
169  distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*afwGeom.radians
170  return pipeBase.Struct(
171  distMean=distMean,
172  distStdDev=distStdDev,
173  maxMatchDist=distMean + self.config.matchDistanceSigma*distStdDev,
174  )
175 
176  def _getExposureMetadata(self, exposure):
177  """!Extract metadata from an exposure
178 
179  @return an lsst.pipe.base.Struct containing the following exposure metadata:
180  - bbox: parent bounding box
181  - wcs: WCS (an lsst.afw.geom.Wcs)
182  - calib calibration (an lsst.afw.image.Calib), or None if unknown
183  - filterName: name of filter, or None if unknown
184  """
185  exposureInfo = exposure.getInfo()
186  filterName = exposureInfo.getFilter().getName() or None
187  if filterName == "_unknown_":
188  filterName = None
189  return pipeBase.Struct(
190  bbox=exposure.getBBox(),
191  wcs=exposureInfo.getWcs(),
192  calib=exposureInfo.getCalib() if exposureInfo.hasCalib() else None,
193  filterName=filterName,
194  )
def _computeMatchStatsOnSky(self, matchList)
Definition: ref_match.py:156
def __init__(self, refObjLoader, schema=None, kwargs)
Construct a RefMatchTask.
Definition: ref_match.py:71
def _getExposureMetadata(self, exposure)
Extract metadata from an exposure.
Definition: ref_match.py:176
Match an input source catalog with objects from a reference catalog.
Definition: ref_match.py:63
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition: display.py:37
def loadAndMatch(self, exposure, sourceCat)
Load reference objects overlapping an exposure and match to sources detected on that exposure...
Definition: ref_match.py:85