LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
matchOptimisticBTask.py
Go to the documentation of this file.
1 
2 __all__ = ["MatchOptimisticBTask", "MatchOptimisticBConfig",
3  "MatchTolerance"]
4 
5 import math
6 
7 import lsst.pex.config as pexConfig
8 import lsst.pipe.base as pipeBase
9 
10 from .setMatchDistance import setMatchDistance
11 from .matchOptimisticB import matchOptimisticB, MatchOptimisticBControl
12 
13 
15  """Stores match tolerances for use in `lsst.meas.astrom.AstrometryTask` and
16  later iterations of the matcher.
17 
18  MatchOptimsiticBTask relies on a maximum distance for matching
19  set by either the default in MatchOptimisticBConfig or the 2 sigma
20  scatter found after AstrometryTask has fit for a wcs.
21 
22  Parameters
23  ----------
24  maxMatchDist : `lsst.geom.Angle`
25  Current maximum distance to consider a match.
26  """
27 
28  def __init__(self, maxMatchDist=None):
29  self.maxMatchDist = maxMatchDist
30 
31 
32 class MatchOptimisticBConfig(pexConfig.Config):
33  """Configuration for MatchOptimisticBTask
34  """
35  maxMatchDistArcSec = pexConfig.RangeField(
36  doc="Maximum separation between reference objects and sources "
37  "beyond which they will not be considered a match (arcsec)",
38  dtype=float,
39  default=3,
40  min=0,
41  )
42  numBrightStars = pexConfig.RangeField(
43  doc="Number of bright stars to use",
44  dtype=int,
45  default=50,
46  min=2,
47  )
48  minMatchedPairs = pexConfig.RangeField(
49  doc="Minimum number of matched pairs; see also minFracMatchedPairs",
50  dtype=int,
51  default=30,
52  min=2,
53  )
54  minFracMatchedPairs = pexConfig.RangeField(
55  doc="Minimum number of matched pairs as a fraction of the smaller of "
56  "the number of reference stars or the number of good sources; "
57  "the actual minimum is the smaller of this value or minMatchedPairs",
58  dtype=float,
59  default=0.3,
60  min=0,
61  max=1,
62  )
63  maxOffsetPix = pexConfig.RangeField(
64  doc="Maximum allowed shift of WCS, due to matching (pixel). "
65  "When changing this value, the LoadReferenceObjectsConfig.pixelMargin should also be updated.",
66  dtype=int,
67  default=300,
68  max=4000,
69  )
70  maxRotationDeg = pexConfig.RangeField(
71  doc="Rotation angle allowed between sources and position reference objects (degrees)",
72  dtype=float,
73  default=1.0,
74  max=6.0,
75  )
76  allowedNonperpDeg = pexConfig.RangeField(
77  doc="Allowed non-perpendicularity of x and y (degree)",
78  dtype=float,
79  default=3.0,
80  max=45.0,
81  )
82  numPointsForShape = pexConfig.Field(
83  doc="number of points to define a shape for matching",
84  dtype=int,
85  default=6,
86  )
87  maxDeterminant = pexConfig.Field(
88  doc="maximum determinant of linear transformation matrix for a usable solution",
89  dtype=float,
90  default=0.02,
91  )
92 
93 
94 # The following block adds links to this task from the Task Documentation page.
95 # \addtogroup LSST_task_documentation
96 # \{
97 # \page measAstrom_matchOptimisticBTask
98 # \ref MatchOptimisticBTask "MatchOptimisticBTask"
99 # Match sources to reference objects
100 # \}
101 
102 
103 class MatchOptimisticBTask(pipeBase.Task):
104  """Match sources to reference objects using the Optimistic Pattern Matcher
105  B algorithm of Tabur 2007.
106  """
107  ConfigClass = MatchOptimisticBConfig
108  _DefaultName = "matchObjectsToSources"
109 
110  def __init__(self, **kwargs):
111  pipeBase.Task.__init__(self, **kwargs)
112 
113  def filterStars(self, refCat):
114  """Extra filtering pass; subclass if desired.
115 
116  Parameters
117  ----------
118  refCat : `lsst.afw.table.SimpleCatalog`
119  Catalog of reference objects.
120 
121  Returns
122  -------
123  trimmedRefCat : `lsst.afw.table.SimpleCatalog`
124  Reference catalog with some filtering applied. Currently no
125  filtering is applied.
126  """
127  return refCat
128 
129  @pipeBase.timeMethod
130  def matchObjectsToSources(self, refCat, sourceCat, wcs, sourceFluxField, refFluxField,
131  match_tolerance=None):
132  """Match sources to position reference stars.
133 
134  Parameters
135  ----------
136  refCat : `lsst.afw.table.SimpleCatalog`
137  Reference catalog to match.
138  sourceCat : `lsst.afw.table.SourceCatalog`
139  Catalog of sources found on an exposure. This should already be
140  down-selected to "good"/"usable" sources in the calling Task.
141  wcs : `lsst.afw.geom.SkyWcs`
142  Current WCS of the exposure containing the sources.
143  sourceFluxField : `str`
144  Field of the sourceCat to use for flux
145  refFluxField : `str`
146  Field of the refCat to use for flux
147  match_tolerance : `lsst.meas.astrom.MatchTolerance`
148  Object containing information from previous
149  `lsst.meas.astrom.AstrometryTask` match/fit cycles for use in
150  matching. If `None` is config defaults.
151 
152  Returns
153  -------
154  matchResult : `lsst.pipe.base.Struct`
155  Result struct with components
156 
157  - ``matches`` : List of matches with distance below the maximum match
158  distance (`list` of `lsst.afw.table.ReferenceMatch`).
159  - ``useableSourceCat`` : Catalog of sources matched and suited for
160  WCS fitting (`lsst.afw.table.SourceCatalog`).
161  - ``match_tolerance`` : MatchTolerance object updated from this
162  match iteration (`lsst.meas.astrom.MatchTolerance`).
163  """
164  import lsstDebug
165  debug = lsstDebug.Info(__name__)
166 
167  preNumObj = len(refCat)
168  refCat = self.filterStars(refCat)
169  numRefObj = len(refCat)
170 
171  if self.log:
172  self.log.info("filterStars purged %d reference stars, leaving %d stars" %
173  (preNumObj - numRefObj, numRefObj))
174 
175  if match_tolerance is None:
176  match_tolerance = MatchTolerance()
177 
178  # Make a name alias here for consistency with older code, and to make
179  # it clear that this is a good/usable (cleaned) source catalog.
180  usableSourceCat = sourceCat
181 
182  numUsableSources = len(usableSourceCat)
183 
184  if len(usableSourceCat) == 0:
185  raise pipeBase.TaskError("No sources are usable")
186 
187  minMatchedPairs = min(self.config.minMatchedPairs,
188  int(self.config.minFracMatchedPairs * min([len(refCat), len(usableSourceCat)])))
189 
190  # match usable (possibly saturated) sources and then purge saturated sources from the match list
191  usableMatches = self._doMatch(
192  refCat=refCat,
193  sourceCat=usableSourceCat,
194  wcs=wcs,
195  refFluxField=refFluxField,
196  numUsableSources=numUsableSources,
197  minMatchedPairs=minMatchedPairs,
198  maxMatchDist=match_tolerance.maxMatchDist,
199  sourceFluxField=sourceFluxField,
200  verbose=debug.verbose,
201  )
202 
203  # cull non-good sources
204  matches = []
205  self._getIsGoodKeys(usableSourceCat.schema)
206  for match in usableMatches:
207  if self._isGoodTest(match.second):
208  # Append the isGood match.
209  matches.append(match)
210 
211  self.log.debug("Found %d usable matches, of which %d had good sources",
212  len(usableMatches), len(matches))
213 
214  if len(matches) == 0:
215  raise RuntimeError("Unable to match sources")
216 
217  self.log.info("Matched %d sources" % len(matches))
218  if len(matches) < minMatchedPairs:
219  self.log.warn("Number of matches is smaller than request")
220 
221  return pipeBase.Struct(
222  matches=matches,
223  usableSourceCat=usableSourceCat,
224  match_tolerance=match_tolerance,
225  )
226 
227  def _getIsGoodKeys(self, schema):
228  """Retrieve the keys needed for the isGoodTest from the source catalog
229  schema.
230 
231  Parameters
232  ----------
233  schema : `lsst.afw.table.Schema`
234  Source schema to retrieve `lsst.afw.table.Key` s from.
235  """
236  self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
237  self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
238  self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
239 
240  def _isGoodTest(self, source):
241  """Test that an object is good for use in the WCS fitter.
242 
243  This is a hard coded version of the isGood flag from the old SourceInfo
244  class that used to be part of this class.
245 
246  Parameters
247  ----------
248  source : `lsst.afw.table.SourceRecord`
249  Source to test.
250 
251  Returns
252  -------
253  isGood : `bool`
254  Source passes CCD edge and saturated tests.
255  """
256  return (not source.get(self.edgeKey) and
257  not source.get(self.interpolatedCenterKey) and
258  not source.get(self.saturatedKey))
259 
260  @pipeBase.timeMethod
261  def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs,
262  maxMatchDist, sourceFluxField, verbose):
263  """Implementation of matching sources to position reference stars.
264 
265  Unlike matchObjectsToSources, this method does not check if the sources
266  are suitable.
267 
268  Parameters
269  ----------
270  refCat : `lsst.afw.table.SimpleCatalog`
271  Catalog of reference objects.
272  sourceCat : `lsst.afw.table.SourceCatalog`
273  Catalog of detected sources.
274  wcs : `lsst.afw.geom.SkyWcs`
275  Current best WCS of the image.
276  refFluxFioeld : `str`
277  Name of flux field in refCat to use.
278  numUsableSources : `int`
279  Total number of source usable for matching.
280  mintMatchPairs : `int`
281  Minimum number of objects to match between the refCat and sourceCat
282  to consider a valid match.
283  maxMatchDist : `lsst.geom.Angle`
284  Maximum separation to considering a reference and a source a match.
285  sourceFluxField : `str`
286  Name of source catalog flux field.
287  verbose : `bool`
288  Print diagnostic information std::cout
289 
290  Returns
291  -------
292  matches : `list` of `lsst.afw.table.ReferenceMatch`
293  """
294  numSources = len(sourceCat)
295  posRefBegInd = numUsableSources - numSources
296  if maxMatchDist is None:
297  maxMatchDistArcSec = self.config.maxMatchDistArcSec
298  else:
299  maxMatchDistArcSec = min(maxMatchDist.asArcseconds(), self.config.maxMatchDistArcSec)
300  configMatchDistPix = maxMatchDistArcSec/wcs.getPixelScale().asArcseconds()
301 
302  matchControl = MatchOptimisticBControl()
303  matchControl.refFluxField = refFluxField
304  matchControl.sourceFluxField = sourceFluxField
305  matchControl.numBrightStars = self.config.numBrightStars
306  matchControl.minMatchedPairs = self.config.minMatchedPairs
307  matchControl.maxOffsetPix = self.config.maxOffsetPix
308  matchControl.numPointsForShape = self.config.numPointsForShape
309  matchControl.maxDeterminant = self.config.maxDeterminant
310 
311  for maxRotInd in range(4):
312  matchControl.maxRotationDeg = self.config.maxRotationDeg * math.pow(2.0, 0.5*maxRotInd)
313  for matchRadInd in range(3):
314  matchControl.matchingAllowancePix = configMatchDistPix * math.pow(1.25, matchRadInd)
315 
316  for angleDiffInd in range(3):
317  matchControl.allowedNonperpDeg = self.config.allowedNonperpDeg*(angleDiffInd+1)
318  matches = matchOptimisticB(
319  refCat,
320  sourceCat,
321  matchControl,
322  wcs,
323  posRefBegInd,
324  verbose,
325  )
326  if matches is not None and len(matches) > 0:
327  setMatchDistance(matches)
328  return matches
329  return matches
afw::table::ReferenceMatchVector matchOptimisticB(afw::table::SimpleCatalog const &posRefCat, afw::table::SourceCatalog const &sourceCat, MatchOptimisticBControl const &control, afw::geom::SkyWcs const &wcs, int posRefBegInd=0, bool verbose=false)
Match sources to stars in a position reference catalog using optimistic pattern matching B...
int min
def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs, maxMatchDist, sourceFluxField, verbose)
def matchObjectsToSources(self, refCat, sourceCat, wcs, sourceFluxField, refFluxField, match_tolerance=None)