lsst.meas.algorithms  14.0-21-ge7d40960+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  "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 class RequireIsolated(pexConfig.Config):
394  """Select sources based on whether they are isolated
395 
396  This object can be used as a `lsst.pex.config.Config` for configuring
397  the column names to check for "parent" and "nChild" keys.
398 
399  Note that this should only be run on a catalog that has had the
400  deblender already run (or else deblend_nChild does not exist).
401  """
402  parentName = pexConfig.Field(dtype=str, default="parent",
403  doc="Name of column for parent")
404  nChildName = pexConfig.Field(dtype=str, default="deblend_nChild",
405  doc="Name of column for nChild")
406 
407  def apply(self, catalog):
408  """Apply the isolation requirements to a catalog
409 
410  Returns whether the source is selected.
411 
412  Parameters
413  ----------
414  catalog : `lsst.afw.table.SourceCatalog`
415  Catalog of sources to which the requirements will be applied.
416 
417  Returns
418  -------
419  selected : `numpy.ndarray`
420  Boolean array indicating for each source whether it is selected
421  (True means selected).
422  """
423  selected = ((catalog[self.parentName] == 0) &
424  (catalog[self.nChildName] == 0))
425  return selected
426 
427 class ScienceSourceSelectorConfig(pexConfig.Config):
428  """Configuration for selecting science sources"""
429  doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
430  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
431  doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
432  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
433  doIsolated = pexConfig.Field(dtype=bool, default=False, doc="Apply isolated limitation?")
434  fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
435  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
436  unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
437  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
438  isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc="Isolated criteria to apply")
439 
440  def setDefaults(self):
441  pexConfig.Config.setDefaults(self)
442  self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_interpolated",
443  "base_PixelFlags_flag_saturated", "base_PsfFlux_flags"]
444  self.signalToNoise.fluxField = "base_PsfFlux_flux"
445  self.signalToNoise.errField = "base_PsfFlux_fluxSigma"
446 
447 
449  """Science source selector
450 
451  By "science" sources, we mean sources that are on images that we
452  are processing, as opposed to sources from reference catalogs.
453 
454  This selects (science) sources by (optionally) applying each of a
455  magnitude limit, flag requirements and star/galaxy separation.
456  """
457  ConfigClass = ScienceSourceSelectorConfig
458 
459  def selectSources(self, catalog, matches=None):
460  """Return a catalog of selected sources
461 
462  Parameters
463  ----------
464  catalog : `lsst.afw.table.SourceCatalog`
465  Catalog of sources to select.
466  matches : `lsst.afw.table.ReferenceMatchVector`, optional
467  List of matches; ignored.
468 
469  Return struct
470  -------------
471  sourceCat : `lsst.afw.table.SourceCatalog`
472  Catalog of selected sources, non-contiguous.
473  """
474  selected = np.ones(len(catalog), dtype=bool)
475  if self.config.doFluxLimit:
476  selected &= self.config.fluxLimit.apply(catalog)
477  if self.config.doFlags:
478  selected &= self.config.flags.apply(catalog)
479  if self.config.doUnresolved:
480  selected &= self.config.unresolved.apply(catalog)
481  if self.config.doSignalToNoise:
482  selected &= self.config.signalToNoise.apply(catalog)
483  if self.config.doIsolated:
484  selected &= self.config.isolated.apply(catalog)
485 
486  self.log.info("Selected %d/%d sources", selected.sum(), len(catalog))
487 
488  return pipeBase.Struct(sourceCat=catalog[selected],
489  selection=selected)
490 
491 class ReferenceSourceSelectorConfig(pexConfig.Config):
492  doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?")
493  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
494  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
495  doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?")
496  magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
497  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
498  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
499  magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply")
500  colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
501  doc="Color limits to apply; key is used as a label only")
502 
503 
505  """Reference source selector
506 
507  This selects reference sources by (optionally) applying each of a
508  magnitude limit, flag requirements and color limits.
509  """
510  ConfigClass = ReferenceSourceSelectorConfig
511 
512  def selectSources(self, catalog, matches=None):
513  """Return a catalog of selected reference sources
514 
515  Parameters
516  ----------
517  catalog : `lsst.afw.table.SourceCatalog`
518  Catalog of sources to select.
519  matches : `lsst.afw.table.ReferenceMatchVector`, optional
520  List of matches; ignored.
521 
522  Return struct
523  -------------
524  sourceCat : `lsst.afw.table.SourceCatalog`
525  Catalog of selected sources, non-contiguous.
526  """
527  selected = np.ones(len(catalog), dtype=bool)
528  if self.config.doMagLimit:
529  selected &= self.config.magLimit.apply(catalog)
530  if self.config.doFlags:
531  selected &= self.config.flags.apply(catalog)
532  if self.config.doSignalToNoise:
533  selected &= self.config.signalToNoise.apply(catalog)
534  if self.config.doMagError:
535  selected &= self.config.magError.apply(catalog)
536  for limit in self.config.colorLimits.values():
537  selected &= limit.apply(catalog)
538 
539  self.log.info("Selected %d/%d references", selected.sum(), len(catalog))
540 
541  result = type(catalog)(catalog.table) # Empty catalog based on the original
542  for source in catalog[selected]:
543  result.append(source)
544  return pipeBase.Struct(sourceCat=result, selection=selected)
545 
546 
547 sourceSelectorRegistry.register("science", ScienceSourceSelectorTask)
548 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.