lsst.meas.algorithms  15.0-10-g113cadf7+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 
24 __all__ = ["BaseSourceSelectorConfig", "BaseSourceSelectorTask", "sourceSelectorRegistry",
25  "ColorLimit", "MagnitudeLimit", "SignalToNoiseLimit", "MagnitudeErrorLimit",
26  "RequireFlags", "RequireUnresolved",
27  "ScienceSourceSelectorConfig", "ScienceSourceSelectorTask",
28  "ReferenceSourceSelectorConfig", "ReferenceSourceSelectorTask",
29  ]
30 
31 import abc
32 import numpy as np
33 
34 import lsst.afw.table as afwTable
35 import lsst.pex.config as pexConfig
36 import lsst.pipe.base as pipeBase
37 import lsst.afw.image
38 
39 
40 class BaseSourceSelectorConfig(pexConfig.Config):
41  badFlags = pexConfig.ListField(
42  doc="List of flags which cause a source to be rejected as bad",
43  dtype=str,
44  default=[
45  "base_PixelFlags_flag_edge",
46  "base_PixelFlags_flag_interpolatedCenter",
47  "base_PixelFlags_flag_saturatedCenter",
48  "base_PixelFlags_flag_crCenter",
49  "base_PixelFlags_flag_bad",
50  ],
51  )
52 
53 
54 class BaseSourceSelectorTask(pipeBase.Task, metaclass=abc.ABCMeta):
55  """!Base class for source selectors
56 
57  Register all source selectors with the sourceSelectorRegistry using:
58  sourceSelectorRegistry.register(name, class)
59  """
60 
61  ConfigClass = BaseSourceSelectorConfig
62  _DefaultName = "sourceSelector"
63 
64  def __init__(self, **kwargs):
65  """!Initialize a source selector."""
66  pipeBase.Task.__init__(self, **kwargs)
67 
68  def run(self, sourceCat, maskedImage=None, **kwargs):
69  """!Select sources and return them.
70 
71  @param[in] sourceCat catalog of sources that may be sources (an lsst.afw.table.SourceCatalog)
72  @param[in] maskedImage the maskedImage containing the sources, for plotting.
73 
74  @return an lsst.pipe.base.Struct containing:
75  - sourceCat catalog of sources that were selected
76  """
77  return self.selectSources(maskedImage=maskedImage, sourceCat=sourceCat, **kwargs)
78 
79  @abc.abstractmethod
80  def selectSources(self, sourceCat, matches=None):
81  """!Return a catalog of sources: a subset of sourceCat.
82 
83  @param[in] sourceCat catalog of sources that may be sources (an lsst.afw.table.SourceCatalog)
84 
85  @return a pipeBase.Struct containing:
86  - sourceCat a catalog of sources
87  """
88 
89  # NOTE: example implementation, returning all sources that have no bad flags set.
90  result = afwTable.SourceCatalog(sourceCat.table)
91  for source in sourceCat:
92  if not self._isBad(source):
93  result.append(source)
94  return pipeBase.Struct(sourceCat=result)
95 
96  def _isBad(self, source):
97  """Return True if any of config.badFlags are set for this source."""
98  return any(source.get(flag) for flag in self.config.badFlags)
99 
100 
101 sourceSelectorRegistry = pexConfig.makeRegistry(
102  doc="A registry of source selectors (subclasses of BaseSourceSelectorTask)",
103 )
104 
105 
106 class BaseLimit(pexConfig.Config):
107  """Base class for selecting sources by applying a limit
108 
109  This object can be used as a `lsst.pex.config.Config` for configuring
110  the limit, and then the `apply` method can be used to identify sources
111  in the catalog that match the configured limit.
112 
113  This provides the `maximum` and `minimum` fields in the Config, and
114  a method to apply the limits to an array of values calculated by the
115  subclass.
116  """
117  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value greater than this")
118  maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value less than this")
119 
120  def apply(self, values):
121  """Apply the limits to an array of values
122 
123  Subclasses should calculate the array of values and then
124  return the result of calling this method.
125 
126  Parameters
127  ----------
128  values : `numpy.ndarray`
129  Array of values to which to apply limits.
130 
131  Returns
132  -------
133  selected : `numpy.ndarray`
134  Boolean array indicating for each source whether it is selected
135  (True means selected).
136  """
137  selected = np.ones(len(values), dtype=bool)
138  with np.errstate(invalid="ignore"): # suppress NAN warnings
139  if self.minimum is not None:
140  selected &= values > self.minimum
141  if self.maximum is not None:
142  selected &= values < self.maximum
143  return selected
144 
145 
147  """Select sources using a color limit
148 
149  This object can be used as a `lsst.pex.config.Config` for configuring
150  the limit, and then the `apply` method can be used to identify sources
151  in the catalog that match the configured limit.
152 
153  We refer to 'primary' and 'secondary' flux measurements; these are the
154  two components of the color, which is:
155 
156  instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary])
157  """
158  primary = pexConfig.Field(dtype=str, doc="Name of column with primary flux measurement")
159  secondary = pexConfig.Field(dtype=str, doc="Name of column with secondary flux measurement")
160 
161  def apply(self, catalog):
162  """Apply the color limit to a catalog
163 
164  Parameters
165  ----------
166  catalog : `lsst.afw.table.SourceCatalog`
167  Catalog of sources to which the limit will be applied.
168 
169  Returns
170  -------
171  selected : `numpy.ndarray`
172  Boolean array indicating for each source whether it is selected
173  (True means selected).
174  """
175  primary = lsst.afw.image.abMagFromFlux(catalog[self.primary])
176  secondary = lsst.afw.image.abMagFromFlux(catalog[self.secondary])
177  color = primary - secondary
178  return BaseLimit.apply(self, color)
179 
180 
182  """Select sources using a flux limit
183 
184  This object can be used as a `lsst.pex.config.Config` for configuring
185  the limit, and then the `apply` method can be used to identify sources
186  in the catalog that match the configured limit.
187  """
188  fluxField = pexConfig.Field(dtype=str, default="slot_CalibFlux_flux",
189  doc="Name of the source flux field to use.")
190 
191  def apply(self, catalog):
192  """Apply the flux limits to a catalog
193 
194  Parameters
195  ----------
196  catalog : `lsst.afw.table.SourceCatalog`
197  Catalog of sources to which the limit will be applied.
198 
199  Returns
200  -------
201  selected : `numpy.ndarray`
202  Boolean array indicating for each source whether it is selected
203  (True means selected).
204  """
205  flagField = self.fluxField + "_flag"
206  if flagField in catalog.schema:
207  selected = np.logical_not(catalog[flagField])
208  else:
209  selected = np.ones(len(catalog), dtype=bool)
210 
211  flux = catalog[self.fluxField]
212  selected &= BaseLimit.apply(self, flux)
213  return selected
214 
215 
217  """Select sources using a magnitude limit
218 
219  Note that this assumes that a zero-point has already been applied and
220  the fluxes are in AB fluxes in Jansky. It is therefore principally
221  intended for reference catalogs rather than catalogs extracted from
222  science images.
223 
224  This object can be used as a `lsst.pex.config.Config` for configuring
225  the limit, and then the `apply` method can be used to identify sources
226  in the catalog that match the configured limit.
227  """
228  fluxField = pexConfig.Field(dtype=str, default="flux",
229  doc="Name of the source flux field to use.")
230 
231  def apply(self, catalog):
232  """Apply the magnitude limits to a catalog
233 
234  Parameters
235  ----------
236  catalog : `lsst.afw.table.SourceCatalog`
237  Catalog of sources to which the limit will be applied.
238 
239  Returns
240  -------
241  selected : `numpy.ndarray`
242  Boolean array indicating for each source whether it is selected
243  (True means selected).
244  """
245  flagField = self.fluxField + "_flag"
246  if flagField in catalog.schema:
247  selected = np.logical_not(catalog[flagField])
248  else:
249  selected = np.ones(len(catalog), dtype=bool)
250 
251  magnitude = lsst.afw.image.abMagFromFlux(catalog[self.fluxField])
252  selected &= BaseLimit.apply(self, magnitude)
253  return selected
254 
255 
257  """Select sources using a flux signal-to-noise limit
258 
259  This object can be used as a `lsst.pex.config.Config` for configuring
260  the limit, and then the `apply` method can be used to identify sources
261  in the catalog that match the configured limit.
262  """
263  fluxField = pexConfig.Field(dtype=str, default="flux",
264  doc="Name of the source flux field to use.")
265  errField = pexConfig.Field(dtype=str, default="flux_err",
266  doc="Name of the source flux error field to use.")
267 
268  def apply(self, catalog):
269  """Apply the signal-to-noise limits to a catalog
270 
271  Parameters
272  ----------
273  catalog : `lsst.afw.table.SourceCatalog`
274  Catalog of sources to which the limit will be applied.
275 
276  Returns
277  -------
278  selected : `numpy.ndarray`
279  Boolean array indicating for each source whether it is selected
280  (True means selected).
281  """
282  flagField = self.fluxField + "_flag"
283  if flagField in catalog.schema:
284  selected = np.logical_not(catalog[flagField])
285  else:
286  selected = np.ones(len(catalog), dtype=bool)
287 
288  signalToNoise = catalog[self.fluxField]/catalog[self.errField]
289  selected &= BaseLimit.apply(self, signalToNoise)
290  return selected
291 
292 
294  """Select sources using a magnitude error limit
295 
296  Because the magnitude error is the inverse of the signal-to-noise
297  ratio, this also works to select sources by signal-to-noise when
298  you only have a magnitude.
299 
300  This object can be used as a `lsst.pex.config.Config` for configuring
301  the limit, and then the `apply` method can be used to identify sources
302  in the catalog that match the configured limit.
303  """
304  magErrField = pexConfig.Field(dtype=str, default="mag_err",
305  doc="Name of the source flux error field to use.")
306 
307  def apply(self, catalog):
308  """Apply the magnitude error limits to a catalog
309 
310  Parameters
311  ----------
312  catalog : `lsst.afw.table.SourceCatalog`
313  Catalog of sources to which the limit will be applied.
314 
315  Returns
316  -------
317  selected : `numpy.ndarray`
318  Boolean array indicating for each source whether it is selected
319  (True means selected).
320  """
321  return BaseLimit.apply(self, catalog[self.magErrField])
322 
323 
324 class RequireFlags(pexConfig.Config):
325  """Select sources using flags
326 
327  This object can be used as a `lsst.pex.config.Config` for configuring
328  the limit, and then the `apply` method can be used to identify sources
329  in the catalog that match the configured limit.
330  """
331  good = pexConfig.ListField(dtype=str, default=[],
332  doc="List of source flag fields that must be set for a source to be used.")
333  bad = pexConfig.ListField(dtype=str, default=[],
334  doc="List of source flag fields that must NOT be set for a source to be used.")
335 
336  def apply(self, catalog):
337  """Apply the flag requirements to a catalog
338 
339  Returns whether the source is selected.
340 
341  Parameters
342  ----------
343  catalog : `lsst.afw.table.SourceCatalog`
344  Catalog of sources to which the requirements will be applied.
345 
346  Returns
347  -------
348  selected : `numpy.ndarray`
349  Boolean array indicating for each source whether it is selected
350  (True means selected).
351  """
352  selected = np.ones(len(catalog), dtype=bool)
353  for flag in self.good:
354  selected &= catalog[flag]
355  for flag in self.bad:
356  selected &= ~catalog[flag]
357  return selected
358 
359 
361  """Select sources using star/galaxy separation
362 
363  This object can be used as a `lsst.pex.config.Config` for configuring
364  the limit, and then the `apply` method can be used to identify sources
365  in the catalog that match the configured limit.
366  """
367  name = pexConfig.Field(dtype=str, default="base_ClassificationExtendedness_value",
368  doc="Name of column for star/galaxy separation")
369 
370  def apply(self, catalog):
371  """Apply the flag requirements to a catalog
372 
373  Returns whether the source is selected.
374 
375  Parameters
376  ----------
377  catalog : `lsst.afw.table.SourceCatalog`
378  Catalog of sources to which the requirements will be applied.
379 
380  Returns
381  -------
382  selected : `numpy.ndarray`
383  Boolean array indicating for each source whether it is selected
384  (True means selected).
385  """
386  value = catalog[self.name]
387  return BaseLimit.apply(self, value)
388 
389 
390 class RequireIsolated(pexConfig.Config):
391  """Select sources based on whether they are isolated
392 
393  This object can be used as a `lsst.pex.config.Config` for configuring
394  the column names to check for "parent" and "nChild" keys.
395 
396  Note that this should only be run on a catalog that has had the
397  deblender already run (or else deblend_nChild does not exist).
398  """
399  parentName = pexConfig.Field(dtype=str, default="parent",
400  doc="Name of column for parent")
401  nChildName = pexConfig.Field(dtype=str, default="deblend_nChild",
402  doc="Name of column for nChild")
403 
404  def apply(self, catalog):
405  """Apply the isolation requirements to a catalog
406 
407  Returns whether the source is selected.
408 
409  Parameters
410  ----------
411  catalog : `lsst.afw.table.SourceCatalog`
412  Catalog of sources to which the requirements will be applied.
413 
414  Returns
415  -------
416  selected : `numpy.ndarray`
417  Boolean array indicating for each source whether it is selected
418  (True means selected).
419  """
420  selected = ((catalog[self.parentName] == 0) &
421  (catalog[self.nChildName] == 0))
422  return selected
423 
424 
425 class ScienceSourceSelectorConfig(pexConfig.Config):
426  """Configuration for selecting science sources"""
427  doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
428  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
429  doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
430  doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
431  doIsolated = pexConfig.Field(dtype=bool, default=False, doc="Apply isolated limitation?")
432  fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
433  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
434  unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
435  signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
436  isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc="Isolated criteria to apply")
437 
438  def setDefaults(self):
439  pexConfig.Config.setDefaults(self)
440  self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_saturated", "base_PsfFlux_flags"]
441  self.signalToNoise.fluxField = "base_PsfFlux_flux"
442  self.signalToNoise.errField = "base_PsfFlux_fluxSigma"
443 
444 
446  """Science source selector
447 
448  By "science" sources, we mean sources that are on images that we
449  are processing, as opposed to sources from reference catalogs.
450 
451  This selects (science) sources by (optionally) applying each of a
452  magnitude limit, flag requirements and star/galaxy separation.
453  """
454  ConfigClass = ScienceSourceSelectorConfig
455 
456  def selectSources(self, catalog, matches=None):
457  """Return a catalog of selected sources
458 
459  Parameters
460  ----------
461  catalog : `lsst.afw.table.SourceCatalog`
462  Catalog of sources to select.
463  matches : `lsst.afw.table.ReferenceMatchVector`, optional
464  List of matches; ignored.
465 
466  Return struct
467  -------------
468  sourceCat : `lsst.afw.table.SourceCatalog`
469  Catalog of selected sources, non-contiguous.
470  """
471  selected = np.ones(len(catalog), dtype=bool)
472  if self.config.doFluxLimit:
473  selected &= self.config.fluxLimit.apply(catalog)
474  if self.config.doFlags:
475  selected &= self.config.flags.apply(catalog)
476  if self.config.doUnresolved:
477  selected &= self.config.unresolved.apply(catalog)
478  if self.config.doSignalToNoise:
479  selected &= self.config.signalToNoise.apply(catalog)
480  if self.config.doIsolated:
481  selected &= self.config.isolated.apply(catalog)
482 
483  self.log.info("Selected %d/%d sources", selected.sum(), len(catalog))
484 
485  return pipeBase.Struct(sourceCat=catalog[selected],
486  selection=selected)
487 
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.