lsst.meas.algorithms  14.0-7-g23fdbe95+3
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", "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 from future.utils import with_metaclass
39 
40 
41 class BaseSourceSelectorConfig(pexConfig.Config):
42  badFlags = pexConfig.ListField(
43  doc="List of flags which cause a source to be rejected as bad",
44  dtype=str,
45  default=[
46  "base_PixelFlags_flag_edge",
47  "base_PixelFlags_flag_interpolatedCenter",
48  "base_PixelFlags_flag_saturatedCenter",
49  "base_PixelFlags_flag_crCenter",
50  "base_PixelFlags_flag_bad",
51  "base_PixelFlags_flag_interpolated",
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 ColorLimit(pexConfig.Config):
109  """Select sources using a color 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  We refer to 'primary' and 'secondary' flux measurements; these are the
116  two components of the color, which is:
117 
118  instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary])
119  """
120  primary = pexConfig.Field(dtype=str, doc="Name of column with primary flux measurement")
121  secondary = pexConfig.Field(dtype=str, doc="Name of column with secondary flux measurement")
122  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with color greater than this")
123  maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with color less than this")
124 
125  def apply(self, catalog):
126  """Apply the color limit to a catalog
127 
128  Parameters
129  ----------
130  catalog : `lsst.afw.table.SourceCatalog`
131  Source to which to apply limits.
132 
133  Returns
134  -------
135  selected : `numpy.ndarray`
136  Boolean array indicating whether each source is selected
137  (True means selected).
138  """
139  primary = lsst.afw.image.abMagFromFlux(catalog[self.primary])
140  secondary = lsst.afw.image.abMagFromFlux(catalog[self.secondary])
141  color = primary - secondary
142  selected = np.ones(len(catalog), dtype=bool)
143  if self.minimum is not None:
144  selected &= color > self.minimum
145  if self.maximum is not None:
146  selected &= color < self.maximum
147  return selected
148 
149 
150 class FluxLimit(pexConfig.Config):
151  """Select sources using a flux limit
152 
153  This object can be used as a `lsst.pex.config.Config` for configuring
154  the limit, and then the `apply` method can be used to identify sources
155  in the catalog that match the configured limit.
156  """
157  fluxField = pexConfig.Field(dtype=str, default="slot_CalibFlux_flux",
158  doc="Name of the source flux field to use.")
159  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects fainter than this flux")
160  maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects brighter than this flux")
161 
162  def apply(self, catalog):
163  """Apply the magnitude limit to a catalog
164 
165  Parameters
166  ----------
167  catalog : `lsst.afw.table.SourceCatalog`
168  Source to which to apply limits.
169 
170  Returns
171  -------
172  selected : `numpy.ndarray`
173  Boolean array indicating whether each source is selected
174  (True means selected).
175  """
176  flagField = self.fluxField + "_flag"
177  if flagField in catalog.schema:
178  selected = np.logical_not(catalog[flagField])
179  else:
180  selected = np.ones(len(catalog), dtype=bool)
181 
182  flux = catalog[self.fluxField]
183  if self.minimum is not None:
184  selected &= flux > self.minimum
185  if self.maximum is not None:
186  selected &= flux < self.maximum
187  return selected
188 
189 
190 class MagnitudeLimit(pexConfig.Config):
191  """Select sources using a magnitude limit
192 
193  Note that this assumes that a zero-point has already been applied and
194  the fluxes are in AB fluxes in Jansky. It is therefore principally
195  intended for reference catalogs rather than catalogs extracted from
196  science images.
197 
198  This object can be used as a `lsst.pex.config.Config` for configuring
199  the limit, and then the `apply` method can be used to identify sources
200  in the catalog that match the configured limit.
201  """
202  fluxField = pexConfig.Field(dtype=str, default="flux",
203  doc="Name of the source flux field to use.")
204  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects fainter than this magnitude")
205  maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects brighter than this magnitude")
206 
207  def apply(self, catalog):
208  """Apply the magnitude limit to a catalog
209 
210  Parameters
211  ----------
212  catalog : `lsst.afw.table.SourceCatalog`
213  Source to which to apply limits.
214 
215  Returns
216  -------
217  selected : `numpy.ndarray`
218  Boolean array indicating whether each source is selected
219  (True means selected).
220  """
221  flagField = self.fluxField + "_flag"
222  if flagField in catalog.schema:
223  selected = np.logical_not(catalog[flagField])
224  else:
225  selected = np.ones(len(catalog), dtype=bool)
226 
227  magnitude = lsst.afw.image.abMagFromFlux(catalog[self.fluxField])
228  if self.minimum is not None:
229  selected &= magnitude > self.minimum
230  if self.maximum is not None:
231  selected &= magnitude < self.maximum
232  return selected
233 
234 
235 class RequireFlags(pexConfig.Config):
236  """Select sources using flags
237 
238  This object can be used as a `lsst.pex.config.Config` for configuring
239  the limit, and then the `apply` method can be used to identify sources
240  in the catalog that match the configured limit.
241  """
242  good = pexConfig.ListField(dtype=str, default=[],
243  doc="List of source flag fields that must be set for a source to be used.")
244  bad = pexConfig.ListField(dtype=str, default=[],
245  doc="List of source flag fields that must NOT be set for a source to be used.")
246 
247  def apply(self, catalog):
248  """Apply the flag requirements to a catalog
249 
250  Returns whether the source is selected.
251 
252  Parameters
253  ----------
254  catalog : `lsst.afw.table.SourceCatalog`
255  Source to which to apply limits.
256 
257  Returns
258  -------
259  selected : `numpy.ndarray`
260  Boolean array indicating whether each source is selected
261  (True means selected).
262  """
263  selected = np.ones(len(catalog), dtype=bool)
264  for flag in self.good:
265  selected &= catalog[flag]
266  for flag in self.bad:
267  selected &= ~catalog[flag]
268  return selected
269 
270 
271 class RequireUnresolved(pexConfig.Config):
272  """Select sources using star/galaxy separation
273 
274  This object can be used as a `lsst.pex.config.Config` for configuring
275  the limit, and then the `apply` method can be used to identify sources
276  in the catalog that match the configured limit.
277  """
278  name = pexConfig.Field(dtype=str, default="base_ClassificationExtendedness_value",
279  doc="Name of column for star/galaxy separation")
280  minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value greater than this.")
281  maximum = pexConfig.Field(dtype=float, optional=True, default=0.5,
282  doc="Select objects with value less than this.")
283 
284  def apply(self, catalog):
285  """Apply the flag requirements to a catalog
286 
287  Returns whether the source is selected.
288 
289  Parameters
290  ----------
291  catalog : `lsst.afw.table.SourceCatalog`
292  Source to which to apply limits.
293 
294  Returns
295  -------
296  selected : `numpy.ndarray`
297  Boolean array indicating whether each source is selected
298  (True means selected).
299  """
300  selected = np.ones(len(catalog), dtype=bool)
301  value = catalog[self.name]
302  if self.minimum is not None:
303  selected &= value > self.minimum
304  if self.maximum is not None:
305  selected &= value < self.maximum
306  return selected
307 
308 
309 class ScienceSourceSelectorConfig(pexConfig.Config):
310  """Configuration for selecting science sources"""
311  doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
312  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
313  doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
314  fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
315  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
316  unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
317 
318  def setDefaults(self):
319  pexConfig.Config.setDefaults(self)
320  self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_interpolated",
321  "base_PixelFlags_flag_saturated"]
322 
323 
325  """Science source selector
326 
327  By "science" sources, we mean sources that are on images that we
328  are processing, as opposed to sources from reference catalogs.
329 
330  This selects (science) sources by (optionally) applying each of a
331  magnitude limit, flag requirements and star/galaxy separation.
332  """
333  ConfigClass = ScienceSourceSelectorConfig
334 
335  def selectSources(self, catalog, matches=None):
336  """Return a catalog of selected sources
337 
338  Parameters
339  ----------
340  catalog : `lsst.afw.table.SourceCatalog`
341  Catalog of sources to select.
342  matches : `lsst.afw.table.ReferenceMatchVector`, optional
343  List of matches; ignored.
344 
345  Return struct
346  -------------
347  sourceCat : `lsst.afw.table.SourceCatalog`
348  Catalog of selected sources, non-contiguous.
349  """
350  selected = np.ones(len(catalog), dtype=bool)
351  if self.config.doFluxLimit:
352  selected &= self.config.fluxLimit.apply(catalog)
353  if self.config.doFlags:
354  selected &= self.config.flags.apply(catalog)
355  if self.config.doUnresolved:
356  selected &= self.config.unresolved.apply(catalog)
357 
358  self.log.info("Selected %d/%d sources", selected.sum(), len(catalog))
359 
360  result = type(catalog)(catalog.table) # Empty catalog based on the original
361  for source in catalog[selected]:
362  result.append(source)
363  return pipeBase.Struct(sourceCat=result, selection=selected)
364 
365 
366 class ReferenceSourceSelectorConfig(pexConfig.Config):
367  doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?")
368  doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
369  magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
370  flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
371  colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
372  doc="Color limits to apply; key is used as a label only")
373 
374 
376  """Reference source selector
377 
378  This selects reference sources by (optionally) applying each of a
379  magnitude limit, flag requirements and color limits.
380  """
381  ConfigClass = ReferenceSourceSelectorConfig
382 
383  def selectSources(self, catalog, matches=None):
384  """Return a catalog of selected reference sources
385 
386  Parameters
387  ----------
388  catalog : `lsst.afw.table.SourceCatalog`
389  Catalog of sources to select.
390  matches : `lsst.afw.table.ReferenceMatchVector`, optional
391  List of matches; ignored.
392 
393  Return struct
394  -------------
395  sourceCat : `lsst.afw.table.SourceCatalog`
396  Catalog of selected sources, non-contiguous.
397  """
398  selected = np.ones(len(catalog), dtype=bool)
399  if self.config.doMagLimit:
400  selected &= self.config.magLimit.apply(catalog)
401  if self.config.doFlags:
402  selected &= self.config.flags.apply(catalog)
403  for limit in self.config.colorLimits.values():
404  selected &= limit.apply(catalog)
405 
406  self.log.info("Selected %d/%d references", selected.sum(), len(catalog))
407 
408  result = type(catalog)(catalog.table) # Empty catalog based on the original
409  for source in catalog[selected]:
410  result.append(source)
411  return pipeBase.Struct(sourceCat=result, selection=selected)
412 
413 
414 sourceSelectorRegistry.register("science", ScienceSourceSelectorTask)
415 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.