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`).
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=(
"band",
"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=[
"band",
"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=[
"band",
"skymap",
"tract",
"patch"],
78 doc=
"Output forced photometry catalog.",
79 name=
"{outputCoaddName}Coadd_forced_src",
80 storageClass=
"SourceCatalog",
81 dimensions=[
"band",
"skymap",
"tract",
"patch"],
85 class ForcedPhotImageConfig(
PipelineTaskConfig, pipelineConnections=ForcedPhotImageConnections):
86 """Config class for forced measurement driver task."""
89 target=MultiBandReferencesTask,
90 doc=
"subtask to retrieve reference source catalog"
93 target=ForcedMeasurementTask,
94 doc=
"subtask to do forced measurement"
97 doc=
"coadd name: typically one of deep or goodSeeing",
104 doc=
"Run subtask to apply aperture corrections"
107 target=ApplyApCorrTask,
108 doc=
"Subtask to apply aperture corrections"
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"