29from ._id_generator
import SkyMapIdGeneratorConfig
30from .forcedMeasurement
import ForcedMeasurementTask
31from .applyApCorr
import ApplyApCorrTask
32from .catalogCalculation
import CatalogCalculationTask
34__all__ = (
"ForcedPhotCoaddConfig",
"ForcedPhotCoaddTask")
38 dimensions=(
"band",
"skymap",
"tract",
"patch"),
39 defaultTemplates={
"inputCoaddName":
"deep",
40 "outputCoaddName":
"deep"}):
41 inputSchema = pipeBase.connectionTypes.InitInput(
42 doc=
"Schema for the input measurement catalogs.",
43 name=
"{inputCoaddName}Coadd_ref_schema",
44 storageClass=
"SourceCatalog",
46 outputSchema = pipeBase.connectionTypes.InitOutput(
47 doc=
"Schema for the output forced measurement catalogs.",
48 name=
"{outputCoaddName}Coadd_forced_src_schema",
49 storageClass=
"SourceCatalog",
51 exposure = pipeBase.connectionTypes.Input(
52 doc=
"Input exposure to perform photometry on.",
53 name=
"{inputCoaddName}Coadd_calexp",
54 storageClass=
"ExposureF",
55 dimensions=[
"band",
"skymap",
"tract",
"patch"],
57 refCat = pipeBase.connectionTypes.Input(
58 doc=
"Catalog of shapes and positions at which to force photometry.",
59 name=
"{inputCoaddName}Coadd_ref",
60 storageClass=
"SourceCatalog",
61 dimensions=[
"skymap",
"tract",
"patch"],
63 refCatInBand = pipeBase.connectionTypes.Input(
64 doc=
"Catalog of shapes and positions in the band having forced photometry done",
65 name=
"{inputCoaddName}Coadd_meas",
66 storageClass=
"SourceCatalog",
67 dimensions=(
"band",
"skymap",
"tract",
"patch")
69 footprintCatInBand = pipeBase.connectionTypes.Input(
70 doc=
"Catalog of footprints to attach to sources",
71 name=
"{inputCoaddName}Coadd_deblendedFlux",
72 storageClass=
"SourceCatalog",
73 dimensions=(
"band",
"skymap",
"tract",
"patch")
75 scarletModels = pipeBase.connectionTypes.Input(
76 doc=
"Multiband scarlet models produced by the deblender",
77 name=
"{inputCoaddName}Coadd_scarletModelData",
78 storageClass=
"ScarletModelData",
79 dimensions=(
"tract",
"patch",
"skymap"),
81 refWcs = pipeBase.connectionTypes.Input(
82 doc=
"Reference world coordinate system.",
83 name=
"{inputCoaddName}Coadd.wcs",
85 dimensions=[
"band",
"skymap",
"tract",
"patch"],
87 measCat = pipeBase.connectionTypes.Output(
88 doc=
"Output forced photometry catalog.",
89 name=
"{outputCoaddName}Coadd_forced_src",
90 storageClass=
"SourceCatalog",
91 dimensions=[
"band",
"skymap",
"tract",
"patch"],
94 def __init__(self, *, config=None):
95 super().__init__(config=config)
96 if config.footprintDatasetName !=
"ScarletModelData":
97 self.inputs.remove(
"scarletModels")
98 if config.footprintDatasetName !=
"DeblendedFlux":
99 self.inputs.remove(
"footprintCatInBand")
102class ForcedPhotCoaddConfig(pipeBase.PipelineTaskConfig,
103 pipelineConnections=ForcedPhotCoaddConnections):
104 measurement = lsst.pex.config.ConfigurableField(
105 target=ForcedMeasurementTask,
106 doc=
"subtask to do forced measurement"
108 coaddName = lsst.pex.config.Field(
109 doc=
"coadd name: typically one of deep or goodSeeing",
113 doApCorr = lsst.pex.config.Field(
116 doc=
"Run subtask to apply aperture corrections"
118 applyApCorr = lsst.pex.config.ConfigurableField(
119 target=ApplyApCorrTask,
120 doc=
"Subtask to apply aperture corrections"
122 catalogCalculation = lsst.pex.config.ConfigurableField(
123 target=CatalogCalculationTask,
124 doc=
"Subtask to run catalogCalculation plugins on catalog"
126 footprintDatasetName = lsst.pex.config.Field(
127 doc=
"Dataset (without coadd prefix) that should be used to obtain (Heavy)Footprints for sources. "
128 "Must have IDs that match those of the reference catalog."
129 "If None, Footprints will be generated by transforming the reference Footprints.",
131 default=
"ScarletModelData",
134 doConserveFlux = lsst.pex.config.Field(
137 doc=
"Whether to use the deblender models as templates to re-distribute the flux "
138 "from the 'exposure' (True), or to perform measurements on the deblender model footprints. "
139 "If footprintDatasetName != 'ScarletModelData' then this field is ignored.")
140 doStripFootprints = lsst.pex.config.Field(
143 doc=
"Whether to strip footprints from the output catalog before "
145 "This is usually done when using scarlet models to save disk space.")
146 hasFakes = lsst.pex.config.Field(
149 doc=
"Should be set to True if fake sources have been inserted into the input data."
151 idGenerator = SkyMapIdGeneratorConfig.make_field()
153 def setDefaults(self):
157 super().setDefaults()
159 self.catalogCalculation.plugins.names = []
160 self.measurement.copyColumns[
"id"] =
"id"
161 self.measurement.copyColumns[
"parent"] =
"parent"
162 self.measurement.plugins.names |= [
'base_InputCount',
'base_Variance']
163 self.measurement.plugins[
'base_PixelFlags'].masksFpAnywhere = [
'CLIPPED',
'SENSOR_EDGE',
164 'REJECTED',
'INEXACT_PSF']
165 self.measurement.plugins[
'base_PixelFlags'].masksFpCenter = [
'CLIPPED',
'SENSOR_EDGE',
166 'REJECTED',
'INEXACT_PSF']
170 """A pipeline task for performing forced measurement on coadd images.
175 Deprecated
and unused. Should always be `
None`.
177 The schema of the reference catalog, passed to the constructor of the
178 references subtask. Optional, but must be specified
if ``initInputs``
179 is not;
if both are specified, ``initInputs`` takes precedence.
181 Dictionary that can contain a key ``inputSchema`` containing the
182 schema. If present will override the value of ``refSchema``.
184 Keyword arguments are passed to the supertask constructor.
187 ConfigClass = ForcedPhotCoaddConfig
188 _DefaultName = "forcedPhotCoadd"
189 dataPrefix =
"deepCoadd_"
191 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
192 super().__init__(**kwds)
194 if butler
is not None:
195 warnings.warn(
"The 'butler' parameter is no longer used and can be safely removed.",
196 category=FutureWarning, stacklevel=2)
199 if initInputs
is not None:
200 refSchema = initInputs[
'inputSchema'].schema
202 if refSchema
is None:
203 raise ValueError(
"No reference schema provided.")
204 self.makeSubtask(
"measurement", refSchema=refSchema)
207 if self.config.doApCorr:
208 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
209 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
212 def runQuantum(self, butlerQC, inputRefs, outputRefs):
213 inputs = butlerQC.get(inputRefs)
215 refCatInBand = inputs.pop(
'refCatInBand')
216 if self.config.footprintDatasetName ==
"ScarletModelData":
217 footprintData = inputs.pop(
"scarletModels")
218 elif self.config.footprintDatasetName ==
"DeblendedFlux":
219 footprintData = inputs.pop(
"footprintCatIndBand")
222 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
228 outputs = self.run(**inputs)
230 if self.config.footprintDatasetName ==
"ScarletModelData" and self.config.doStripFootprints:
231 sources = outputs.measCat
232 for source
in sources[sources[
"parent"] != 0]:
233 source.setFootprint(
None)
234 butlerQC.put(outputs, outputRefs)
236 def generateMeasCat(self, dataId, exposure, refCat, refCatInBand, refWcs, footprintData):
237 """Generate a measurement catalog.
241 dataId : `lsst.daf.butler.DataCoordinate`
242 Butler data ID for this image,
with ``{tract, patch, band}`` keys.
243 exposure : `lsst.afw.image.exposure.Exposure`
244 Exposure to generate the catalog
for.
246 Catalog of shapes
and positions at which to force photometry.
248 Catalog of shapes
and position
in the band forced photometry
is
249 currently being performed
250 refWcs : `lsst.afw.image.SkyWcs`
251 Reference world coordinate system.
253 Either the scarlet data models
or the deblended catalog containing
254 footprints. If `footprintData`
is `
None` then the footprints
255 contained
in `refCatInBand` are used.
260 Catalog of forced sources to measure.
262 Unique binary id associated
with the input exposure
267 Raised
if a footprint
with a given source id was
in the reference
268 catalog but
not in the reference catalog
in band (meaning there was
269 some sort of mismatch
in the two input catalogs)
271 id_generator = self.config.idGenerator.apply(dataId)
272 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
273 idFactory=id_generator.make_table_id_factory())
275 if self.config.footprintDatasetName ==
"ScarletModelData":
277 self._attachScarletFootprints(
279 modelData=footprintData,
284 if self.config.footprintDatasetName
is None:
285 footprintCat = refCatInBand
287 footprintCat = footprintData
288 for srcRecord
in measCat:
289 fpRecord = footprintCat.find(srcRecord.getId())
291 raise LookupError(
"Cannot find Footprint for source {}; please check that {} "
292 "IDs are compatible with reference source IDs"
293 .format(srcRecord.getId(), footprintCat))
294 srcRecord.setFootprint(fpRecord.getFootprint())
295 return measCat, id_generator.catalog_id
297 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
298 """Perform forced measurement on a single exposure.
303 The measurement catalog, based on the sources listed in the
306 The measurement image upon which to perform forced detection.
308 The reference catalog of sources to measure.
309 refWcs : `lsst.afw.image.SkyWcs`
310 The WCS
for the references.
312 Optional unique exposureId used
for random seed
in measurement
317 result : ~`lsst.pipe.base.Struct`
318 Structure
with fields:
321 Catalog of forced measurement results
324 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
325 if self.config.doApCorr:
326 self.applyApCorr.run(
328 apCorrMap=exposure.getInfo().getApCorrMap()
330 self.catalogCalculation.run(measCat)
332 return pipeBase.Struct(measCat=measCat)
334 def _attachScarletFootprints(self, catalog, modelData, exposure, band):
335 """Attach scarlet models as HeavyFootprints
337 if self.config.doConserveFlux:
338 redistributeImage = exposure.image
340 redistributeImage =
None
342 modelData.updateCatalogFootprints(
345 psfModel=exposure.getPsf(),
346 redistributeImage=redistributeImage,
347 removeScarletData=
True,
348 updateFluxColumns=
False,