lsst.meas.algorithms  14.0-8-gf6dacf9e
sourceSelector.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 from __future__ import absolute_import, division, print_function
24 
25 __all__ = ["BaseSourceSelectorConfig", "BaseSourceSelectorTask", "sourceSelectorRegistry",
26  "ColorLimit", "MagnitudeLimit", "SignalToNoiseLimit", "MagnitudeErrorLimit",
27  "RequireFlags", "RequireUnresolved",
28  "ScienceSourceSelectorConfig", "ScienceSourceSelectorTask",
29  "ReferenceSourceSelectorConfig", "ReferenceSourceSelectorTask",
30  ]
31 
32 import abc
33 import numpy as np
34 
35 import lsst.afw.table as afwTable
36 import lsst.pex.config as pexConfig
37 import lsst.pipe.base as pipeBase
38 import lsst.afw.image
39 from future.utils import with_metaclass
40 
41 
42 class BaseSourceSelectorConfig(pexConfig.Config):
43  badFlags = pexConfig.ListField(
44  doc="List of flags which cause a source to be rejected as bad",
45  dtype=str,
46  default=[
47  "base_PixelFlags_flag_edge",
48  "base_PixelFlags_flag_interpolatedCenter",
49  "base_PixelFlags_flag_saturatedCenter",
50  "base_PixelFlags_flag_crCenter",
51  "base_PixelFlags_flag_bad",
52  "base_PixelFlags_flag_interpolated",
53  ],
54  )
55 
56 
57 class BaseSourceSelectorTask(with_metaclass(abc.ABCMeta, pipeBase.Task)):
58  """!Base class for source selectors
59 
60  Register all source selectors with the sourceSelectorRegistry using:
61  sourceSelectorRegistry.register(name, class)
62  """
63 
64  ConfigClass = BaseSourceSelectorConfig
65  _DefaultName = "sourceSelector"
66 
67  def __init__(self, **kwargs):
68  """!Initialize a source selector."""
69  pipeBase.Task.__init__(self, **kwargs)
70 
71  def run(self, sourceCat, maskedImage=None, **kwargs):
72  """!Select sources and return them.
73 
74  @param[in] sourceCat catalog of sources that may be sources (an lsst.afw.table.SourceCatalog)
75  @param[in] maskedImage the maskedImage containing the sources, for plotting.
76 
77  @return an lsst.pipe.base.Struct containing:
78  - sourceCat catalog of sources that were selected
79  """
80  return self.selectSources(maskedImage=maskedImage, sourceCat=sourceCat, **kwargs)
81 
82  @abc.abstractmethod
83  def selectSources(self, sourceCat, matches=None):
84  """!Return a catalog of sources: a subset of sourceCat.
85 
86  @param[in] sourceCat catalog of sources that may be sources (an lsst.afw.table.SourceCatalog)
87 
88  @return a pipeBase.Struct containing:
89  - sourceCat a catalog of sources
90  """
91 
92  # NOTE: example implementation, returning all sources that have no bad flags set.
93  result = afwTable.SourceCatalog(sourceCat.table)
94  for source in sourceCat:
95  if not self._isBad(source):
96  result.append(source)
97  return pipeBase.Struct(sourceCat=result)
98 
99  def _isBad(self, source):
100  """Return True if any of config.badFlags are set for this source."""
101  return any(source.get(flag) for flag in self.config.badFlags)
102 
103 
104 sourceSelectorRegistry = pexConfig.makeRegistry(
105  doc="A registry of source selectors (subclasses of BaseSourceSelectorTask)",
106 )
107 
108 
109 class BaseLimit(pexConfig.Config):
110  """Base class for selecting sources by applying a limit
111 
112  This object can be used as a `lsst.pex.config.Config` for configuring
113  the limit, and then the `apply` method can be used to identify sources
114  in the catalog that match the configured limit.
115 
116  This provides the `maximum` and `minimum` fields in the Config, and
117  a method to apply the limits to an array of values calculated by the
118  subclass.
119  """
120  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value greater than this")
121  maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value less than this")
122 
123  def apply(self, values):
124  """Apply the limits to an array of values
125 
126  Subclasses should calculate the array of values and then
127  return the result of calling this method.
128 
129  Parameters
130  ----------
131  values : `numpy.ndarray`
132  Array of values to which to apply limits.
133 
134  Returns
135  -------
136  selected : `numpy.ndarray`
137  Boolean array indicating for each source whether it is selected
138  (True means selected).
139  """
140  selected = np.ones(len(values), dtype=bool)
141  if self.minimum is not None:
142  selected &= values > self.minimum
143  if self.maximum is not None:
144  selected &= values < self.maximum
145  return selected
146 
147 
149  """Select sources using a color limit
150 
151  This object can be used as a `lsst.pex.config.Config` for configuring
152  the limit, and then the `apply` method can be used to identify sources
153  in the catalog that match the configured limit.
154 
155  We refer to 'primary' and 'secondary' flux measurements; these are the
156  two components of the color, which is:
157 
158  instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary])
159  """
160  primary = pexConfig.Field(dtype=str, doc="Name of column with primary flux measurement")
161  secondary = pexConfig.Field(dtype=str, doc="Name of column with secondary flux measurement")
162 
163  def apply(self, catalog):
164  """Apply the color limit to a catalog
165 
166  Parameters
167  ----------
168  catalog : `lsst.afw.table.SourceCatalog`
169  Catalog of sources to which the limit will be applied.
170 
171  Returns
172  -------
173  selected : `numpy.ndarray`
174  Boolean array indicating for each source whether it is selected
175  (True means selected).
176  """
177  primary = lsst.afw.image.abMagFromFlux(catalog[self.primary])
178  secondary = lsst.afw.image.abMagFromFlux(catalog[self.secondary])
179  color = primary - secondary
180  return BaseLimit.apply(self, color)
181 
182 
184  """Select sources using a flux limit
185 
186  This object can be used as a `lsst.pex.config.Config` for configuring
187  the limit, and then the `apply` method can be used to identify sources
188  in the catalog that match the configured limit.
189  """
190  fluxField = pexConfig.Field(dtype=str, default="slot_CalibFlux_flux",
191  doc="Name of the source flux field to use.")
192 
193  def apply(self, catalog):
194  """Apply the flux limits to a catalog
195 
196  Parameters
197  ----------
198  catalog : `lsst.afw.table.SourceCatalog`
199  Catalog of sources to which the limit will be applied.
200 
201  Returns
202  -------
203  selected : `numpy.ndarray`
204  Boolean array indicating for each source whether it is selected
205  (True means selected).
206  """
207  flagField = self.fluxField + "_flag"
208  if flagField in catalog.schema:
209  selected = np.logical_not(catalog[flagField])
210  else:
211  selected = np.ones(len(catalog), dtype=bool)
212 
213  flux = catalog[self.fluxField]
214  selected &= BaseLimit.apply(self, flux)
215  return selected
216 
217 
219  """Select sources using a magnitude limit
220 
221  Note that this assumes that a zero-point has already been applied and
222  the fluxes are in AB fluxes in Jansky. It is therefore principally
223  intended for reference catalogs rather than catalogs extracted from
224  science images.
225 
226  This object can be used as a `lsst.pex.config.Config` for configuring
227  the limit, and then the `apply` method can be used to identify sources
228  in the catalog that match the configured limit.
229  """
230  fluxField = pexConfig.Field(dtype=str, default="flux",
231  doc="Name of the source flux field to use.")
232 
233  def apply(self, catalog):
234  """Apply the magnitude limits to a catalog
235 
236  Parameters
237  ----------
238  catalog : `lsst.afw.table.SourceCatalog`
239  Catalog of sources to which the limit will be applied.
240 
241  Returns
242  -------
243  selected : `numpy.ndarray`
244  Boolean array indicating for each source whether it is selected
245  (True means selected).
246  """
247  flagField = self.fluxField + "_flag"
248  if flagField in catalog.schema:
249  selected = np.logical_not(catalog[flagField])
250  else:
251  selected = np.ones(len(catalog), dtype=bool)
252 
253  magnitude = lsst.afw.image.abMagFromFlux(catalog[self.fluxField])
254  selected &= BaseLimit.apply(self, magnitude)
255  return selected
256 
257 
259  """Select sources using a flux signal-to-noise limit
260 
261  This object can be used as a `lsst.pex.config.Config` for configuring
262  the limit, and then the `apply` method can be used to identify sources
263  in the catalog that match the configured limit.
264  """
265  fluxField = pexConfig.Field(dtype=str, default="flux",
266  doc="Name of the source flux field to use.")
267  errField = pexConfig.Field(dtype=str, default="flux_err",
268  doc="Name of the source flux error field to use.")
269 
270  def apply(self, catalog):
271  """Apply the signal-to-noise limits to a catalog
272 
273  Parameters
274  ----------
275  catalog : `lsst.afw.table.SourceCatalog`
276  Catalog of sources to which the limit will be applied.
277 
278  Returns
279  -------
280  selected : `numpy.ndarray`
281  Boolean array indicating for each source whether it is selected
282  (True means selected).
283  """
284  flagField = self.fluxField + "_flag"
285  if flagField in catalog.schema:
286  selected = np.logical_not(catalog[flagField])
287  else:
288  selected = np.ones(len(catalog), dtype=bool)
289 
290  signalToNoise = catalog[self.fluxField]/catalog[self.errField]
291  selected &= BaseLimit.apply(self, signalToNoise)
292  return selected
293 
294 
296  """Select sources using a magnitude error limit
297 
298  Because the magnitude error is the inverse of the signal-to-noise
299  ratio, this also works to select sources by signal-to-noise when
300  you only have a magnitude.
301 
302  This object can be used as a `lsst.pex.config.Config` for configuring
303  the limit, and then the `apply` method can be used to identify sources
304  in the catalog that match the configured limit.
305  """
306  magErrField = pexConfig.Field(dtype=str, default="mag_err",
307  doc="Name of the source flux error field to use.")
308 
309  def apply(self, catalog):
310  """Apply the magnitude error limits to a catalog
311 
312  Parameters
313  ----------
314  catalog : `lsst.afw.table.SourceCatalog`
315  Catalog of sources to which the limit will be applied.
316 
317  Returns
318  -------
319  selected : `numpy.ndarray`
320  Boolean array indicating for each source whether it is selected
321  (True means selected).
322  """
323  return BaseLimit.apply(self, catalog[self.magErrField])
324 
325 
326 class RequireFlags(pexConfig.Config):
327  """Select sources using flags
328 
329  This object can be used as a `lsst.pex.config.Config` for configuring
330  the limit, and then the `apply` method can be used to identify sources
331  in the catalog that match the configured limit.
332  """
333  good = pexConfig.ListField(dtype=str, default=[],
334  doc="List of source flag fields that must be set for a source to be used.")
335  bad = pexConfig.ListField(dtype=str, default=[],
336  doc="List of source flag fields that must NOT be set for a source to be used.")
337 
338  def apply(self, catalog):
339  """Apply the flag requirements to a catalog
340 
341  Returns whether the source is selected.
342 
343  Parameters
344  ----------
345  catalog : `lsst.afw.table.SourceCatalog`
346  Catalog of sources to which the requirements will be applied.
347 
348  Returns
349  -------
350  selected : `numpy.ndarray`
351  Boolean array indicating for each source whether it is selected
352  (True means selected).
353  """
354  selected = np.ones(len(catalog), dtype=bool)
355  for flag in self.good:
356  selected &= catalog[flag]
357  for flag in self.bad:
358  selected &= ~catalog[flag]
359  return selected
360 
361 
363  """Select sources using star/galaxy separation
364 
365  This object can be used as a `lsst.pex.config.Config` for configuring
366  the limit, and then the `apply` method can be used to identify sources
367  in the catalog that match the configured limit.
368  """
369  name = pexConfig.Field(dtype=str, default="base_ClassificationExtendedness_value",
370  doc="Name of column for star/galaxy separation")
371 
372  def apply(self, catalog):
373  """Apply the flag requirements to a catalog
374 
375  Returns whether the source is selected.
376 
377  Parameters
378  ----------
379  catalog : `lsst.afw.table.SourceCatalog`
380  Catalog of sources to which the requirements will be applied.
381 
382  Returns
383  -------
384  selected : `numpy.ndarray`
385  Boolean array indicating for each source whether it is selected
386  (True means selected).
387  """
388  selected = np.ones(len(catalog), dtype=bool)
389  value = catalog[self.name]
390  return BaseLimit.apply(self, value)
391 
392 
393 class ScienceSourceSelectorConfig(pexConfig.Config):
394  """Configuration for selecting science sources"""
395  doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
396  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
397  doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
398  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
399  fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
400  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
401  unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
402  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
403 
404  def setDefaults(self):
405  pexConfig.Config.setDefaults(self)
406  self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_interpolated",
407  "base_PixelFlags_flag_saturated", "base_PsfFlux_flags"]
408  self.signalToNoise.fluxField = "base_PsfFlux_flux"
409  self.signalToNoise.errField = "base_PsfFlux_fluxSigma"
410 
411 
413  """Science source selector
414 
415  By "science" sources, we mean sources that are on images that we
416  are processing, as opposed to sources from reference catalogs.
417 
418  This selects (science) sources by (optionally) applying each of a
419  magnitude limit, flag requirements and star/galaxy separation.
420  """
421  ConfigClass = ScienceSourceSelectorConfig
422 
423  def selectSources(self, catalog, matches=None):
424  """Return a catalog of selected sources
425 
426  Parameters
427  ----------
428  catalog : `lsst.afw.table.SourceCatalog`
429  Catalog of sources to select.
430  matches : `lsst.afw.table.ReferenceMatchVector`, optional
431  List of matches; ignored.
432 
433  Return struct
434  -------------
435  sourceCat : `lsst.afw.table.SourceCatalog`
436  Catalog of selected sources, non-contiguous.
437  """
438  selected = np.ones(len(catalog), dtype=bool)
439  if self.config.doFluxLimit:
440  selected &= self.config.fluxLimit.apply(catalog)
441  if self.config.doFlags:
442  selected &= self.config.flags.apply(catalog)
443  if self.config.doUnresolved:
444  selected &= self.config.unresolved.apply(catalog)
445  if self.config.doSignalToNoise:
446  selected &= self.config.signalToNoise.apply(catalog)
447 
448  self.log.info("Selected %d/%d sources", selected.sum(), len(catalog))
449 
450  result = type(catalog)(catalog.table) # Empty catalog based on the original
451  for source in catalog[selected]:
452  result.append(source)
453  return pipeBase.Struct(sourceCat=result, selection=selected)
454 
455 
456 class ReferenceSourceSelectorConfig(pexConfig.Config):
457  doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?")
458  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
459  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
460  doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?")
461  magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
462  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
463  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
464  magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply")
465  colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
466  doc="Color limits to apply; key is used as a label only")
467 
468 
470  """Reference source selector
471 
472  This selects reference sources by (optionally) applying each of a
473  magnitude limit, flag requirements and color limits.
474  """
475  ConfigClass = ReferenceSourceSelectorConfig
476 
477  def selectSources(self, catalog, matches=None):
478  """Return a catalog of selected reference sources
479 
480  Parameters
481  ----------
482  catalog : `lsst.afw.table.SourceCatalog`
483  Catalog of sources to select.
484  matches : `lsst.afw.table.ReferenceMatchVector`, optional
485  List of matches; ignored.
486 
487  Return struct
488  -------------
489  sourceCat : `lsst.afw.table.SourceCatalog`
490  Catalog of selected sources, non-contiguous.
491  """
492  selected = np.ones(len(catalog), dtype=bool)
493  if self.config.doMagLimit:
494  selected &= self.config.magLimit.apply(catalog)
495  if self.config.doFlags:
496  selected &= self.config.flags.apply(catalog)
497  if self.config.doSignalToNoise:
498  selected &= self.config.signalToNoise.apply(catalog)
499  if self.config.doMagError:
500  selected &= self.config.magError.apply(catalog)
501  for limit in self.config.colorLimits.values():
502  selected &= limit.apply(catalog)
503 
504  self.log.info("Selected %d/%d references", selected.sum(), len(catalog))
505 
506  result = type(catalog)(catalog.table) # Empty catalog based on the original
507  for source in catalog[selected]:
508  result.append(source)
509  return pipeBase.Struct(sourceCat=result, selection=selected)
510 
511 
512 sourceSelectorRegistry.register("science", ScienceSourceSelectorTask)
513 sourceSelectorRegistry.register("references", ReferenceSourceSelectorTask)
def selectSources(self, sourceCat, matches=None)
Return a catalog of sources: a subset of sourceCat.
template ndarray::Array< double, 1 > abMagFromFlux(ndarray::Array< double const, 1 > const &flux)
def __init__(self, kwargs)
Initialize a source selector.
def run(self, sourceCat, maskedImage=None, kwargs)
Select sources and return them.