24 __all__ = [
"BaseSourceSelectorConfig", 
"BaseSourceSelectorTask", 
"sourceSelectorRegistry",
 
   25            "ColorLimit", 
"MagnitudeLimit", 
"SignalToNoiseLimit", 
"MagnitudeErrorLimit",
 
   26            "RequireFlags", 
"RequireUnresolved",
 
   27            "ScienceSourceSelectorConfig", 
"ScienceSourceSelectorTask",
 
   28            "ReferenceSourceSelectorConfig", 
"ReferenceSourceSelectorTask",
 
   33 import astropy.units 
as u
 
   37 import lsst.pex.config 
as pexConfig
 
   46     """Base class for source selectors 
   48     Source selectors are classes that perform a selection on a catalog 
   49     object given a set of criteria or cuts. They return the selected catalog 
   50     and can optionally set a specified Flag field in the input catalog to 
   51     identify if the source was selected. 
   53     Register all source selectors with the sourceSelectorRegistry using: 
   54         sourceSelectorRegistry.register(name, class) 
   59         A boolean variable specify if the inherited source selector uses 
   60         matches to an external catalog, and thus requires the ``matches`` 
   61         argument to ``run()``. 
   64     ConfigClass = BaseSourceSelectorConfig
 
   65     _DefaultName = 
"sourceSelector" 
   69         pipeBase.Task.__init__(self, **kwargs)
 
   71     def run(self, sourceCat, sourceSelectedField=None, matches=None, exposure=None):
 
   72         """Select sources and return them. 
   74         The input catalog must be contiguous in memory. 
   78         sourceCat : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 
   79                     or `astropy.table.Table` 
   80             Catalog of sources to select from. 
   81         sourceSelectedField : `str` or None 
   82             Name of flag field in sourceCat to set for selected sources. 
   83             If set, will modify sourceCat in-place. 
   84         matches : `list` of `lsst.afw.table.ReferenceMatch` or None 
   85             List of matches to use for source selection. 
   86             If usesMatches is set in source selector this field is required. 
   87             If not, it is ignored. 
   88         exposure : `lsst.afw.image.Exposure` or None 
   89             The exposure the catalog was built from; used for debug display. 
   93         struct : `lsst.pipe.base.Struct` 
   94             The struct contains the following data: 
   96             - sourceCat : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 
   97                           or `astropy.table.Table` 
   98                 The catalog of sources that were selected. 
   99                 (may not be memory-contiguous) 
  100             - selected : `numpy.ndarray` of `bool`` 
  101                 Boolean array of sources that were selected, same length as 
  107             Raised if ``sourceCat`` is not contiguous. 
  109         if hasattr(sourceCat, 
'isContiguous'):
 
  111             if not sourceCat.isContiguous():
 
  112                 raise RuntimeError(
"Input catalogs for source selection must be contiguous.")
 
  118         if sourceSelectedField 
is not None:
 
  119             if isinstance(sourceCat, (pandas.DataFrame, astropy.table.Table)):
 
  120                 sourceCat[sourceSelectedField] = result.selected
 
  122                 source_selected_key = \
 
  123                     sourceCat.getSchema()[sourceSelectedField].asKey()
 
  125                 for source, flag 
in zip(sourceCat, result.selected):
 
  126                     source.set(source_selected_key, bool(flag))
 
  127         return pipeBase.Struct(sourceCat=sourceCat[result.selected],
 
  128                                selected=result.selected)
 
  132         """Return a selection of sources selected by some criteria. 
  136         sourceCat : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 
  137                     or `astropy.table.Table` 
  138             Catalog of sources to select from. 
  139             This catalog must be contiguous in memory. 
  140         matches : `list` of `lsst.afw.table.ReferenceMatch` or None 
  141             A list of lsst.afw.table.ReferenceMatch objects 
  142         exposure : `lsst.afw.image.Exposure` or None 
  143             The exposure the catalog was built from; used for debug display. 
  147         struct : `lsst.pipe.base.Struct` 
  148             The struct contains the following data: 
  150             - selected : `numpy.ndarray` of `bool`` 
  151                 Boolean array of sources that were selected, same length as 
  154         raise NotImplementedError(
"BaseSourceSelectorTask is abstract")
 
  157 sourceSelectorRegistry = pexConfig.makeRegistry(
 
  158     doc=
"A registry of source selectors (subclasses of " 
  159         "BaseSourceSelectorTask)",
 
  164     """Base class for selecting sources by applying a limit 
  166     This object can be used as a `lsst.pex.config.Config` for configuring 
  167     the limit, and then the `apply` method can be used to identify sources 
  168     in the catalog that match the configured limit. 
  170     This provides the `maximum` and `minimum` fields in the Config, and 
  171     a method to apply the limits to an array of values calculated by the 
  174     minimum = pexConfig.Field(dtype=float, optional=
True, doc=
"Select objects with value greater than this")
 
  175     maximum = pexConfig.Field(dtype=float, optional=
True, doc=
"Select objects with value less than this")
 
  178         """Apply the limits to an array of values 
  180         Subclasses should calculate the array of values and then 
  181         return the result of calling this method. 
  185         values : `numpy.ndarray` 
  186             Array of values to which to apply limits. 
  190         selected : `numpy.ndarray` 
  191             Boolean array indicating for each source whether it is selected 
  192             (True means selected). 
  194         selected = np.ones(len(values), dtype=bool)
 
  195         with np.errstate(invalid=
"ignore"):  
 
  197                 selected &= values > self.
minimum 
  199                 selected &= values < self.
maximum 
  204     """Select sources using a color limit 
  206     This object can be used as a `lsst.pex.config.Config` for configuring 
  207     the limit, and then the `apply` method can be used to identify sources 
  208     in the catalog that match the configured limit. 
  210     We refer to 'primary' and 'secondary' flux measurements; these are the 
  211     two components of the color, which is: 
  213         instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary]) 
  215     primary = pexConfig.Field(dtype=str, doc=
"Name of column with primary flux measurement")
 
  216     secondary = pexConfig.Field(dtype=str, doc=
"Name of column with secondary flux measurement")
 
  219         """Apply the color limit to a catalog 
  223         catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 
  224                   or `astropy.table.Table` 
  225             Catalog of sources to which the limit will be applied. 
  229         selected : `numpy.ndarray` 
  230             Boolean array indicating for each source whether it is selected 
  231             (True means selected). 
  233         primary = _getFieldFromCatalog(catalog, self.
primary)
 
  234         secondary = _getFieldFromCatalog(catalog, self.
secondary)
 
  236         primary = (primary*u.nJy).to_value(u.ABmag)
 
  237         secondary = (secondary*u.nJy).to_value(u.ABmag)
 
  238         color = primary - secondary
 
  239         return BaseLimit.apply(self, color)
 
  243     """Select sources using a flux limit 
  245     This object can be used as a `lsst.pex.config.Config` for configuring 
  246     the limit, and then the `apply` method can be used to identify sources 
  247     in the catalog that match the configured limit. 
  249     fluxField = pexConfig.Field(dtype=str, default=
"slot_CalibFlux_instFlux",
 
  250                                 doc=
"Name of the source flux field to use.")
 
  253         """Apply the flux limits to a catalog 
  257         catalog : `lsst.afw.table.SourceCatalog` 
  258             Catalog of sources to which the limit will be applied. 
  262         selected : `numpy.ndarray` 
  263             Boolean array indicating for each source whether it is selected 
  264             (True means selected). 
  267         selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=
True))
 
  268         flux = _getFieldFromCatalog(catalog, self.
fluxField)
 
  270         selected &= BaseLimit.apply(self, flux)
 
  275     """Select sources using a magnitude limit 
  277     Note that this assumes that a zero-point has already been applied and 
  278     the fluxes are in AB fluxes in Jansky. It is therefore principally 
  279     intended for reference catalogs rather than catalogs extracted from 
  282     This object can be used as a `lsst.pex.config.Config` for configuring 
  283     the limit, and then the `apply` method can be used to identify sources 
  284     in the catalog that match the configured limit. 
  286     fluxField = pexConfig.Field(dtype=str, default=
"flux",
 
  287                                 doc=
"Name of the source flux field to use.")
 
  290         """Apply the magnitude limits to a catalog 
  294         catalog : `lsst.afw.table.SourceCatalog` 
  295             Catalog of sources to which the limit will be applied. 
  299         selected : `numpy.ndarray` 
  300             Boolean array indicating for each source whether it is selected 
  301             (True means selected). 
  304         selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=
True))
 
  305         flux = _getFieldFromCatalog(catalog, self.
fluxField)
 
  307         magnitude = (flux*u.nJy).to_value(u.ABmag)
 
  308         selected &= BaseLimit.apply(self, magnitude)
 
  313     """Select sources using a flux signal-to-noise limit 
  315     This object can be used as a `lsst.pex.config.Config` for configuring 
  316     the limit, and then the `apply` method can be used to identify sources 
  317     in the catalog that match the configured limit. 
  319     fluxField = pexConfig.Field(dtype=str, default=
"flux",
 
  320                                 doc=
"Name of the source flux field to use.")
 
  321     errField = pexConfig.Field(dtype=str, default=
"flux_err",
 
  322                                doc=
"Name of the source flux error field to use.")
 
  325         """Apply the signal-to-noise limits to a catalog 
  329         catalog : `lsst.afw.table.SourceCatalog` 
  330             Catalog of sources to which the limit will be applied. 
  334         selected : `numpy.ndarray` 
  335             Boolean array indicating for each source whether it is selected 
  336             (True means selected). 
  339         selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=
True))
 
  340         flux = _getFieldFromCatalog(catalog, self.
fluxField)
 
  341         err = _getFieldFromCatalog(catalog, self.
errField)
 
  343         signalToNoise = flux/err
 
  344         selected &= BaseLimit.apply(self, signalToNoise)
 
  349     """Select sources using a magnitude error limit 
  351     Because the magnitude error is the inverse of the signal-to-noise 
  352     ratio, this also works to select sources by signal-to-noise when 
  353     you only have a magnitude. 
  355     This object can be used as a `lsst.pex.config.Config` for configuring 
  356     the limit, and then the `apply` method can be used to identify sources 
  357     in the catalog that match the configured limit. 
  359     magErrField = pexConfig.Field(dtype=str, default=
"mag_err",
 
  360                                   doc=
"Name of the source flux error field to use.")
 
  363         """Apply the magnitude error limits to a catalog 
  367         catalog : `lsst.afw.table.SourceCatalog` 
  368             Catalog of sources to which the limit will be applied. 
  372         selected : `numpy.ndarray` 
  373             Boolean array indicating for each source whether it is selected 
  374             (True means selected). 
  376         return BaseLimit.apply(self, catalog[self.
magErrField])
 
  380     """Select sources using flags 
  382     This object can be used as a `lsst.pex.config.Config` for configuring 
  383     the limit, and then the `apply` method can be used to identify sources 
  384     in the catalog that match the configured limit. 
  386     good = pexConfig.ListField(dtype=str, default=[],
 
  387                                doc=
"List of source flag fields that must be set for a source to be used.")
 
  388     bad = pexConfig.ListField(dtype=str, default=[],
 
  389                               doc=
"List of source flag fields that must NOT be set for a source to be used.")
 
  392         """Apply the flag requirements to a catalog 
  394         Returns whether the source is selected. 
  398         catalog : `lsst.afw.table.SourceCatalog` 
  399             Catalog of sources to which the requirements will be applied. 
  403         selected : `numpy.ndarray` 
  404             Boolean array indicating for each source whether it is selected 
  405             (True means selected). 
  407         selected = np.ones(len(catalog), dtype=bool)
 
  408         for flag 
in self.
good:
 
  409             selected &= catalog[flag]
 
  410         for flag 
in self.
bad:
 
  411             selected &= ~catalog[flag]
 
  416     """Select sources using star/galaxy separation 
  418     This object can be used as a `lsst.pex.config.Config` for configuring 
  419     the limit, and then the `apply` method can be used to identify sources 
  420     in the catalog that match the configured limit. 
  422     name = pexConfig.Field(dtype=str, default=
"base_ClassificationExtendedness_value",
 
  423                            doc=
"Name of column for star/galaxy separation")
 
  428         ``base_ClassificationExtendedness_value < 0.5`` means unresolved. 
  433         """Apply the flag requirements to a catalog 
  435         Returns whether the source is selected. 
  439         catalog : `lsst.afw.table.SourceCatalog` 
  440             Catalog of sources to which the requirements will be applied. 
  444         selected : `numpy.ndarray` 
  445             Boolean array indicating for each source whether it is selected 
  446             (True means selected). 
  448         value = catalog[self.
name]
 
  449         return BaseLimit.apply(self, value)
 
  453     """Select sources based on whether they are isolated 
  455     This object can be used as a `lsst.pex.config.Config` for configuring 
  456     the column names to check for "parent" and "nChild" keys. 
  458     Note that this should only be run on a catalog that has had the 
  459     deblender already run (or else deblend_nChild does not exist). 
  461     parentName = pexConfig.Field(dtype=str, default=
"parent",
 
  462                                  doc=
"Name of column for parent")
 
  463     nChildName = pexConfig.Field(dtype=str, default=
"deblend_nChild",
 
  464                                  doc=
"Name of column for nChild")
 
  467         """Apply the isolation requirements to a catalog 
  469         Returns whether the source is selected. 
  473         catalog : `lsst.afw.table.SourceCatalog` 
  474             Catalog of sources to which the requirements will be applied. 
  478         selected : `numpy.ndarray` 
  479             Boolean array indicating for each source whether it is selected 
  480             (True means selected). 
  488     """Configuration for selecting science sources""" 
  489     doFluxLimit = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply flux limit?")
 
  490     doFlags = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply flag limitation?")
 
  491     doUnresolved = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply unresolved limitation?")
 
  492     doSignalToNoise = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply signal-to-noise limit?")
 
  493     doIsolated = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply isolated limitation?")
 
  494     fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc=
"Flux limit to apply")
 
  495     flags = pexConfig.ConfigField(dtype=RequireFlags, doc=
"Flags to require")
 
  496     unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc=
"Star/galaxy separation to apply")
 
  497     signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc=
"Signal-to-noise limit to apply")
 
  498     isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc=
"Isolated criteria to apply")
 
  501         pexConfig.Config.setDefaults(self)
 
  502         self.
flags.bad = [
"base_PixelFlags_flag_edge", 
"base_PixelFlags_flag_saturated", 
"base_PsfFlux_flags"]
 
  507 @pexConfig.registerConfigurable(
"science", sourceSelectorRegistry)
 
  509     """Science source selector 
  511     By "science" sources, we mean sources that are on images that we 
  512     are processing, as opposed to sources from reference catalogs. 
  514     This selects (science) sources by (optionally) applying each of a 
  515     magnitude limit, flag requirements and star/galaxy separation. 
  517     ConfigClass = ScienceSourceSelectorConfig
 
  520         """Return a selection of sources selected by specified criteria. 
  524         sourceCat : `lsst.afw.table.SourceCatalog` 
  525             Catalog of sources to select from. 
  526             This catalog must be contiguous in memory. 
  527         matches : `list` of `lsst.afw.table.ReferenceMatch` or None 
  528             Ignored in this SourceSelector. 
  529         exposure : `lsst.afw.image.Exposure` or None 
  530             The exposure the catalog was built from; used for debug display. 
  534         struct : `lsst.pipe.base.Struct` 
  535             The struct contains the following data: 
  537             - selected : `array` of `bool`` 
  538                 Boolean array of sources that were selected, same length as 
  541         selected = np.ones(len(sourceCat), dtype=bool)
 
  542         if self.config.doFluxLimit:
 
  543             selected &= self.config.fluxLimit.apply(sourceCat)
 
  544         if self.config.doFlags:
 
  545             selected &= self.config.flags.apply(sourceCat)
 
  546         if self.config.doUnresolved:
 
  547             selected &= self.config.unresolved.apply(sourceCat)
 
  548         if self.config.doSignalToNoise:
 
  549             selected &= self.config.signalToNoise.apply(sourceCat)
 
  550         if self.config.doIsolated:
 
  551             selected &= self.config.isolated.apply(sourceCat)
 
  553         self.log.
info(
"Selected %d/%d sources", selected.sum(), len(sourceCat))
 
  555         return pipeBase.Struct(selected=selected)
 
  559     doMagLimit = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply magnitude limit?")
 
  560     doFlags = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply flag limitation?")
 
  561     doUnresolved = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply unresolved limitation?")
 
  562     doSignalToNoise = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply signal-to-noise limit?")
 
  563     doMagError = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply magnitude error limit?")
 
  564     magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc=
"Magnitude limit to apply")
 
  565     flags = pexConfig.ConfigField(dtype=RequireFlags, doc=
"Flags to require")
 
  566     unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc=
"Star/galaxy separation to apply")
 
  567     signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc=
"Signal-to-noise limit to apply")
 
  568     magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc=
"Magnitude error limit to apply")
 
  569     colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
 
  570                                             doc=
"Color limits to apply; key is used as a label only")
 
  573 @pexConfig.registerConfigurable(
"references", sourceSelectorRegistry)
 
  575     """Reference source selector 
  577     This selects reference sources by (optionally) applying each of a 
  578     magnitude limit, flag requirements and color limits. 
  580     ConfigClass = ReferenceSourceSelectorConfig
 
  583         """Return a selection of reference sources selected by some criteria. 
  587         sourceCat : `lsst.afw.table.SourceCatalog` 
  588             Catalog of sources to select from. 
  589             This catalog must be contiguous in memory. 
  590         matches : `list` of `lsst.afw.table.ReferenceMatch` or None 
  591             Ignored in this SourceSelector. 
  592         exposure : `lsst.afw.image.Exposure` or None 
  593             The exposure the catalog was built from; used for debug display. 
  597         struct : `lsst.pipe.base.Struct` 
  598             The struct contains the following data: 
  600             - selected : `array` of `bool`` 
  601                 Boolean array of sources that were selected, same length as 
  604         selected = np.ones(len(sourceCat), dtype=bool)
 
  605         if self.config.doMagLimit:
 
  606             selected &= self.config.magLimit.apply(sourceCat)
 
  607         if self.config.doFlags:
 
  608             selected &= self.config.flags.apply(sourceCat)
 
  609         if self.config.doUnresolved:
 
  610             selected &= self.config.unresolved.apply(sourceCat)
 
  611         if self.config.doSignalToNoise:
 
  612             selected &= self.config.signalToNoise.apply(sourceCat)
 
  613         if self.config.doMagError:
 
  614             selected &= self.config.magError.apply(sourceCat)
 
  615         for limit 
in self.config.colorLimits.values():
 
  616             selected &= limit.apply(sourceCat)
 
  618         self.log.
info(
"Selected %d/%d references", selected.sum(), len(sourceCat))
 
  620         return pipeBase.Struct(selected=selected)
 
  623 def _getFieldFromCatalog(catalog, field, isFlag=False):
 
  625     Get a field from a catalog, for `lsst.afw.table` catalogs or 
  626     `pandas.DataFrame` or `astropy.table.Table` catalogs. 
  630     catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` 
  631               or `astropy.table.Table` 
  632         Catalog of sources to extract field array 
  635     isFlag : `bool`, optional 
  636         Is this a flag column?  If it does not exist, return array 
  642         Array of field values from the catalog. 
  645     if isinstance(catalog, (pandas.DataFrame, astropy.table.Table)):
 
  646         if field 
in catalog.columns:
 
  649             arr = np.array(catalog[field])
 
  651         if field 
in catalog.schema:
 
  655     if isFlag 
and not found:
 
  656         arr = np.zeros(len(catalog), dtype=bool)
 
  658         raise KeyError(f
"Could not find field {field} in catalog.")