22 r"""Base classes for forced measurement plugins and the driver task for these. 
   24 In forced measurement, a reference catalog is used to define restricted 
   25 measurements (usually just fluxes) on an image.  As the reference catalog may 
   26 be deeper than the detection limit of the measurement image, we do not assume 
   27 that we can use detection and deblend information from the measurement image. 
   28 Instead, we assume this information is present in the reference catalog and 
   29 can be "transformed" in some sense to the measurement frame.  At the very 
   30 least, this means that `~lsst.afw.detection.Footprint`\ s from the reference 
   31 catalog should be transformed and installed as Footprints in the output 
   32 measurement catalog. If we have a procedure that can transform "heavy" 
   33 Footprints (ie, including pixel data), we can then proceed with measurement as 
   34 usual, but using the reference catalog's ``id`` and ``parent`` fields to 
   35 define deblend families.  If this transformation does not preserve 
   36 heavy Footprints (this is currently the case, at least for CCD forced 
   37 photometry), then we will only be able to replace objects with noise one 
   38 deblend family at a time, and hence measurements run in single-object mode may 
   39 be contaminated by neighbors when run on objects with ``parent != 0``. 
   41 Measurements are generally recorded in the coordinate system of the image 
   42 being measured (and all slot-eligible fields must be), but non-slot fields may 
   43 be recorded in other coordinate systems if necessary to avoid information loss 
   44 (this should, of course, be indicated in the field documentation).  Note that 
   45 the reference catalog may be in a different coordinate system; it is the 
   46 responsibility of plugins to transform the data they need themselves, using 
   47 the reference WCS provided.  However, for plugins that only require a position 
   48 or shape, they may simply use output `~lsst.afw.table.SourceCatalog`\'s 
   49 centroid or shape slots, which will generally be set to the transformed 
   50 position of the reference object before any other plugins are run, and hence 
   51 avoid using the reference catalog at all. 
   53 Command-line driver tasks for forced measurement include 
   54 `ForcedPhotImageTask`, `ForcedPhotCcdTask`, and `ForcedPhotCoaddTask`. 
   57 import lsst.pex.config
 
   60 from .pluginRegistry 
import PluginRegistry
 
   61 from .baseMeasurement 
import (BaseMeasurementPluginConfig, BaseMeasurementPlugin,
 
   62                               BaseMeasurementConfig, BaseMeasurementTask)
 
   63 from .noiseReplacer 
import NoiseReplacer, DummyNoiseReplacer
 
   65 __all__ = (
"ForcedPluginConfig", 
"ForcedPlugin",
 
   66            "ForcedMeasurementConfig", 
"ForcedMeasurementTask")
 
   70     """Base class for configs of forced measurement plugins.""" 
   76     """Base class for forced measurement plugins. 
   80     config : `ForcedPlugin.ConfigClass` 
   81         Configuration for this plugin. 
   83         The string with which the plugin was registered. 
   84     schemaMapper : `lsst.afw.table.SchemaMapper` 
   85         A mapping from reference catalog fields to output catalog fields. 
   86         Output fields should be added to the output schema. While most plugins 
   87         will not need to map fields from the reference schema, if they do so, 
   88         those fields will be transferred before any plugins are run. 
   89     metadata : `lsst.daf.base.PropertySet` 
   90         Plugin metadata that will be attached to the output catalog. 
   91     logName : `str`, optional 
   92         Name to use when logging errors. 
   96     """Subclasses of `ForcedPlugin` must be registered here (`PluginRegistry`). 
   99     ConfigClass = ForcedPluginConfig
 
  101     def __init__(self, config, name, schemaMapper, metadata, logName=None):
 
  102         BaseMeasurementPlugin.__init__(self, config, name, logName=logName)
 
  104     def measure(self, measRecord, exposure, refRecord, refWcs):
 
  105         """Measure the properties of a source given an image and a reference. 
  109         exposure : `lsst.afw.image.ExposureF` 
  110             The pixel data to be measured, together with the associated PSF, 
  111             WCS, etc. All other sources in the image should have been replaced 
  112             by noise according to deblender outputs. 
  113         measRecord : `lsst.afw.table.SourceRecord` 
  114             Record describing the object being measured. Previously-measured 
  115             quantities will be retrieved from here, and it will be updated 
  116             in-place with the outputs of this plugin. 
  117         refRecord : `lsst.afw.table.SimpleRecord` 
  118             Additional parameters to define the fit, as measured elsewhere. 
  119         refWcs : `lsst.afw.geom.SkyWcs` or `lsst.afw.geom.Angle` 
  120             The coordinate system for the reference catalog values. An 
  121             `~lsst.geom.Angle` may be passed, indicating that a local tangent 
  122             WCS should be created for each object using the given angle as a 
  127         In the normal mode of operation, the source centroid will be set to 
  128         the WCS-transformed position of the reference object, so plugins that 
  129         only require a reference position should not have to access the 
  130         reference object at all. 
  132         raise NotImplementedError()
 
  134     def measureN(self, measCat, exposure, refCat, refWcs):
 
  135         """Measure the properties of blended sources from image & reference. 
  137         This operates on all members of a blend family at once. 
  141         exposure : `lsst.afw.image.ExposureF` 
  142             The pixel data to be measured, together with the associated PSF, 
  143             WCS, etc. Sources not in the blended hierarchy to be measured 
  144             should have been replaced with noise using deblender outputs. 
  145         measCat : `lsst.afw.table.SourceCatalog` 
  146             Catalog describing the objects (and only those objects) being 
  147             measured. Previously-measured quantities will be retrieved from 
  148             here, and it will be updated in-place with the outputs of this 
  150         refCat : `lsst.afw.table.SimpleCatalog` 
  151             Additional parameters to define the fit, as measured elsewhere. 
  152             Ordered such that ``zip(measCat, refcat)`` may be used. 
  153         refWcs : `lsst.afw.geom.SkyWcs` or `lsst.afw.geom.Angle` 
  154             The coordinate system for the reference catalog values. An 
  155             `~lsst.geom.Angle` may be passed, indicating that a local tangent 
  156             WCS should be created for each object using the given angle as a 
  161         In the normal mode of operation, the source centroids will be set to 
  162         the WCS-transformed position of the reference object, so plugins that 
  163         only require a reference position should not have to access the 
  164         reference object at all. 
  166         raise NotImplementedError()
 
  170     """Config class for forced measurement driver task. 
  173     plugins = ForcedPlugin.registry.makeField(
 
  175         default=[
"base_PixelFlags",
 
  176                  "base_TransformedCentroid",
 
  178                  "base_TransformedShape",
 
  181                  "base_CircularApertureFlux",
 
  183                  "base_LocalBackground",
 
  185         doc=
"Plugins to be run and their configuration" 
  187     algorithms = property(
lambda self: self.
plugins, doc=
"backwards-compatibility alias for plugins")
 
  188     undeblended = ForcedPlugin.registry.makeField(
 
  191         doc=
"Plugins to run on undeblended image" 
  194     copyColumns = lsst.pex.config.DictField(
 
  195         keytype=str, itemtype=str, doc=
"Mapping of reference columns to source columns",
 
  196         default={
"id": 
"objectId", 
"parent": 
"parentObjectId", 
"deblend_nChild": 
"deblend_nChild",
 
  197                  "coord_ra": 
"coord_ra", 
"coord_dec": 
"coord_dec"}
 
  200     checkUnitsParseStrict = lsst.pex.config.Field(
 
  201         doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
 
  207         self.
slots.centroid = 
"base_TransformedCentroid" 
  208         self.
slots.shape = 
"base_TransformedShape" 
  209         self.
slots.apFlux = 
None 
  210         self.
slots.modelFlux = 
None 
  211         self.
slots.psfFlux = 
None 
  212         self.
slots.gaussianFlux = 
None 
  213         self.
slots.calibFlux = 
None 
  217     """Measure sources on an image, constrained by a reference catalog. 
  219     A subtask for measuring the properties of sources on a single image, 
  220     using an existing "reference" catalog to constrain some aspects of the 
  225     refSchema : `lsst.afw.table.Schema` 
  226         Schema of the reference catalog.  Must match the catalog later passed 
  227         to 'ForcedMeasurementTask.generateMeasCat` and/or 
  228         `ForcedMeasurementTask.run`. 
  229     algMetadata : `lsst.daf.base.PropertyList` or `None` 
  230         Will be updated in place to to record information about each 
  231         algorithm. An empty `~lsst.daf.base.PropertyList` will be created if 
  234         Keyword arguments are passed to the supertask constructor. 
  238     Note that while `SingleFrameMeasurementTask` is passed an initial 
  239     `~lsst.afw.table.Schema` that is appended to in order to create the output 
  240     `~lsst.afw.table.Schema`, `ForcedMeasurementTask` is initialized with the 
  241     `~lsst.afw.table.Schema` of the reference catalog, from which a new 
  242     `~lsst.afw.table.Schema` for the output catalog is created.  Fields to be 
  243     copied directly from the reference `~lsst.afw.table.Schema` are added 
  244     before ``Plugin`` fields are added. 
  247     ConfigClass = ForcedMeasurementConfig
 
  249     def __init__(self, refSchema, algMetadata=None, **kwds):
 
  250         super(ForcedMeasurementTask, self).
__init__(algMetadata=algMetadata, **kwds)
 
  253         self.
config.slots.setupSchema(self.
mapper.editOutputSchema())
 
  254         for refName, targetName 
in self.
config.copyColumns.items():
 
  255             refItem = refSchema.find(refName)
 
  256             self.
mapper.addMapping(refItem.key, targetName)
 
  257         self.
config.slots.setupSchema(self.
mapper.editOutputSchema())
 
  260         self.
schema.checkUnits(parse_strict=self.
config.checkUnitsParseStrict)
 
  262     def run(self, measCat, exposure, refCat, refWcs, exposureId=None, beginOrder=None, endOrder=None):
 
  263         r"""Perform forced measurement. 
  267         exposure : `lsst.afw.image.exposureF` 
  268             Image to be measured. Must have at least a `lsst.afw.geom.SkyWcs` 
  270         measCat : `lsst.afw.table.SourceCatalog` 
  271             Source catalog for measurement results; must be initialized with 
  272             empty records already corresponding to those in ``refCat`` (via 
  273             e.g. `generateMeasCat`). 
  274         refCat : `lsst.afw.table.SourceCatalog` 
  275             A sequence of `lsst.afw.table.SourceRecord` objects that provide 
  276             reference information for the measurement.  These will be passed 
  277             to each plugin in addition to the output 
  278             `~lsst.afw.table.SourceRecord`. 
  279         refWcs : `lsst.afw.geom.SkyWcs` 
  280             Defines the X,Y coordinate system of ``refCat``. 
  281         exposureId : `int`, optional 
  282             Optional unique exposureId used to calculate random number 
  283             generator seed in the NoiseReplacer. 
  284         beginOrder : `int`, optional 
  285             Beginning execution order (inclusive). Algorithms with 
  286             ``executionOrder`` < ``beginOrder`` are not executed. `None` for no limit. 
  287         endOrder : `int`, optional 
  288             Ending execution order (exclusive). Algorithms with 
  289             ``executionOrder`` >= ``endOrder`` are not executed. `None` for no limit. 
  293         Fills the initial empty `~lsst.afw.table.SourceCatalog` with forced 
  294         measurement results.  Two steps must occur before `run` can be called: 
  296         - `generateMeasCat` must be called to create the output ``measCat`` 
  298         - `~lsst.afw.detection.Footprint`\ s appropriate for the forced sources 
  299           must be attached to the ``measCat`` records. The 
  300           `attachTransformedFootprints` method can be used to do this, but 
  301           this degrades "heavy" (i.e., including pixel values) 
  302           `~lsst.afw.detection.Footprint`\s to regular 
  303           `~lsst.afw.detection.Footprint`\s, leading to non-deblended 
  304           measurement, so most callers should provide 
  305           `~lsst.afw.detection.Footprint`\s some other way. Typically, calling 
  306           code will have access to information that will allow them to provide 
  307           heavy footprints - for instance, `ForcedPhotCoaddTask` uses the 
  308           heavy footprints from deblending run in the same band just before 
  309           non-forced is run measurement in that band. 
  321         refCatIdDict = {ref.getId(): ref.getParent() 
for ref 
in refCat}
 
  326                 if topId 
not in refCatIdDict:
 
  327                     raise RuntimeError(
"Reference catalog contains a child for which at least " 
  328                                        "one parent in its parent chain is not in the catalog.")
 
  329                 topId = refCatIdDict[topId]
 
  334         footprints = {ref.getId(): (ref.getParent(), measRecord.getFootprint())
 
  335                       for (ref, measRecord) 
in zip(refCat, measCat)}
 
  337         self.
log.
info(
"Performing forced measurement on %d source%s", len(refCat),
 
  338                       "" if len(refCat) == 1 
else "s")
 
  340         if self.
config.doReplaceWithNoise:
 
  342                                           footprints, log=self.
log, exposureId=exposureId)
 
  343             algMetadata = measCat.getTable().getMetadata()
 
  344             if algMetadata 
is not None:
 
  345                 algMetadata.addInt(
"NOISE_SEED_MULTIPLIER", self.
config.noiseReplacer.noiseSeedMultiplier)
 
  346                 algMetadata.addString(
"NOISE_SOURCE", self.
config.noiseReplacer.noiseSource)
 
  347                 algMetadata.addDouble(
"NOISE_OFFSET", self.
config.noiseReplacer.noiseOffset)
 
  348                 if exposureId 
is not None:
 
  349                     algMetadata.addLong(
"NOISE_EXPOSURE_ID", exposureId)
 
  355         refParentCat, measParentCat = refCat.getChildren(0, measCat)
 
  356         for parentIdx, (refParentRecord, measParentRecord) 
in enumerate(zip(refParentCat, measParentCat)):
 
  359             refChildCat, measChildCat = refCat.getChildren(refParentRecord.getId(), measCat)
 
  361             for refChildRecord, measChildRecord 
in zip(refChildCat, measChildCat):
 
  362                 noiseReplacer.insertSource(refChildRecord.getId())
 
  363                 self.
callMeasure(measChildRecord, exposure, refChildRecord, refWcs,
 
  364                                  beginOrder=beginOrder, endOrder=endOrder)
 
  365                 noiseReplacer.removeSource(refChildRecord.getId())
 
  368             noiseReplacer.insertSource(refParentRecord.getId())
 
  369             self.
callMeasure(measParentRecord, exposure, refParentRecord, refWcs,
 
  370                              beginOrder=beginOrder, endOrder=endOrder)
 
  371             self.
callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
 
  372                               refParentCat[parentIdx:parentIdx+1],
 
  373                               beginOrder=beginOrder, endOrder=endOrder)
 
  376                               beginOrder=beginOrder, endOrder=endOrder)
 
  377             noiseReplacer.removeSource(refParentRecord.getId())
 
  382             for measRecord, refRecord 
in zip(measCat, refCat):
 
  384                     self.
doMeasurement(plugin, measRecord, exposure, refRecord, refWcs)
 
  387         r"""Initialize an output catalog from the reference catalog. 
  391         exposure : `lsst.afw.image.exposureF` 
  392             Image to be measured. 
  393         refCat : iterable of `lsst.afw.table.SourceRecord` 
  394             Catalog of reference sources. 
  395         refWcs : `lsst.afw.geom.SkyWcs` 
  396             Defines the X,Y coordinate system of ``refCat``. 
  397         idFactory : `lsst.afw.table.IdFactory`, optional 
  398             Factory for creating IDs for sources. 
  402         meascat : `lsst.afw.table.SourceCatalog` 
  403             Source catalog ready for measurement. 
  407         This generates a new blank `~lsst.afw.table.SourceRecord` for each 
  408         record in ``refCat``. Note that this method does not attach any 
  409         `~lsst.afw.detection.Footprint`\ s.  Doing so is up to the caller (who 
  410         may call `attachedTransformedFootprints` or define their own method - 
  411         see `run` for more information). 
  413         if idFactory 
is None:
 
  417         table = measCat.table
 
  419         table.preallocate(len(refCat))
 
  421             newSource = measCat.addNew()
 
  422             newSource.assign(ref, self.
mapper)
 
  426         r"""Attach Footprints to blank sources prior to measurement. 
  430         `~lsst.afw.detection.Footprint`\s for forced photometry must be in the 
  431         pixel coordinate system of the image being measured, while the actual 
  432         detections may start out in a different coordinate system. This 
  433         default implementation transforms the Footprints from the reference 
  434         catalog from the WCS to the exposure's WCS, which downgrades 
  435         ``HeavyFootprint``\s into regular `~lsst.afw.detection.Footprint`\s, 
  436         destroying deblend information. 
  438         Note that `ForcedPhotImageTask` delegates to this method in its own 
  439         `~ForcedPhotImageTask.attachFootprints` method.  This method can be 
  440         overridden by its subclasses to define how their 
  441         `~lsst.afw.detection.Footprint`\s should be generated. 
  443         See the documentation for `run` for information about the 
  444         relationships between `run`, `generateMeasCat`, and 
  445         `attachTransformedFootprints`. 
  447         exposureWcs = exposure.getWcs()
 
  448         region = exposure.getBBox(lsst.afw.image.PARENT)
 
  449         for srcRecord, refRecord 
in zip(sources, refCat):
 
  450             srcRecord.setFootprint(refRecord.getFootprint().
transform(refWcs, exposureWcs, region))