22 """Base command-line driver task for forced measurement. 
   24 Must be inherited to specialize for a specific dataset to be used (see 
   25 `ForcedPhotCcdTask`, `ForcedPhotCoaddTask`). 
   29 import lsst.pex.config
 
   32 import lsst.pex.config
 
   34 from lsst.pipe.base import PipelineTaskConnections, PipelineTaskConfig
 
   37 from .references 
import MultiBandReferencesTask
 
   38 from .forcedMeasurement 
import ForcedMeasurementTask
 
   39 from .applyApCorr 
import ApplyApCorrTask
 
   40 from .catalogCalculation 
import CatalogCalculationTask
 
   42 __all__ = (
"ForcedPhotImageConfig", 
"ForcedPhotImageTask", 
"ForcedPhotImageConnections")
 
   46                                  dimensions=(
"abstract_filter", 
"skymap", 
"tract", 
"patch"),
 
   47                                  defaultTemplates={
"inputCoaddName": 
"deep",
 
   48                                                    "outputCoaddName": 
"deep"}):
 
   49     inputSchema = cT.InitInput(
 
   50         doc=
"Schema for the input measurement catalogs.",
 
   51         name=
"{inputCoaddName}Coadd_ref_schema",
 
   52         storageClass=
"SourceCatalog",
 
   54     outputSchema = cT.InitOutput(
 
   55         doc=
"Schema for the output forced measurement catalogs.",
 
   56         name=
"{outputCoaddName}Coadd_forced_src_schema",
 
   57         storageClass=
"SourceCatalog",
 
   60         doc=
"Input exposure to perform photometry on.",
 
   61         name=
"{inputCoaddName}Coadd",
 
   62         storageClass=
"ExposureF",
 
   63         dimensions=[
"abstract_filter", 
"skymap", 
"tract", 
"patch"],
 
   66         doc=
"Catalog of shapes and positions at which to force photometry.",
 
   67         name=
"{inputCoaddName}Coadd_ref",
 
   68         storageClass=
"SourceCatalog",
 
   69         dimensions=[
"skymap", 
"tract", 
"patch"],
 
   72         doc=
"Reference world coordinate system.",
 
   73         name=
"{inputCoaddName}Coadd.wcs",
 
   75         dimensions=[
"abstract_filter", 
"skymap", 
"tract", 
"patch"],
 
   78         doc=
"Output forced photometry catalog.",
 
   79         name=
"{outputCoaddName}Coadd_forced_src",
 
   80         storageClass=
"SourceCatalog",
 
   81         dimensions=[
"abstract_filter", 
"skymap", 
"tract", 
"patch"],
 
   85 class ForcedPhotImageConfig(
PipelineTaskConfig, pipelineConnections=ForcedPhotImageConnections):
 
   86     """Config class for forced measurement driver task.""" 
   88     references = lsst.pex.config.ConfigurableField(
 
   89         target=MultiBandReferencesTask,
 
   90         doc=
"subtask to retrieve reference source catalog" 
   92     measurement = lsst.pex.config.ConfigurableField(
 
   93         target=ForcedMeasurementTask,
 
   94         doc=
"subtask to do forced measurement" 
   96     coaddName = lsst.pex.config.Field(
 
   97         doc=
"coadd name: typically one of deep or goodSeeing",
 
  101     doApCorr = lsst.pex.config.Field(
 
  104         doc=
"Run subtask to apply aperture corrections" 
  106     applyApCorr = lsst.pex.config.ConfigurableField(
 
  107         target=ApplyApCorrTask,
 
  108         doc=
"Subtask to apply aperture corrections" 
  110     catalogCalculation = lsst.pex.config.ConfigurableField(
 
  111         target=CatalogCalculationTask,
 
  112         doc=
"Subtask to run catalogCalculation plugins on catalog" 
  121         self.catalogCalculation.plugins.names = []
 
  125     """A base class for command-line forced measurement drivers. 
  129     butler : `lsst.daf.persistence.butler.Butler`, optional 
  130         A Butler which will be passed to the references subtask to allow it to 
  131         load its schema from disk. Optional, but must be specified if 
  132         ``refSchema`` is not; if both are specified, ``refSchema`` takes 
  134     refSchema : `lsst.afw.table.Schema`, optional 
  135         The schema of the reference catalog, passed to the constructor of the 
  136         references subtask. Optional, but must be specified if ``butler`` is 
  137         not; if both are specified, ``refSchema`` takes precedence. 
  139         Keyword arguments are passed to the supertask constructor. 
  143     This is a an abstract class, which is the common ancestor for 
  144     `ForcedPhotCcdTask` and `ForcedPhotCoaddTask`. It provides the 
  145     `runDataRef` method that does most of the work, while delegating a few 
  146     customization tasks to other methods that are overridden by subclasses. 
  148     This task is not directly usable as a command line task. Subclasses must: 
  150     - Set the `_DefaultName` class attribute; 
  151     - Implement `makeIdFactory`; 
  152     - Implement `fetchReferences`; 
  153     - Optionally, implement `attachFootprints`. 
  156     ConfigClass = ForcedPhotImageConfig
 
  157     _DefaultName = 
"processImageForcedTask" 
  159     def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
 
  160         super().__init__(**kwds)
 
  162         if initInputs 
is not None:
 
  163             refSchema = initInputs[
'inputSchema'].schema
 
  165         self.makeSubtask(
"references", butler=butler, schema=refSchema)
 
  166         if refSchema 
is None:
 
  167             refSchema = self.references.schema
 
  168         self.makeSubtask(
"measurement", refSchema=refSchema)
 
  171         if self.config.doApCorr:
 
  172             self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
 
  173         self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
 
  176     def runQuantum(self, butlerQC, inputRefs, outputRefs):
 
  177         inputs = butlerQC.get(inputRefs)
 
  178         inputs[
'measCat'] = self.generateMeasCat(inputRefs.exposure.dataId,
 
  180                                                  inputs[
'refCat'], inputs[
'refWcs'],
 
  182         outputs = self.run(**inputs)
 
  183         butlerQC.put(outputs, outputRefs)
 
  185     def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName):
 
  186         """Generate a measurement catalog for Gen3. 
  190         exposureDataId : `DataId` 
  191             Butler dataId for this exposure. 
  192         exposure : `lsst.afw.image.exposure.Exposure` 
  193             Exposure to generate the catalog for. 
  194         refCat : `lsst.afw.table.SourceCatalog` 
  195             Catalog of shapes and positions at which to force photometry. 
  196         refWcs : `lsst.afw.image.SkyWcs` 
  197             Reference world coordinate system. 
  199             Name of DimensionPacker to use to generate the packed version 
  200             of the data ID to mangle into source IDs. 
  204         measCat : `lsst.afw.table.SourceCatalog` 
  205             Catalog of forced sources to measure. 
  207         expId, expBits = exposureDataId.pack(idPackerName, returnMaxBits=
True)
 
  210         measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
 
  214     def runDataRef(self, dataRef, psfCache=None):
 
  215         """Perform forced measurement on a single exposure. 
  219         dataRef : `lsst.daf.persistence.ButlerDataRef` 
  220             Passed to the ``references`` subtask to obtain the reference WCS, 
  221             the ``getExposure`` method (implemented by derived classes) to 
  222             read the measurment image, and the ``fetchReferences`` method to 
  223             get the exposure and load the reference catalog (see 
  224             :lsst-task`lsst.meas.base.references.CoaddSrcReferencesTask`). 
  225             Refer to derived class documentation for details of the datasets 
  226             and data ID keys which are used. 
  227         psfCache : `int`, optional 
  228             Size of PSF cache, or `None`. The size of the PSF cache can have 
  229             a significant effect upon the runtime for complicated PSF models. 
  233         Sources are generated with ``generateMeasCat`` in the ``measurement`` 
  234         subtask. These are passed to ``measurement``'s ``run`` method, which 
  235         fills the source catalog with the forced measurement results. The 
  236         sources are then passed to the ``writeOutputs`` method (implemented by 
  237         derived classes) which writes the outputs. 
  239         refWcs = self.references.getWcs(dataRef)
 
  240         exposure = self.getExposure(dataRef)
 
  241         if psfCache 
is not None:
 
  242             exposure.getPsf().setCacheSize(psfCache)
 
  243         refCat = self.fetchReferences(dataRef, exposure)
 
  245         measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
 
  246                                                    idFactory=self.makeIdFactory(dataRef))
 
  247         self.log.
info(
"Performing forced measurement on %s" % (dataRef.dataId,))
 
  248         self.attachFootprints(measCat, refCat, exposure, refWcs, dataRef)
 
  250         exposureId = self.getExposureId(dataRef)
 
  252         forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
 
  254         self.writeOutput(dataRef, forcedPhotResult.measCat)
 
  256     def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
 
  257         """Perform forced measurement on a single exposure. 
  261         measCat : `lsst.afw.table.SourceCatalog` 
  262             The measurement catalog, based on the sources listed in the 
  264         exposure : `lsst.afw.image.Exposure` 
  265             The measurement image upon which to perform forced detection. 
  266         refCat : `lsst.afw.table.SourceCatalog` 
  267             The reference catalog of sources to measure. 
  268         refWcs : `lsst.afw.image.SkyWcs` 
  269             The WCS for the references. 
  271             Optional unique exposureId used for random seed in measurement 
  276         result : `lsst.pipe.base.Struct` 
  277             Structure with fields: 
  280                 Catalog of forced measurement results 
  281                 (`lsst.afw.table.SourceCatalog`). 
  283         self.measurement.
run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
 
  284         if self.config.doApCorr:
 
  285             self.applyApCorr.
run(
 
  287                 apCorrMap=exposure.getInfo().getApCorrMap()
 
  289         self.catalogCalculation.
run(measCat)
 
  293     def makeIdFactory(self, dataRef):
 
  294         """Hook for derived classes to make an ID factory for forced sources. 
  298         That this applies to forced *source* IDs, not object IDs, which are 
  299         usually handled by the ``measurement.copyColumns`` config option. 
  302         raise NotImplementedError()
 
  304     def getExposureId(self, dataRef):
 
  305         raise NotImplementedError()
 
  307     def fetchReferences(self, dataRef, exposure):
 
  308         """Hook for derived classes to define how to get reference objects. 
  312         Derived classes should call one of the ``fetch*`` methods on the 
  313         ``references`` subtask, but which one they call depends on whether the 
  314         region to get references for is a easy to describe in patches (as it 
  315         would be when doing forced measurements on a coadd), or is just an 
  316         arbitrary box (as it would be for CCD forced measurements). 
  318         raise NotImplementedError()
 
  320     def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef):
 
  321         r"""Attach footprints to blank sources prior to measurements. 
  325         `~lsst.afw.detection.Footprint`\ s for forced photometry must be in the 
  326         pixel coordinate system of the image being measured, while the actual 
  327         detections may start out in a different coordinate system. 
  329         Subclasses of this class must implement this method to define how 
  330         those `~lsst.afw.detection.Footprint`\ s should be generated. 
  332         This default implementation transforms the 
  333         `~lsst.afw.detection.Footprint`\ s from the reference catalog from the 
  334         reference WCS to the exposure's WcS, which downgrades 
  335         `lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s into regular 
  336         `~lsst.afw.detection.Footprint`\ s, destroying deblend information. 
  338         return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
 
  340     def getExposure(self, dataRef):
 
  341         """Read input exposure on which measurement will be performed. 
  345         dataRef : `lsst.daf.persistence.ButlerDataRef` 
  346             Butler data reference. 
  348         return dataRef.get(self.dataPrefix + 
"calexp", immediate=
True)
 
  350     def writeOutput(self, dataRef, sources):
 
  351         """Write forced source table 
  355         dataRef : `lsst.daf.persistence.ButlerDataRef` 
  356             Butler data reference. The forced_src dataset (with 
  357             self.dataPrefix prepended) is all that will be modified. 
  358         sources : `lsst.afw.table.SourceCatalog` 
  359             Catalog of sources to save. 
  361         dataRef.put(sources, self.dataPrefix + 
"forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
 
  363     def getSchemaCatalogs(self):
 
  364         """The schema catalogs that will be used by this task. 
  368         schemaCatalogs : `dict` 
  369             Dictionary mapping dataset type to schema catalog. 
  373         There is only one schema for each type of forced measurement. The 
  374         dataset type for this measurement is defined in the mapper. 
  377         catalog.getTable().setMetadata(self.measurement.algMetadata)
 
  378         datasetType = self.dataPrefix + 
"forced_src" 
  379         return {datasetType: catalog}
 
  381     def _getConfigName(self):
 
  383         return self.dataPrefix + 
"forced_config" 
  385     def _getMetadataName(self):
 
  387         return self.dataPrefix + 
"forced_metadata"