LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
matchOptimisticB.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 import math
3 import numpy
4 
5 import lsst.afw.table as afwTable
6 import lsst.pex.config as pexConfig
7 import lsst.pipe.base as pipeBase
8 from .astromLib import matchOptimisticB, MatchOptimisticBControl
9 
10 __all__ = ["MatchOptimisticBTask", "MatchOptimisticBConfig"]
11 
12 class MatchOptimisticBConfig(pexConfig.Config):
13  sourceFluxType = pexConfig.Field(
14  doc = "Type of source flux; typically one of Ap or Psf",
15  dtype = str,
16  default = "Ap",
17  )
18  maxMatchDistArcSec = pexConfig.RangeField(
19  doc = "Maximum radius for matching sources to reference objects (arcsec)",
20  dtype = float,
21  default = 1,
22  min = 0,
23  )
24  numBrightStars = pexConfig.RangeField(
25  doc = "Number of bright stars to use",
26  dtype = int,
27  default = 50,
28  min = 2,
29  )
30  minMatchedPairs = pexConfig.RangeField(
31  doc = "Minimum number of matched pairs; see also minFracMatchedPairs",
32  dtype = int,
33  default = 30,
34  min = 2,
35  )
36  minFracMatchedPairs = pexConfig.RangeField(
37  doc = "Minimum number of matched pairs as a fraction of the smaller of "
38  "the number of reference stars or the number of good sources; "
39  "the actual minimum is the smaller of this value or minMatchedPairs",
40  dtype = float,
41  default = 0.3,
42  min = 0,
43  max = 1,
44  )
45  maxOffsetPix = pexConfig.RangeField(
46  doc = "Maximum offset between sources and position reference objects (pixel)",
47  dtype = int,
48  default = 300,
49  max = 4000,
50  )
51  rotationAllowedInRad = pexConfig.RangeField(
52  doc = "Rotation angle allowed between sources and position reference objects (radian)",
53  dtype = float,
54  default = 0.02,
55  max = 0.1,
56  )
57  angleDiffFrom90 = pexConfig.RangeField(
58  doc = "Difference of angle between x and y from 90 degree allowed (degree)",
59  dtype = float,
60  default = 0.2,
61  max = 45.0,
62  )
63  numPointsForShape = pexConfig.Field(
64  doc = "number of points to define a shape for matching",
65  dtype = int,
66  default = 6,
67  )
68  maxDeterminant = pexConfig.Field(
69  doc = "maximum determinant of linear transformation matrix for a usable solution",
70  dtype = float,
71  default = 0.02,
72  )
73 
74 class SourceInfo(object):
75  """Provide information about sources in a source catalog for various versions of the schema
76 
77  Fields set include:
78  - edgeKey key for field that is True if source is near an edge
79  - saturatedKey key for field that is True if source has any saturated pixels
80  - centroidKey key for centroid
81  - centroidFlagKey key for flag that is True if centroid is valid
82  - fluxField name of flux field
83 
84  @throw RuntimeError if schema version unsupported or a needed field not found
85  """
86  def __init__(self, schema, fluxType="Ap"):
87  """Construct a SourceInfo
88 
89  @param[in] schema source catalog schema
90  @param[in] fluxType flux type: typically one of "Ap" or "Psf"
91 
92  @throw RuntimeError if the flux field is not found
93  """
94  version = schema.getVersion()
95  if version == 0:
96  self.edgeKey = schema["flags.pixel.edge"].asKey()
97  self.saturatedKey = schema["flags.pixel.saturated.any"].asKey()
98  self.centroidKey = afwTable.Point2DKey(schema["slot.Centroid"])
99  self.centroidFlagKey = schema["slot.Centroid.flags"].asKey()
100  self.fluxField = "slot.%sFlux" % (fluxType,)
101  elif version == 1:
102  self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
103  self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
104  self.centroidKey = afwTable.Point2DKey(schema["slot_Centroid"])
105  self.centroidFlagKey = schema["slot_Centroid_flag"].asKey()
106  self.fluxField = "slot_%sFlux_flux" % (fluxType,)
107  else:
108  raise RuntimeError("Version %r of sourceCat schema not supported" % (version,))
109 
110  if self.fluxField not in schema:
111  raise RuntimeError("Could not find flux field %s in source schema" % (self.fluxField,))
112 
113  def hasCentroid(self, source):
114  """Return True if the source has a valid centroid
115  """
116  centroid = source.get(self.centroidKey)
117  return numpy.all(numpy.isfinite(centroid)) and not source.getCentroidFlag()
118 
119  def isCleanSource(self, source):
120  """Return True if the source has a valid centroid and is not near the edge
121  """
122  return self.hasCentroid(source) and not source.get(self.edgeKey)
123 
124  def isGoodSource(self, source):
125  """Return True if source is clean (as per isCleanSource) and is not saturated
126  """
127  return self.isCleanSource(source) and not source.get(self.saturatedKey)
128 
129 
130 # The following block adds links to this task from the Task Documentation page.
131 ## \addtogroup LSST_task_documentation
132 ## \{
133 ## \page measAstrom_matchOptimisticBTask
134 ## \ref MatchOptimisticBTask "MatchOptimisticBTask"
135 ## Match sources to reference objects
136 ## \}
137 
138 class MatchOptimisticBTask(pipeBase.Task):
139  """!Match sources to reference objects
140 
141  @anchor MatchOptimisticBTask_
142 
143  @section meas_astrom_matchOptimisticB_Contents Contents
144 
145  - @ref meas_astrom_matchOptimisticB_Purpose
146  - @ref meas_astrom_matchOptimisticB_Initialize
147  - @ref meas_astrom_matchOptimisticB_IO
148  - @ref meas_astrom_matchOptimisticB_Config
149  - @ref meas_astrom_matchOptimisticB_Example
150  - @ref meas_astrom_matchOptimisticB_Debug
151 
152  @section meas_astrom_matchOptimisticB_Purpose Description
153 
154  Match sources to reference objects. This is often done as a preliminary step to fitting an astrometric
155  or photometric solution. For details about the matching algorithm see matchOptimisticB.h
156 
157  @section meas_astrom_matchOptimisticB_Initialize Task initialisation
158 
159  @copydoc \_\_init\_\_
160 
161  @section meas_astrom_matchOptimisticB_IO Invoking the Task
162 
163  @copydoc matchObjectsToSources
164 
165  @section meas_astrom_matchOptimisticB_Config Configuration parameters
166 
167  See @ref MatchOptimisticBConfig
168 
169  @section meas_astrom_matchOptimisticB_Example A complete example of using MatchOptimisticBTask
170 
171  MatchOptimisticBTask is a subtask of AstrometryTask, which is called by PhotoCalTask.
172  See \ref meas_photocal_photocal_Example.
173 
174  @section meas_astrom_matchOptimisticB_Debug Debug variables
175 
176  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
177  flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
178  @b debug.py files.
179 
180  The available variables in MatchOptimisticBTask are:
181  <DL>
182  <DT> @c verbose (bool)
183  <DD> If True then the matcher prints debug messages to stdout
184  </DL>
185 
186  To investigate the @ref meas_astrom_matchOptimisticB_Debug, put something like
187  @code{.py}
188  import lsstDebug
189  def DebugInfo(name):
190  debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
191  if name == "lsst.pipe.tasks.astrometry":
192  debug.verbose = True
193 
194  return debug
195 
196  lsstDebug.Info = DebugInfo
197  @endcode
198  into your debug.py file and run this task with the @c --debug flag.
199  """
200  ConfigClass = MatchOptimisticBConfig
201  _DefaultName = "matchObjectsToSources"
202 
203  def filterStars(self, refCat):
204  """Extra filtering pass; subclass if desired
205  """
206  return refCat
207 
208  @pipeBase.timeMethod
209  def matchObjectsToSources(self, refCat, sourceCat, wcs, refFluxField):
210  """!Match sources to position reference stars
211 
212  @param[in] refCat catalog of reference objects that overlap the exposure; reads fields for:
213  - coord
214  - the specified flux field
215  @param[in] sourceCat catalog of sources found on an exposure; reads fields for:
216  - centroid
217  - centroid flag
218  - edge flag
219  - saturated flag
220  - aperture flux, if found, else PSF flux
221  @param[in] wcs estimated WCS
222  @param[in] refFluxField field of refCat to use for flux
223  @return an lsst.pipe.base.Struct with fields:
224  - matches a list of matches, an instance of lsst.afw.table.ReferenceMatch
225  """
226  import lsstDebug
227  debug = lsstDebug.Info(__name__)
228 
229  preNumObj = len(refCat)
230  refCat = self.filterStars(refCat)
231  numRefObj = len(refCat)
232 
233  if self.log: self.log.info("filterStars purged %d reference stars, leaving %d stars" % \
234  (preNumObj - numRefObj, numRefObj))
235 
236  sourceInfo = SourceInfo(schema=sourceCat.schema, fluxType=self.config.sourceFluxType)
237 
238  # cleanSourceCat: sources that are good but may be saturated
239  numSources = len(sourceCat)
240  cleanSourceCat = afwTable.SourceCatalog(sourceCat.table)
241  cleanSourceCat.extend(s for s in sourceCat if sourceInfo.isCleanSource(s))
242  numCleanSources = len(cleanSourceCat)
243  self.log.info("Purged %d unsuable sources, leaving %d clean sources" % \
244  (numSources - numCleanSources, numCleanSources))
245 
246  # goodSourceCat: sources that are good and not saturated
247  goodSources = afwTable.SourceCatalog(sourceCat.table)
248  goodSources.extend(s for s in cleanSourceCat if sourceInfo.isGoodSource(s))
249  numGoodSources = len(goodSources)
250  self.log.info("Purged %d saturated sources, leaving %d good sources" % \
251  (numCleanSources - numGoodSources, numGoodSources))
252 
253  del sourceCat # avoid accidentally using sourceCat; use cleanSourceCat or goodSources from now on
254 
255  self.log.info("Matching to %d/%d good input sources" % (len(goodSources), len(cleanSourceCat)))
256 
257  minMatchedPairs = min(self.config.minMatchedPairs,
258  int(self.config.minFracMatchedPairs * min([len(refCat), len(goodSources)])))
259 
260  # match clean (possibly saturated) sources and then purge saturated sources from the match list
261  matches0 = self._doMatch(
262  refCat = refCat,
263  sourceCat = cleanSourceCat,
264  wcs = wcs,
265  refFluxField = refFluxField,
266  numCleanSources = numCleanSources,
267  minMatchedPairs = minMatchedPairs,
268  sourceInfo = sourceInfo,
269  verbose = debug.verbose,
270  )
271  if matches0 is not None:
272  matches0 = [m for m in matches0 if sourceInfo.isGoodSource(m.second)]
273  else:
274  matches0 = []
275 
276  # match good (unsaturated) sources (i.e. prefilter saturated sources)
277  if len(refCat) > len(cleanSourceCat) - len(goodSources):
278  matches1 = self._doMatch(
279  refCat = refCat,
280  sourceCat = goodSources,
281  wcs = wcs,
282  refFluxField = refFluxField,
283  numCleanSources = numCleanSources,
284  minMatchedPairs = minMatchedPairs,
285  sourceInfo = sourceInfo,
286  verbose = debug.verbose,
287  )
288  else:
289  matches1 = []
290  if matches1 == None:
291  matches1 = []
292 
293  if len(matches0) == 0 and len(matches1) == 0:
294  raise RuntimeError("Unable to match sources")
295 
296  # Adopt matches with more matches
297  if len(matches0) > len(matches1):
298  matches = matches0
299  else:
300  matches = matches1
301 
302  if self.log: self.log.info("Matched %d sources" % len(matches))
303  if len(matches) < minMatchedPairs:
304  self.log.warn("Number of matches is smaller than request")
305 
306  return pipeBase.Struct(
307  matches = matches,
308  )
309 
310  @pipeBase.timeMethod
311  def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numCleanSources, minMatchedPairs,
312  sourceInfo, verbose):
313  """!Implementation of matching sources to position reference stars
314 
315  Unlike matchObjectsToSources, this method does not check if the sources are suitable.
316 
317  @param[in] refCat catalog of position reference stars that overlap an exposure
318  @param[in] sourceCat catalog of sources found on the exposure
319  @param[in] wcs estimated WCS of exposure
320  @param[in] refFluxField field of refCat to use for flux
321  @param[in] numCleanSources number of clean sources (sources with known centroid
322  that are not near the edge, but may be saturated)
323  @param[in] minMatchedPairs minimum number of matches
324  @param[in] sourceInfo SourceInfo for the sourceCat
325  @param[in] verbose true to print diagnostic information to std::cout
326  """
327  numSources = len(sourceCat)
328  posRefBegInd = numCleanSources - numSources
329  configMatchDistPix = self.config.maxMatchDistArcSec/wcs.pixelScale().asArcseconds()
330 
331  matchControl = MatchOptimisticBControl()
332  matchControl.refFluxField = refFluxField
333  matchControl.sourceFluxField = sourceInfo.fluxField
334  matchControl.numBrightStars = self.config.numBrightStars
335  matchControl.minMatchedPairs = self.config.minMatchedPairs
336  matchControl.maxOffsetPix = self.config.maxOffsetPix
337  matchControl.numPointsForShape = self.config.numPointsForShape
338  matchControl.maxDeterminant = self.config.maxDeterminant
339 
340  for maxRotInd in range(4):
341  matchControl.maxRotationRad = self.config.rotationAllowedInRad * math.pow(2.0, 0.5*maxRotInd)
342  for matchRadInd in range(3):
343  matchControl.matchingAllowancePix = configMatchDistPix * math.pow(1.25, matchRadInd)
344 
345  for angleDiffInd in range(3):
346  matchControl.angleDiffFrom90 = self.config.angleDiffFrom90*(angleDiffInd+1)
347  matches = matchOptimisticB(
348  refCat,
349  sourceCat,
350  matchControl,
351  posRefBegInd,
352  verbose,
353  )
354  if matches is not None and len(matches) != 0:
355  return matches
def matchObjectsToSources
Match sources to position reference stars.
double min
Definition: attributes.cc:216
afwTable::ReferenceMatchVector matchOptimisticB(afwTable::SimpleCatalog const &posRefCat, afwTable::SourceCatalog const &sourceCat, MatchOptimisticBControl const &control, int posRefBegInd, bool verbose)
def _doMatch
Implementation of matching sources to position reference stars.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55