Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-17474: Convert ForcedPhotCcd and ForcedPhotCoadd into pipelineTasks. #135

Merged
merged 1 commit into from
Jan 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 99 additions & 0 deletions python/lsst/meas/base/forcedPhotCcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ class ForcedPhotCcdConfig(ForcedPhotImageConfig):
default=False
)

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

# Override the Gen3 datasets from ForcedPhotImageTaskConfig.
# When these nameTemplate values are used in ForcedPhotCoadd,
# there is a coadd name that may change, depending on the
# coadd type. For CCD forced photometry, there will likely
# only ever be a single calexp type, but for consistency, use
# the nameTemplate with nothing to substitute.
self.outputSchema.nameTemplate = "forced_src_schema"
self.exposure.nameTemplate = "{inputName}"
self.exposure.dimensions = ["Instrument", "Visit", "Detector"]
self.measCat.nameTemplate = "forced_src"
self.measCat.dimensions = ["Instrument", "Visit", "Detector", "SkyMap", "Tract"]

self.formatTemplateNames({"inputName": "calexp",
czwa marked this conversation as resolved.
Show resolved Hide resolved
"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 @@ -186,6 +205,86 @@ class ForcedPhotCcdTask(ForcedPhotImageTask):
_DefaultName = "forcedPhotCcd"
dataPrefix = ""

def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
inputData['refCat'] = self.filterReferences(inputData['exposure'],
inputData['refCat'], inputData['refWcs'])
inputData['measCat'] = self.generateMeasCat(inputDataIds['exposure'],
inputData['exposure'],
inputData['refCat'], inputData['refWcs'],
"VisitDetector", butler)

return self.run(**inputData)

def filterReferences(self, exposure, refCat, refWcs):
"""Filter reference catalog so that all sources are within the
boundaries of the exposure.

Parameters
----------
exposure : `lsst.afw.image.exposure.Exposure`
Exposure to generate the catalog for.
refCat : `lsst.afw.table.SourceCatalog`
Catalog of shapes and positions at which to force photometry.
refWcs : `lsst.afw.image.SkyWcs`
Reference world coordinate system.

Returns
-------
refSources : `lsst.afw.table.SourceCatalog`
Filtered catalog of forced sources to measure.

Notes
-----
Filtering the reference catalog is currently handled by Gen2
specific methods. To function for Gen3, this method copies
code segments to do the filtering and transformation. The
majority of this code is based on the methods of
lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader

"""

# Step 1: Determine bounds of the exposure photometry will
# be performed on.
expWcs = exposure.getWcs()
expRegion = 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(refCat)(refCat.table)
for record in 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.
refSources = type(refCat)(refCat.table)
for record in 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:
refSources.append(record)

# Step 4: Transform source footprints from the reference
# coordinates to the exposure coordinates.
for refRecord in refSources:
refRecord.setFootprint(refRecord.getFootprint().transform(refWcs,
expWcs, expRegion))
czwa marked this conversation as resolved.
Show resolved Hide resolved
# Step 5: Replace reference catalog with filtered source list.
return refSources

def makeIdFactory(self, dataRef):
"""Create an object that generates globally unique source IDs.

Expand Down
101 changes: 97 additions & 4 deletions python/lsst/meas/base/forcedPhotImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,49 @@
__all__ = ("ForcedPhotImageConfig", "ForcedPhotImageTask")


class ForcedPhotImageConfig(lsst.pex.config.Config):
class ForcedPhotImageConfig(lsst.pipe.base.PipelineTaskConfig):
"""Config class for forced measurement driver task."""
# Gen 3 options
inputSchema = lsst.pipe.base.InitInputDatasetField(
doc="Schema for the input measurement catalogs.",
nameTemplate="{inputCoaddName}Coadd_ref_schema",
storageClass="SourceCatalog",
)
outputSchema = lsst.pipe.base.InitOutputDatasetField(
doc="Schema for the output forced measurement catalogs.",
nameTemplate="{outputCoaddName}Coadd_forced_src_schema",
storageClass="SourceCatalog",
)
exposure = lsst.pipe.base.InputDatasetField(
doc="Input exposure to perform photometry on.",
nameTemplate="{inputCoaddName}Coadd",
scalar=True,
storageClass="ExposureF",
dimensions=["AbstractFilter", "SkyMap", "Tract", "Patch"],
)
refCat = lsst.pipe.base.InputDatasetField(
doc="Catalog of shapes and positions at which to force photometry.",
nameTemplate="{inputCoaddName}Coadd_ref",
scalar=True,
storageClass="SourceCatalog",
dimensions=["SkyMap", "Tract", "Patch"],
)
refWcs = lsst.pipe.base.InputDatasetField(
doc="Reference world coordinate system.",
nameTemplate="{inputCoaddName}Coadd.wcs",
scalar=True,
storageClass="TablePersistableWcs",
dimensions=["AbstractFilter", "SkyMap", "Tract", "Patch"],
)
measCat = lsst.pipe.base.OutputDatasetField(
doc="Output forced photometry catalog.",
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 +113,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(lsst.pipe.base.PipelineTask, lsst.pipe.base.CmdLineTask):
"""A base class for command-line forced measurement drivers.

Parameters
Expand Down Expand Up @@ -111,8 +157,12 @@ 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 __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
czwa marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(**kwds)

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

self.makeSubtask("references", butler=butler, schema=refSchema)
if refSchema is None:
refSchema = self.references.schema
Expand All @@ -123,6 +173,49 @@ def __init__(self, butler=None, refSchema=None, **kwds):
self.makeSubtask("applyApCorr", schema=self.measurement.schema)
self.makeSubtask('catalogCalculation', schema=self.measurement.schema)

def getInitOutputDatasets(self):
return {"outputSchema": lsst.afw.table.SourceCatalog(self.measurement.schema)}

def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
inputData['measCat'] = self.generateMeasCat(inputDataIds['exposure'],
inputData['exposure'],
inputData['refCat'], inputData['refWcs'],
"TractPatch", butler)

return self.run(**inputData)

def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName, butler):
"""Generate a measurement catalog for Gen3.

Parameters
----------
exposureDataId : `DataId`
Butler dataId for this exposure.
exposure : `lsst.afw.image.exposure.Exposure`
Exposure to generate the catalog for.
refCat : `lsst.afw.table.SourceCatalog`
Catalog of shapes and positions at which to force photometry.
refWcs : `lsst.afw.image.SkyWcs`
Reference world coordinate system.
idPackerName : `str`
Type of ID packer to construct from the registry.
butler : `lsst.daf.persistence.butler.Butler`
Butler to use to construct id packer.

Returns
-------
measCat : `lsst.afw.table.SourceCatalog`
Catalog of forced sources to measure.
"""
packer = butler.registry.makeDataIdPacker(idPackerName, exposureDataId)
expId = packer.pack(exposureDataId)
expBits = packer.maxBits
idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)

measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
idFactory=idFactory)
return measCat

def runDataRef(self, dataRef, psfCache=None):
"""Perform forced measurement on a single exposure.

Expand Down