LSSTApplications  11.0-22-g33de520,12.1+21,13.0
LSSTDataManagementBasePackage
matchOptimisticB.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 from builtins import range
3 from builtins import object
4 import math
5 
6 import lsst.pex.config as pexConfig
7 import lsst.pipe.base as pipeBase
8 from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
9 
10 from .setMatchDistance import setMatchDistance
11 from .astromLib import matchOptimisticB, MatchOptimisticBControl
12 
13 __all__ = ["MatchOptimisticBTask", "MatchOptimisticBConfig"]
14 
15 
16 class MatchOptimisticBConfig(pexConfig.Config):
17  """Configuration for MatchOptimisticBTask
18  """
19  maxMatchDistArcSec = pexConfig.RangeField(
20  doc="Maximum separation between reference objects and sources "
21  "beyond which they will not be considered a match (arcsec)",
22  dtype=float,
23  default=3,
24  min=0,
25  )
26  numBrightStars = pexConfig.RangeField(
27  doc="Number of bright stars to use",
28  dtype=int,
29  default=50,
30  min=2,
31  )
32  minMatchedPairs = pexConfig.RangeField(
33  doc="Minimum number of matched pairs; see also minFracMatchedPairs",
34  dtype=int,
35  default=30,
36  min=2,
37  )
38  minFracMatchedPairs = pexConfig.RangeField(
39  doc="Minimum number of matched pairs as a fraction of the smaller of "
40  "the number of reference stars or the number of good sources; "
41  "the actual minimum is the smaller of this value or minMatchedPairs",
42  dtype=float,
43  default=0.3,
44  min=0,
45  max=1,
46  )
47  maxOffsetPix = pexConfig.RangeField(
48  doc="Maximum allowed shift of WCS, due to matching (pixel)",
49  dtype=int,
50  default=300,
51  max=4000,
52  )
53  maxRotationDeg = pexConfig.RangeField(
54  doc="Rotation angle allowed between sources and position reference objects (degrees)",
55  dtype=float,
56  default=1.0,
57  max=6.0,
58  )
59  allowedNonperpDeg = pexConfig.RangeField(
60  doc="Allowed non-perpendicularity of x and y (degree)",
61  dtype=float,
62  default=3.0,
63  max=45.0,
64  )
65  numPointsForShape = pexConfig.Field(
66  doc="number of points to define a shape for matching",
67  dtype=int,
68  default=6,
69  )
70  maxDeterminant = pexConfig.Field(
71  doc="maximum determinant of linear transformation matrix for a usable solution",
72  dtype=float,
73  default=0.02,
74  )
75  sourceSelector = sourceSelectorRegistry.makeField(
76  doc="How to select sources for cross-matching",
77  default="matcher"
78  )
79 
80  def setDefaults(self):
81  sourceSelector = self.sourceSelector["matcher"]
82  sourceSelector.setDefaults()
83 
84 # The following block adds links to this task from the Task Documentation page.
85 # \addtogroup LSST_task_documentation
86 # \{
87 # \page measAstrom_matchOptimisticBTask
88 # \ref MatchOptimisticBTask "MatchOptimisticBTask"
89 # Match sources to reference objects
90 # \}
91 
92 
93 class MatchOptimisticBTask(pipeBase.Task):
94  """!Match sources to reference objects
95 
96  @anchor MatchOptimisticBTask_
97 
98  @section meas_astrom_matchOptimisticB_Contents Contents
99 
100  - @ref meas_astrom_matchOptimisticB_Purpose
101  - @ref meas_astrom_matchOptimisticB_Initialize
102  - @ref meas_astrom_matchOptimisticB_IO
103  - @ref meas_astrom_matchOptimisticB_Config
104  - @ref meas_astrom_matchOptimisticB_Example
105  - @ref meas_astrom_matchOptimisticB_Debug
106 
107  @section meas_astrom_matchOptimisticB_Purpose Description
108 
109  Match sources to reference objects. This is often done as a preliminary step to fitting an astrometric
110  or photometric solution. For details about the matching algorithm see matchOptimisticB.h
111 
112  @section meas_astrom_matchOptimisticB_Initialize Task initialisation
113 
114  @copydoc \_\_init\_\_
115 
116  @section meas_astrom_matchOptimisticB_IO Invoking the Task
117 
118  @copydoc matchObjectsToSources
119 
120  @section meas_astrom_matchOptimisticB_Config Configuration parameters
121 
122  See @ref MatchOptimisticBConfig
123 
124  To modify the tests for usable sources and good sources, subclass SourceInfo and
125  set MatchOptimisticBTask.SourceInfoClass to your subclass.
126 
127  @section meas_astrom_matchOptimisticB_Example A complete example of using MatchOptimisticBTask
128 
129  MatchOptimisticBTask is a subtask of AstrometryTask, which is called by PhotoCalTask.
130  See \ref meas_photocal_photocal_Example.
131 
132  @section meas_astrom_matchOptimisticB_Debug Debug variables
133 
134  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
135  flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
136  @b debug.py files.
137 
138  The available variables in MatchOptimisticBTask are:
139  <DL>
140  <DT> @c verbose (bool)
141  <DD> If True then the matcher prints debug messages to stdout
142  </DL>
143 
144  To investigate the @ref meas_astrom_matchOptimisticB_Debug, put something like
145  @code{.py}
146  import lsstDebug
147  def DebugInfo(name):
148  debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
149  if name == "lsst.pipe.tasks.astrometry":
150  debug.verbose = True
151 
152  return debug
153 
154  lsstDebug.Info = DebugInfo
155  @endcode
156  into your debug.py file and run this task with the @c --debug flag.
157  """
158  ConfigClass = MatchOptimisticBConfig
159  _DefaultName = "matchObjectsToSources"
160 
161  def __init__(self, **kwargs):
162  pipeBase.Task.__init__(self, **kwargs)
163  self.makeSubtask("sourceSelector")
164 
165  def filterStars(self, refCat):
166  """Extra filtering pass; subclass if desired
167  """
168  return refCat
169 
170  @pipeBase.timeMethod
171  def matchObjectsToSources(self, refCat, sourceCat, wcs, refFluxField, maxMatchDist=None):
172  """!Match sources to position reference stars
173 
174  @param[in] refCat catalog of reference objects that overlap the exposure; reads fields for:
175  - coord
176  - the specified flux field
177  @param[in] sourceCat catalog of sources found on an exposure; reads fields for:
178  - centroid
179  - centroid flag
180  - edge flag
181  - saturated flag
182  - aperture flux, if found, else PSF flux
183  @param[in] wcs estimated WCS
184  @param[in] refFluxField field of refCat to use for flux
185  @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
186  (an lsst.afw.geom.Angle); if specified then the smaller of config.maxMatchDistArcSec or
187  maxMatchDist is used; if None then config.maxMatchDistArcSec is used
188  @return an lsst.pipe.base.Struct with fields:
189  - matches a list of matches, each instance of lsst.afw.table.ReferenceMatch
190  - usableSourcCat a catalog of sources potentially usable for matching.
191  For this fitter usable sources include unresolved sources not too near the edge.
192  It includes saturated sources, even those these are removed from the final match list,
193  because saturated sources may be used to determine the match list.
194  """
195  import lsstDebug
196  debug = lsstDebug.Info(__name__)
197 
198  preNumObj = len(refCat)
199  refCat = self.filterStars(refCat)
200  numRefObj = len(refCat)
201 
202  if self.log:
203  self.log.info("filterStars purged %d reference stars, leaving %d stars" %
204  (preNumObj - numRefObj, numRefObj))
205 
206  # usableSourceCat: sources that are good but may be saturated
207  numSources = len(sourceCat)
208  selectedSources = self.sourceSelector.selectSources(sourceCat)
209  usableSourceCat = selectedSources.sourceCat
210  numUsableSources = len(usableSourceCat)
211  self.log.info("Purged %d unusable sources, leaving %d usable sources" %
212  (numSources - numUsableSources, numUsableSources))
213 
214  if len(usableSourceCat) == 0:
215  raise pipeBase.TaskError("No sources are usable")
216 
217  del sourceCat # avoid accidentally using sourceCat; use usableSourceCat or goodSourceCat from now on
218 
219  minMatchedPairs = min(self.config.minMatchedPairs,
220  int(self.config.minFracMatchedPairs * min([len(refCat), len(usableSourceCat)])))
221 
222  # match usable (possibly saturated) sources and then purge saturated sources from the match list
223  usableMatches = self._doMatch(
224  refCat=refCat,
225  sourceCat=usableSourceCat,
226  wcs=wcs,
227  refFluxField=refFluxField,
228  numUsableSources=numUsableSources,
229  minMatchedPairs=minMatchedPairs,
230  maxMatchDist=maxMatchDist,
231  sourceFluxField=self.sourceSelector.fluxField,
232  verbose=debug.verbose,
233  )
234 
235  # cull non-good sources
236  matches = []
237  self._getIsGoodKeys(usableSourceCat.schema)
238  for match in usableMatches:
239  if self._isGoodTest(match.second):
240  # Append the isGood match.
241  matches.append(match)
242 
243  self.log.debug("Found %d usable matches, of which %d had good sources",
244  len(usableMatches), len(matches))
245 
246  if len(matches) == 0:
247  raise RuntimeError("Unable to match sources")
248 
249  self.log.info("Matched %d sources" % len(matches))
250  if len(matches) < minMatchedPairs:
251  self.log.warn("Number of matches is smaller than request")
252 
253  return pipeBase.Struct(
254  matches=matches,
255  usableSourceCat=usableSourceCat,
256  )
257 
258  def _getIsGoodKeys(self, schema):
259  self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
260  self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
261  self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
262 
263  def _isGoodTest(self, source):
264  """
265  This is a hard coded version of the isGood flag from the old SourceInfo class that used to be
266  part of this class. This is done current as the API for sourceSelector does not currently
267  support matchLists.
268  """
269  return (not source.get(self.edgeKey) and
270  not source.get(self.interpolatedCenterKey) and
271  not source.get(self.saturatedKey))
272 
273  @pipeBase.timeMethod
274  def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs,
275  maxMatchDist, sourceFluxField, verbose):
276  """!Implementation of matching sources to position reference stars
277 
278  Unlike matchObjectsToSources, this method does not check if the sources are suitable.
279 
280  @param[in] refCat catalog of position reference stars that overlap an exposure
281  @param[in] sourceCat catalog of sources found on the exposure
282  @param[in] wcs estimated WCS of exposure
283  @param[in] refFluxField field of refCat to use for flux
284  @param[in] numUsableSources number of usable sources (sources with known centroid
285  that are not near the edge, but may be saturated)
286  @param[in] minMatchedPairs minimum number of matches
287  @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
288  (an lsst.afw.geom.Angle); if specified then the smaller of config.maxMatchDistArcSec or
289  maxMatchDist is used; if None then config.maxMatchDistArcSec is used
290  @param[in] sourceInfo SourceInfo for the sourceCat
291  @param[in] verbose true to print diagnostic information to std::cout
292 
293  @return a list of matches, an instance of lsst.afw.table.ReferenceMatch
294  """
295  numSources = len(sourceCat)
296  posRefBegInd = numUsableSources - numSources
297  if maxMatchDist is None:
298  maxMatchDistArcSec = self.config.maxMatchDistArcSec
299  else:
300  maxMatchDistArcSec = min(maxMatchDist.asArcseconds(), self.config.maxMatchDistArcSec)
301  configMatchDistPix = maxMatchDistArcSec/wcs.pixelScale().asArcseconds()
302 
303  matchControl = MatchOptimisticBControl()
304  matchControl.refFluxField = refFluxField
305  matchControl.sourceFluxField = sourceFluxField
306  matchControl.numBrightStars = self.config.numBrightStars
307  matchControl.minMatchedPairs = self.config.minMatchedPairs
308  matchControl.maxOffsetPix = self.config.maxOffsetPix
309  matchControl.numPointsForShape = self.config.numPointsForShape
310  matchControl.maxDeterminant = self.config.maxDeterminant
311 
312  for maxRotInd in range(4):
313  matchControl.maxRotationDeg = self.config.maxRotationDeg * math.pow(2.0, 0.5*maxRotInd)
314  for matchRadInd in range(3):
315  matchControl.matchingAllowancePix = configMatchDistPix * math.pow(1.25, matchRadInd)
316 
317  for angleDiffInd in range(3):
318  matchControl.allowedNonperpDeg = self.config.allowedNonperpDeg*(angleDiffInd+1)
319  matches = matchOptimisticB(
320  refCat,
321  sourceCat,
322  matchControl,
323  wcs,
324  posRefBegInd,
325  verbose,
326  )
327  if matches is not None and len(matches) > 0:
328  setMatchDistance(matches)
329  return matches
330  return matches
def matchObjectsToSources
Match sources to position reference stars.
lsst::afw::table::ReferenceMatchVector matchOptimisticB(lsst::afw::table::SimpleCatalog const &posRefCat, lsst::afw::table::SourceCatalog const &sourceCat, MatchOptimisticBControl const &control, afw::image::Wcs const &wcs, int posRefBegInd=0, bool verbose=false)
Match sources to stars in a position reference catalog using optimistic pattern matching B...
def _doMatch
Implementation of matching sources to position reference stars.