lsst.meas.algorithms  14.0-19-gbbfecc2c
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  with np.errstate(invalid="ignore"): # suppress NAN warnings
142  if self.minimum is not None:
143  selected &= values > self.minimum
144  if self.maximum is not None:
145  selected &= values < self.maximum
146  return selected
147 
148 
150  """Select sources using a color limit
151 
152  This object can be used as a `lsst.pex.config.Config` for configuring
153  the limit, and then the `apply` method can be used to identify sources
154  in the catalog that match the configured limit.
155 
156  We refer to 'primary' and 'secondary' flux measurements; these are the
157  two components of the color, which is:
158 
159  instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary])
160  """
161  primary = pexConfig.Field(dtype=str, doc="Name of column with primary flux measurement")
162  secondary = pexConfig.Field(dtype=str, doc="Name of column with secondary flux measurement")
163 
164  def apply(self, catalog):
165  """Apply the color limit to a catalog
166 
167  Parameters
168  ----------
169  catalog : `lsst.afw.table.SourceCatalog`
170  Catalog of sources to which the limit will be applied.
171 
172  Returns
173  -------
174  selected : `numpy.ndarray`
175  Boolean array indicating for each source whether it is selected
176  (True means selected).
177  """
178  primary = lsst.afw.image.abMagFromFlux(catalog[self.primary])
179  secondary = lsst.afw.image.abMagFromFlux(catalog[self.secondary])
180  color = primary - secondary
181  return BaseLimit.apply(self, color)
182 
183 
185  """Select sources using a flux limit
186 
187  This object can be used as a `lsst.pex.config.Config` for configuring
188  the limit, and then the `apply` method can be used to identify sources
189  in the catalog that match the configured limit.
190  """
191  fluxField = pexConfig.Field(dtype=str, default="slot_CalibFlux_flux",
192  doc="Name of the source flux field to use.")
193 
194  def apply(self, catalog):
195  """Apply the flux limits to a catalog
196 
197  Parameters
198  ----------
199  catalog : `lsst.afw.table.SourceCatalog`
200  Catalog of sources to which the limit will be applied.
201 
202  Returns
203  -------
204  selected : `numpy.ndarray`
205  Boolean array indicating for each source whether it is selected
206  (True means selected).
207  """
208  flagField = self.fluxField + "_flag"
209  if flagField in catalog.schema:
210  selected = np.logical_not(catalog[flagField])
211  else:
212  selected = np.ones(len(catalog), dtype=bool)
213 
214  flux = catalog[self.fluxField]
215  selected &= BaseLimit.apply(self, flux)
216  return selected
217 
218 
220  """Select sources using a magnitude limit
221 
222  Note that this assumes that a zero-point has already been applied and
223  the fluxes are in AB fluxes in Jansky. It is therefore principally
224  intended for reference catalogs rather than catalogs extracted from
225  science images.
226 
227  This object can be used as a `lsst.pex.config.Config` for configuring
228  the limit, and then the `apply` method can be used to identify sources
229  in the catalog that match the configured limit.
230  """
231  fluxField = pexConfig.Field(dtype=str, default="flux",
232  doc="Name of the source flux field to use.")
233 
234  def apply(self, catalog):
235  """Apply the magnitude limits to a catalog
236 
237  Parameters
238  ----------
239  catalog : `lsst.afw.table.SourceCatalog`
240  Catalog of sources to which the limit will be applied.
241 
242  Returns
243  -------
244  selected : `numpy.ndarray`
245  Boolean array indicating for each source whether it is selected
246  (True means selected).
247  """
248  flagField = self.fluxField + "_flag"
249  if flagField in catalog.schema:
250  selected = np.logical_not(catalog[flagField])
251  else:
252  selected = np.ones(len(catalog), dtype=bool)
253 
254  magnitude = lsst.afw.image.abMagFromFlux(catalog[self.fluxField])
255  selected &= BaseLimit.apply(self, magnitude)
256  return selected
257 
258 
260  """Select sources using a flux signal-to-noise limit
261 
262  This object can be used as a `lsst.pex.config.Config` for configuring
263  the limit, and then the `apply` method can be used to identify sources
264  in the catalog that match the configured limit.
265  """
266  fluxField = pexConfig.Field(dtype=str, default="flux",
267  doc="Name of the source flux field to use.")
268  errField = pexConfig.Field(dtype=str, default="flux_err",
269  doc="Name of the source flux error field to use.")
270 
271  def apply(self, catalog):
272  """Apply the signal-to-noise limits to a catalog
273 
274  Parameters
275  ----------
276  catalog : `lsst.afw.table.SourceCatalog`
277  Catalog of sources to which the limit will be applied.
278 
279  Returns
280  -------
281  selected : `numpy.ndarray`
282  Boolean array indicating for each source whether it is selected
283  (True means selected).
284  """
285  flagField = self.fluxField + "_flag"
286  if flagField in catalog.schema:
287  selected = np.logical_not(catalog[flagField])
288  else:
289  selected = np.ones(len(catalog), dtype=bool)
290 
291  signalToNoise = catalog[self.fluxField]/catalog[self.errField]
292  selected &= BaseLimit.apply(self, signalToNoise)
293  return selected
294 
295 
297  """Select sources using a magnitude error limit
298 
299  Because the magnitude error is the inverse of the signal-to-noise
300  ratio, this also works to select sources by signal-to-noise when
301  you only have a magnitude.
302 
303  This object can be used as a `lsst.pex.config.Config` for configuring
304  the limit, and then the `apply` method can be used to identify sources
305  in the catalog that match the configured limit.
306  """
307  magErrField = pexConfig.Field(dtype=str, default="mag_err",
308  doc="Name of the source flux error field to use.")
309 
310  def apply(self, catalog):
311  """Apply the magnitude error limits to a catalog
312 
313  Parameters
314  ----------
315  catalog : `lsst.afw.table.SourceCatalog`
316  Catalog of sources to which the limit will be applied.
317 
318  Returns
319  -------
320  selected : `numpy.ndarray`
321  Boolean array indicating for each source whether it is selected
322  (True means selected).
323  """
324  return BaseLimit.apply(self, catalog[self.magErrField])
325 
326 
327 class RequireFlags(pexConfig.Config):
328  """Select sources using flags
329 
330  This object can be used as a `lsst.pex.config.Config` for configuring
331  the limit, and then the `apply` method can be used to identify sources
332  in the catalog that match the configured limit.
333  """
334  good = pexConfig.ListField(dtype=str, default=[],
335  doc="List of source flag fields that must be set for a source to be used.")
336  bad = pexConfig.ListField(dtype=str, default=[],
337  doc="List of source flag fields that must NOT be set for a source to be used.")
338 
339  def apply(self, catalog):
340  """Apply the flag requirements to a catalog
341 
342  Returns whether the source is selected.
343 
344  Parameters
345  ----------
346  catalog : `lsst.afw.table.SourceCatalog`
347  Catalog of sources to which the requirements will be applied.
348 
349  Returns
350  -------
351  selected : `numpy.ndarray`
352  Boolean array indicating for each source whether it is selected
353  (True means selected).
354  """
355  selected = np.ones(len(catalog), dtype=bool)
356  for flag in self.good:
357  selected &= catalog[flag]
358  for flag in self.bad:
359  selected &= ~catalog[flag]
360  return selected
361 
362 
364  """Select sources using star/galaxy separation
365 
366  This object can be used as a `lsst.pex.config.Config` for configuring
367  the limit, and then the `apply` method can be used to identify sources
368  in the catalog that match the configured limit.
369  """
370  name = pexConfig.Field(dtype=str, default="base_ClassificationExtendedness_value",
371  doc="Name of column for star/galaxy separation")
372 
373  def apply(self, catalog):
374  """Apply the flag requirements to a catalog
375 
376  Returns whether the source is selected.
377 
378  Parameters
379  ----------
380  catalog : `lsst.afw.table.SourceCatalog`
381  Catalog of sources to which the requirements will be applied.
382 
383  Returns
384  -------
385  selected : `numpy.ndarray`
386  Boolean array indicating for each source whether it is selected
387  (True means selected).
388  """
389  selected = np.ones(len(catalog), dtype=bool)
390  value = catalog[self.name]
391  return BaseLimit.apply(self, value)
392 
393 
394 class ScienceSourceSelectorConfig(pexConfig.Config):
395  """Configuration for selecting science sources"""
396  doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
397  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
398  doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
399  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
400  fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
401  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
402  unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
403  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
404 
405  def setDefaults(self):
406  pexConfig.Config.setDefaults(self)
407  self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_interpolated",
408  "base_PixelFlags_flag_saturated", "base_PsfFlux_flags"]
409  self.signalToNoise.fluxField = "base_PsfFlux_flux"
410  self.signalToNoise.errField = "base_PsfFlux_fluxSigma"
411 
412 
414  """Science source selector
415 
416  By "science" sources, we mean sources that are on images that we
417  are processing, as opposed to sources from reference catalogs.
418 
419  This selects (science) sources by (optionally) applying each of a
420  magnitude limit, flag requirements and star/galaxy separation.
421  """
422  ConfigClass = ScienceSourceSelectorConfig
423 
424  def selectSources(self, catalog, matches=None):
425  """Return a catalog of selected sources
426 
427  Parameters
428  ----------
429  catalog : `lsst.afw.table.SourceCatalog`
430  Catalog of sources to select.
431  matches : `lsst.afw.table.ReferenceMatchVector`, optional
432  List of matches; ignored.
433 
434  Return struct
435  -------------
436  sourceCat : `lsst.afw.table.SourceCatalog`
437  Catalog of selected sources, non-contiguous.
438  """
439  selected = np.ones(len(catalog), dtype=bool)
440  if self.config.doFluxLimit:
441  selected &= self.config.fluxLimit.apply(catalog)
442  if self.config.doFlags:
443  selected &= self.config.flags.apply(catalog)
444  if self.config.doUnresolved:
445  selected &= self.config.unresolved.apply(catalog)
446  if self.config.doSignalToNoise:
447  selected &= self.config.signalToNoise.apply(catalog)
448 
449  self.log.info("Selected %d/%d sources", selected.sum(), len(catalog))
450 
451  result = type(catalog)(catalog.table) # Empty catalog based on the original
452  for source in catalog[selected]:
453  result.append(source)
454  return pipeBase.Struct(sourceCat=result, selection=selected)
455 
456 
457 class ReferenceSourceSelectorConfig(pexConfig.Config):
458  doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?")
459  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
460  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
461  doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?")
462  magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
463  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
464  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
465  magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply")
466  colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
467  doc="Color limits to apply; key is used as a label only")
468 
469 
471  """Reference source selector
472 
473  This selects reference sources by (optionally) applying each of a
474  magnitude limit, flag requirements and color limits.
475  """
476  ConfigClass = ReferenceSourceSelectorConfig
477 
478  def selectSources(self, catalog, matches=None):
479  """Return a catalog of selected reference sources
480 
481  Parameters
482  ----------
483  catalog : `lsst.afw.table.SourceCatalog`
484  Catalog of sources to select.
485  matches : `lsst.afw.table.ReferenceMatchVector`, optional
486  List of matches; ignored.
487 
488  Return struct
489  -------------
490  sourceCat : `lsst.afw.table.SourceCatalog`
491  Catalog of selected sources, non-contiguous.
492  """
493  selected = np.ones(len(catalog), dtype=bool)
494  if self.config.doMagLimit:
495  selected &= self.config.magLimit.apply(catalog)
496  if self.config.doFlags:
497  selected &= self.config.flags.apply(catalog)
498  if self.config.doSignalToNoise:
499  selected &= self.config.signalToNoise.apply(catalog)
500  if self.config.doMagError:
501  selected &= self.config.magError.apply(catalog)
502  for limit in self.config.colorLimits.values():
503  selected &= limit.apply(catalog)
504 
505  self.log.info("Selected %d/%d references", selected.sum(), len(catalog))
506 
507  result = type(catalog)(catalog.table) # Empty catalog based on the original
508  for source in catalog[selected]:
509  result.append(source)
510  return pipeBase.Struct(sourceCat=result, selection=selected)
511 
512 
513 sourceSelectorRegistry.register("science", ScienceSourceSelectorTask)
514 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.