lsst.meas.algorithms  15.0-3-gd5b9ff95+1
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  ],
53  )
54 
55 
56 class BaseSourceSelectorTask(with_metaclass(abc.ABCMeta, pipeBase.Task)):
57  """!Base class for source selectors
58 
59  Register all source selectors with the sourceSelectorRegistry using:
60  sourceSelectorRegistry.register(name, class)
61  """
62 
63  ConfigClass = BaseSourceSelectorConfig
64  _DefaultName = "sourceSelector"
65 
66  def __init__(self, **kwargs):
67  """!Initialize a source selector."""
68  pipeBase.Task.__init__(self, **kwargs)
69 
70  def run(self, sourceCat, maskedImage=None, **kwargs):
71  """!Select sources and return them.
72 
73  @param[in] sourceCat catalog of sources that may be sources (an lsst.afw.table.SourceCatalog)
74  @param[in] maskedImage the maskedImage containing the sources, for plotting.
75 
76  @return an lsst.pipe.base.Struct containing:
77  - sourceCat catalog of sources that were selected
78  """
79  return self.selectSources(maskedImage=maskedImage, sourceCat=sourceCat, **kwargs)
80 
81  @abc.abstractmethod
82  def selectSources(self, sourceCat, matches=None):
83  """!Return a catalog of sources: a subset of sourceCat.
84 
85  @param[in] sourceCat catalog of sources that may be sources (an lsst.afw.table.SourceCatalog)
86 
87  @return a pipeBase.Struct containing:
88  - sourceCat a catalog of sources
89  """
90 
91  # NOTE: example implementation, returning all sources that have no bad flags set.
92  result = afwTable.SourceCatalog(sourceCat.table)
93  for source in sourceCat:
94  if not self._isBad(source):
95  result.append(source)
96  return pipeBase.Struct(sourceCat=result)
97 
98  def _isBad(self, source):
99  """Return True if any of config.badFlags are set for this source."""
100  return any(source.get(flag) for flag in self.config.badFlags)
101 
102 
103 sourceSelectorRegistry = pexConfig.makeRegistry(
104  doc="A registry of source selectors (subclasses of BaseSourceSelectorTask)",
105 )
106 
107 
108 class BaseLimit(pexConfig.Config):
109  """Base class for selecting sources by applying a limit
110 
111  This object can be used as a `lsst.pex.config.Config` for configuring
112  the limit, and then the `apply` method can be used to identify sources
113  in the catalog that match the configured limit.
114 
115  This provides the `maximum` and `minimum` fields in the Config, and
116  a method to apply the limits to an array of values calculated by the
117  subclass.
118  """
119  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value greater than this")
120  maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value less than this")
121 
122  def apply(self, values):
123  """Apply the limits to an array of values
124 
125  Subclasses should calculate the array of values and then
126  return the result of calling this method.
127 
128  Parameters
129  ----------
130  values : `numpy.ndarray`
131  Array of values to which to apply limits.
132 
133  Returns
134  -------
135  selected : `numpy.ndarray`
136  Boolean array indicating for each source whether it is selected
137  (True means selected).
138  """
139  selected = np.ones(len(values), dtype=bool)
140  with np.errstate(invalid="ignore"): # suppress NAN warnings
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 class RequireIsolated(pexConfig.Config):
393  """Select sources based on whether they are isolated
394 
395  This object can be used as a `lsst.pex.config.Config` for configuring
396  the column names to check for "parent" and "nChild" keys.
397 
398  Note that this should only be run on a catalog that has had the
399  deblender already run (or else deblend_nChild does not exist).
400  """
401  parentName = pexConfig.Field(dtype=str, default="parent",
402  doc="Name of column for parent")
403  nChildName = pexConfig.Field(dtype=str, default="deblend_nChild",
404  doc="Name of column for nChild")
405 
406  def apply(self, catalog):
407  """Apply the isolation requirements to a catalog
408 
409  Returns whether the source is selected.
410 
411  Parameters
412  ----------
413  catalog : `lsst.afw.table.SourceCatalog`
414  Catalog of sources to which the requirements will be applied.
415 
416  Returns
417  -------
418  selected : `numpy.ndarray`
419  Boolean array indicating for each source whether it is selected
420  (True means selected).
421  """
422  selected = ((catalog[self.parentName] == 0) &
423  (catalog[self.nChildName] == 0))
424  return selected
425 
426 class ScienceSourceSelectorConfig(pexConfig.Config):
427  """Configuration for selecting science sources"""
428  doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
429  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
430  doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
431  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
432  doIsolated = pexConfig.Field(dtype=bool, default=False, doc="Apply isolated limitation?")
433  fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
434  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
435  unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
436  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
437  isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc="Isolated criteria to apply")
438 
439  def setDefaults(self):
440  pexConfig.Config.setDefaults(self)
441  self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_saturated", "base_PsfFlux_flags"]
442  self.signalToNoise.fluxField = "base_PsfFlux_flux"
443  self.signalToNoise.errField = "base_PsfFlux_fluxSigma"
444 
445 
447  """Science source selector
448 
449  By "science" sources, we mean sources that are on images that we
450  are processing, as opposed to sources from reference catalogs.
451 
452  This selects (science) sources by (optionally) applying each of a
453  magnitude limit, flag requirements and star/galaxy separation.
454  """
455  ConfigClass = ScienceSourceSelectorConfig
456 
457  def selectSources(self, catalog, matches=None):
458  """Return a catalog of selected sources
459 
460  Parameters
461  ----------
462  catalog : `lsst.afw.table.SourceCatalog`
463  Catalog of sources to select.
464  matches : `lsst.afw.table.ReferenceMatchVector`, optional
465  List of matches; ignored.
466 
467  Return struct
468  -------------
469  sourceCat : `lsst.afw.table.SourceCatalog`
470  Catalog of selected sources, non-contiguous.
471  """
472  selected = np.ones(len(catalog), dtype=bool)
473  if self.config.doFluxLimit:
474  selected &= self.config.fluxLimit.apply(catalog)
475  if self.config.doFlags:
476  selected &= self.config.flags.apply(catalog)
477  if self.config.doUnresolved:
478  selected &= self.config.unresolved.apply(catalog)
479  if self.config.doSignalToNoise:
480  selected &= self.config.signalToNoise.apply(catalog)
481  if self.config.doIsolated:
482  selected &= self.config.isolated.apply(catalog)
483 
484  self.log.info("Selected %d/%d sources", selected.sum(), len(catalog))
485 
486  return pipeBase.Struct(sourceCat=catalog[selected],
487  selection=selected)
488 
489 class ReferenceSourceSelectorConfig(pexConfig.Config):
490  doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?")
491  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
492  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
493  doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?")
494  magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
495  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
496  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
497  magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply")
498  colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
499  doc="Color limits to apply; key is used as a label only")
500 
501 
503  """Reference source selector
504 
505  This selects reference sources by (optionally) applying each of a
506  magnitude limit, flag requirements and color limits.
507  """
508  ConfigClass = ReferenceSourceSelectorConfig
509 
510  def selectSources(self, catalog, matches=None):
511  """Return a catalog of selected reference sources
512 
513  Parameters
514  ----------
515  catalog : `lsst.afw.table.SourceCatalog`
516  Catalog of sources to select.
517  matches : `lsst.afw.table.ReferenceMatchVector`, optional
518  List of matches; ignored.
519 
520  Return struct
521  -------------
522  sourceCat : `lsst.afw.table.SourceCatalog`
523  Catalog of selected sources, non-contiguous.
524  """
525  selected = np.ones(len(catalog), dtype=bool)
526  if self.config.doMagLimit:
527  selected &= self.config.magLimit.apply(catalog)
528  if self.config.doFlags:
529  selected &= self.config.flags.apply(catalog)
530  if self.config.doSignalToNoise:
531  selected &= self.config.signalToNoise.apply(catalog)
532  if self.config.doMagError:
533  selected &= self.config.magError.apply(catalog)
534  for limit in self.config.colorLimits.values():
535  selected &= limit.apply(catalog)
536 
537  self.log.info("Selected %d/%d references", selected.sum(), len(catalog))
538 
539  result = type(catalog)(catalog.table) # Empty catalog based on the original
540  for source in catalog[selected]:
541  result.append(source)
542  return pipeBase.Struct(sourceCat=result, selection=selected)
543 
544 
545 sourceSelectorRegistry.register("science", ScienceSourceSelectorTask)
546 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.