24 __all__ = [
"ReserveSourcesConfig", 
"ReserveSourcesTask"]
 
   28 from lsst.pex.config 
import Config, Field
 
   33     """Configuration for reserving sources""" 
   34     fraction = Field(dtype=float, default=0.0,
 
   35                      doc=
"Fraction of candidates to reserve from fitting; none if <= 0")
 
   36     seed = Field(dtype=int, default=1,
 
   37                  doc=(
"This number will be added to the exposure ID to set the random seed for " 
   38                       "reserving candidates"))
 
   42     """Reserve sources from analysis 
   44     We randomly select a fraction of sources that will be reserved 
   45     from analysis. This allows evaluation of the quality of model fits 
   46     using sources that were not involved in the fitting process. 
   48     Constructor parameters 
   49     ---------------------- 
   50     columnName : `str`, required 
   51         Name of flag column to add; we will suffix this with "_reserved". 
   52     schema : `lsst.afw.table.Schema`, required 
   55         Documentation for column to add. 
   56     config : `ReserveSourcesConfig` 
   59     ConfigClass = ReserveSourcesConfig
 
   60     _DefaultName = 
"reserveSources" 
   62     def __init__(self, columnName=None, schema=None, doc=None, **kwargs):
 
   63         Task.__init__(self, **kwargs)
 
   64         assert columnName 
is not None, 
"columnName not provided" 
   65         assert schema 
is not None, 
"schema not provided" 
   67         self.
key = schema.addField(self.
columnName + 
"_reserved", type=
"Flag", doc=doc)
 
   69     def run(self, sources, prior=None, expId=0):
 
   70         """Select sources to be reserved 
   72         Reserved sources will be flagged in the catalog, and we will return 
   73         boolean arrays that identify the sources to be reserved from and 
   74         used in the analysis. Typically you'll want to use the sources 
   75         from the `use` array in your fitting, and use the sources from the 
   76         `reserved` array as an independent test of your fitting. 
   80         sources : `lsst.afw.table.Catalog` or `list` of `lsst.afw.table.Record` 
   81             Sources from which to select some to be reserved. 
   82         prior : `numpy.ndarray` of type `bool`, optional 
   83             Prior selection of sources. Should have the same length as 
   84             `sources`. If set, we will only consider for reservation sources 
   85             that are flagged `True` in this array. 
   87             Exposure identifier; used for seeding the random number generator. 
   89         Return struct contents 
   90         ---------------------- 
   91         reserved : `numpy.ndarray` of type `bool` 
   92             Sources to be reserved are flagged `True` in this array. 
   93         use : `numpy.ndarray` of type `bool` 
   94             Sources the user should use in analysis are flagged `True`. 
   97             assert len(prior) == len(sources), 
"Length mismatch: %s vs %s" % (len(prior), len(sources))
 
   98             numSources = prior.sum()
 
  100             numSources = len(sources)
 
  101         selection = self.
select(numSources, expId)
 
  102         if prior 
is not None:
 
  105         self.
log.
info(
"Reserved %d/%d sources", selection.sum(), len(selection))
 
  106         return Struct(reserved=selection,
 
  107                       use=prior & ~selection 
if prior 
is not None else np.logical_not(selection))
 
  110         """Randomly select some sources 
  112         We return a boolean array with a random selection. The fraction 
  113         of sources selected is specified by the config parameter `fraction`. 
  118             Number of sources in catalog from which to select. 
  120             Exposure identifier; used for seeding the random number generator. 
  124         selection : `numpy.ndarray` of type `bool` 
  125             Selected sources are flagged `True` in this array. 
  127         selection = np.zeros(numSources, dtype=bool)
 
  128         if self.
config.fraction <= 0:
 
  130         reserve = int(np.round(numSources*self.
config.fraction))
 
  131         selection[:reserve] = 
True 
  132         rng = np.random.RandomState((self.
config.seed + expId) & 0xFFFFFFFF)
 
  133         rng.shuffle(selection)
 
  137         """Apply selection to full catalog 
  139         The `select` method makes a random selection of sources. If those 
  140         sources don't represent the full population (because a sub-selection 
  141         has already been made), then we need to generate a selection covering 
  142         the entire population. 
  146         prior : `numpy.ndarray` of type `bool` 
  147             Prior selection of sources, identifying the subset from which the 
  148             random selection has been made. 
  149         selection : `numpy.ndarray` of type `bool` 
  150             Selection of sources in subset identified by `prior`. 
  154         full : `numpy.ndarray` of type `bool` 
  155             Selection applied to full population. 
  157         full = np.zeros(len(prior), dtype=bool)
 
  158         full[prior] = selection
 
  162         """Mark sources in a list or catalog 
  164         This requires iterating through the list and setting the flag in 
  165         each source individually. Even if the `sources` is a `Catalog` 
  166         with contiguous records, it's not currently possible to set a boolean 
  167         column (DM-6981) so we need to iterate. 
  171         catalog : `lsst.afw.table.Catalog` or `list` of `lsst.afw.table.Record` 
  172             Catalog in which to flag selected sources. 
  173         selection : `numpy.ndarray` of type `bool` 
  174             Selection of sources to mark. 
  176         for src, select 
in zip(sources, selection):
 
  178                 src.set(self.
key, 
True)