Skip to content

Commit

Permalink
Convert ForcedPhotCcd and ForcedPhotCoadd into pipelineTasks.
Browse files Browse the repository at this point in the history
The conversion of ForcedPhotCoadd was straightforward.  ForcedPhotCcd
required more handling, with duplicate operations in adjustArgsAndRun
to work around Gen-2 butler methods in the source filtering and
transformation code.
  • Loading branch information
czwa committed Jan 29, 2019
1 parent a4d4bcb commit a361322
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 13 deletions.
122 changes: 118 additions & 4 deletions python/lsst/meas/base/forcedPhotCcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import lsst.pex.config
import lsst.pex.exceptions
from lsst.log import Log
import lsst.pipe.base
import lsst.pipe.base as pipeBase
import lsst.geom
import lsst.afw.geom
import lsst.afw.image
Expand All @@ -41,7 +41,7 @@
__all__ = ("PerTractCcdDataIdContainer", "ForcedPhotCcdConfig", "ForcedPhotCcdTask", "imageOverlapsTract")


class PerTractCcdDataIdContainer(lsst.pipe.base.DataIdContainer):
class PerTractCcdDataIdContainer(pipeBase.DataIdContainer):
"""A data ID container which combines raw data IDs with a tract.
Notes
Expand Down Expand Up @@ -139,12 +139,64 @@ def imageOverlapsTract(tract, imageWcs, imageBox):


class ForcedPhotCcdConfig(ForcedPhotImageConfig):
# Gen 3 options
inputSchema = pipeBase.InitInputDatasetField(
doc="Schema for the input measurement catalogs.",
name="",
nameTemplate="{inputCoaddName}Coadd_ref_schema",
storageClass="SourceCatalog",
)
outputSchema = pipeBase.InitOutputDatasetField(
doc="Schema for the output forced measurement catalogs.",
name="",
nameTemplate="forced_src_schema",
storageClass="SourceCatalog",
)
exposure = pipeBase.InputDatasetField(
doc="Input exposure to perform photometry on.",
name="",
nameTemplate="{inputName}",
scalar=True,
storageClass="ExposureF",
dimensions=["Instrument", "Visit", "Detector"]
)
refCat = pipeBase.InputDatasetField(
doc="Reference catalog of sources.",
name="",
nameTemplate="{inputCoaddName}Coadd_ref",
scalar=True,
storageClass="SourceCatalog",
dimensions=["SkyMap", "Tract", "Patch"],
)
refWcs = pipeBase.InputDatasetField(
doc="Input coadd exposure that is the source of the reference catalog.",
name="",
nameTemplate="{inputCoaddName}Coadd.wcs",
scalar=True,
storageClass="TablePersistableWcs",
dimensions=["AbstractFilter", "SkyMap", "Tract", "Patch"],
)
measCat = pipeBase.OutputDatasetField(
doc="Output forced photometry catalog.",
name="forced_src",
scalar=True,
storageClass="SourceCatalog",
dimensions=["Instrument", "Visit", "Detector", "SkyMap", "Tract"],
)

doApplyUberCal = lsst.pex.config.Field(
dtype=bool,
doc="Apply meas_mosaic ubercal results to input calexps?",
default=False
)

def setDefaults(self):
super().setDefaults()

self.formatTemplateNames({"inputName": "calexp",
"inputCoaddName": "deep"})
self.quantum.dimensions = ("Instrument", "Visit", "Detector", "SkyMap", "Tract")


class ForcedPhotCcdTask(ForcedPhotImageTask):
"""A command-line driver for performing forced measurement on CCD images.
Expand Down Expand Up @@ -182,10 +234,72 @@ class ForcedPhotCcdTask(ForcedPhotImageTask):
"""

ConfigClass = ForcedPhotCcdConfig
RunnerClass = lsst.pipe.base.ButlerInitializedTaskRunner
RunnerClass = pipeBase.ButlerInitializedTaskRunner
_DefaultName = "forcedPhotCcd"
dataPrefix = ""

def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
# Filtering the reference catalog is currently handled by
# Gen2 specific methods. To get around this, port in code
# segments to do the filtering and transformation.

# Step 1: Determine bounds of the exposure photometry will
# be performed on.
expWcs = inputData['exposure'].getWcs()
expRegion = inputData['exposure'].getBBox(lsst.afw.image.PARENT)
expBBox = lsst.geom.Box2D(expRegion)
expBoxCorners = expBBox.getCorners()
expSkyCorners = [expWcs.pixelToSky(corner).getVector() for
corner in expBoxCorners]
expPolygon = lsst.sphgeom.ConvexPolygon(expSkyCorners)

# Step 2: Filter out reference catalog sources that are
# not contained within the exposure boundaries.
sources = type(inputData['refCat'])(inputData['refCat'].table)
for record in inputData['refCat']:
if expPolygon.contains(record.getCoord().getVector()):
sources.append(record)
refCatIdDict = {ref.getId(): ref.getParent() for ref in sources}

# Step 3: Cull sources that do not have their parent
# source in the filtered catalog. Save two copies of each
# source.
sources = type(inputData['refCat'])(inputData['refCat'].table)
refSources = type(inputData['refCat'])(inputData['refCat'].table)
for record in inputData['refCat']:
if expPolygon.contains(record.getCoord().getVector()):
recordId = record.getId()
topId = recordId
while (topId > 0):
if topId in refCatIdDict:
topId = refCatIdDict[topId]
else:
break
if topId == 0:
sources.append(record)
refSources.append(record)

# Step 4: Transform source footprints from the reference
# coordinates to the exposure coordinates.
for srcRecord, refRecord in zip(sources, refSources):
srcRecord.setFootprint(refRecord.getFootprint().transform(inputData['refWcs'],
expWcs, expRegion))
# Step 5: Replace reference catalog with filtered source list.
inputData['refCat'] = sources

if 'measCat' not in inputData.keys():
packer = butler.registry.makeDataIdPacker("VisitDetector", inputDataIds['exposure'])
expId = packer.pack(inputDataIds['exposure'])
expBits = packer.maxBits
idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)

inputData['measCat'] = self.measurement.generateMeasCat(inputData['exposure'],
inputData['refCat'],
inputData['refWcs'],
idFactory=idFactory)

return super().adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)

def makeIdFactory(self, dataRef):
"""Create an object that generates globally unique source IDs.
Expand Down Expand Up @@ -282,7 +396,7 @@ def _getMetadataName(self):

@classmethod
def _makeArgumentParser(cls):
parser = lsst.pipe.base.ArgumentParser(name=cls._DefaultName)
parser = pipeBase.ArgumentParser(name=cls._DefaultName)
parser.add_id_argument("--id", "forced_src", help="data ID with raw CCD keys [+ tract optionally], "
"e.g. --id visit=12345 ccd=1,2 [tract=0]",
ContainerClass=PerTractCcdDataIdContainer)
Expand Down
92 changes: 83 additions & 9 deletions python/lsst/meas/base/forcedPhotImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
`ForcedPhotCcdTask`, `ForcedPhotCoaddTask`).
"""

import lsst.afw.table
import lsst.afw.table as afwTable
import lsst.pex.config
import lsst.daf.base
import lsst.pipe.base
import lsst.pipe.base as pipeBase
import lsst.pex.config

from .references import MultiBandReferencesTask
Expand All @@ -39,9 +39,55 @@
__all__ = ("ForcedPhotImageConfig", "ForcedPhotImageTask")


class ForcedPhotImageConfig(lsst.pex.config.Config):
class ForcedPhotImageConfig(pipeBase.PipelineTaskConfig):
"""Config class for forced measurement driver task."""
# Gen 3 options
inputSchema = pipeBase.InitInputDatasetField(
doc="Schema for the input measurement catalogs.",
name="",
nameTemplate="{inputCoaddName}Coadd_ref_schema",
storageClass="SourceCatalog",
)
outputSchema = pipeBase.InitOutputDatasetField(
doc="Schema for the output forced measurement catalogs.",
name="",
nameTemplate="{outputCoaddName}Coadd_forced_src_schema",
storageClass="SourceCatalog",
)
exposure = pipeBase.InputDatasetField(
doc="Input exposure to perform photometry on.",
name="",
nameTemplate="{inputCoaddName}Coadd",
scalar=True,
storageClass="ExposureF",
dimensions=["AbstractFilter", "SkyMap", "Tract", "Patch"],
)
refCat = pipeBase.InputDatasetField(
doc="Reference catalog of sources.",
name="",
nameTemplate="{inputCoaddName}Coadd_ref",
scalar=True,
storageClass="SourceCatalog",
dimensions=["SkyMap", "Tract", "Patch"],
)
refWcs = pipeBase.InputDatasetField(
doc="Reference world coordinate system.",
name="",
nameTemplate="{inputCoaddName}Coadd.wcs",
scalar=True,
storageClass="TablePersistableWcs",
dimensions=["AbstractFilter", "SkyMap", "Tract", "Patch"],
)
measCat = pipeBase.OutputDatasetField(
doc="Output forced photometry catalog.",
name="",
nameTemplate="{outputCoaddName}Coadd_forced_src",
scalar=True,
storageClass="SourceCatalog",
dimensions=["AbstractFilter", "SkyMap", "Tract", "Patch"],
)

# ForcedPhotImage options
references = lsst.pex.config.ConfigurableField(
target=MultiBandReferencesTask,
doc="subtask to retrieve reference source catalog"
Expand Down Expand Up @@ -73,10 +119,16 @@ def setDefaults(self):
# Docstring inherited.
# Make catalogCalculation a no-op by default as no modelFlux is setup by default in
# ForcedMeasurementTask
super().setDefaults()

self.catalogCalculation.plugins.names = []
self.formatTemplateNames({"inputCoaddName": "deep",
"outputCoaddName": "deep",
"inputName": None})
self.quantum.dimensions = ("AbstractFilter", "SkyMap", "Tract", "Patch")


class ForcedPhotImageTask(lsst.pipe.base.CmdLineTask):
class ForcedPhotImageTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
"""A base class for command-line forced measurement drivers.
Parameters
Expand Down Expand Up @@ -111,8 +163,30 @@ class ForcedPhotImageTask(lsst.pipe.base.CmdLineTask):
ConfigClass = ForcedPhotImageConfig
_DefaultName = "processImageForcedTask"

def __init__(self, butler=None, refSchema=None, **kwds):
super(lsst.pipe.base.CmdLineTask, self).__init__(**kwds)
def getInitOutputDatasets(self):
return {"outputSchema": afwTable.SourceCatalog(self.measurement.schema)}

def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
if 'measCat' not in inputData.keys():
packer = butler.registry.makeDataIdPacker("TractPatch", inputDataIds['exposure'])
expId = packer.pack(inputDataIds['exposure'])
expBits = packer.maxBits
idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)

inputData['measCat'] = self.measurement.generateMeasCat(inputData['exposure'],
inputData['refCat'],
inputData['refWcs'],
idFactory=idFactory)

return super().adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)

def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
super().__init__(**kwds)

if initInputs is not None:
if refSchema is None:
refSchema = initInputs['inputSchema'].schema

self.makeSubtask("references", butler=butler, schema=refSchema)
if refSchema is None:
refSchema = self.references.schema
Expand Down Expand Up @@ -200,7 +274,7 @@ def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
)
self.catalogCalculation.run(measCat)

return lsst.pipe.base.Struct(measCat=measCat)
return pipeBase.Struct(measCat=measCat)

def makeIdFactory(self, dataRef):
"""Hook for derived classes to make an ID factory for forced sources.
Expand Down Expand Up @@ -270,7 +344,7 @@ def writeOutput(self, dataRef, sources):
sources : `lsst.afw.table.SourceCatalog`
Catalog of sources to save.
"""
dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
dataRef.put(sources, self.dataPrefix + "forced_src", flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)

def getSchemaCatalogs(self):
"""The schema catalogs that will be used by this task.
Expand All @@ -285,7 +359,7 @@ def getSchemaCatalogs(self):
There is only one schema for each type of forced measurement. The
dataset type for this measurement is defined in the mapper.
"""
catalog = lsst.afw.table.SourceCatalog(self.measurement.schema)
catalog = afwTable.SourceCatalog(self.measurement.schema)
catalog.getTable().setMetadata(self.measurement.algMetadata)
datasetType = self.dataPrefix + "forced_src"
return {datasetType: catalog}
Expand Down

0 comments on commit a361322

Please sign in to comment.