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