LSST Applications g0da5cf3356+25b44625d0,g17e5ecfddb+50a5ac4092,g1c76d35bf8+585f0f68a2,g295839609d+8ef6456700,g2e2c1a68ba+cc1f6f037e,g38293774b4+62d12e78cb,g3b44f30a73+2891c76795,g48ccf36440+885b902d19,g4b2f1765b6+0c565e8f25,g5320a0a9f6+bd4bf1dc76,g56364267ca+403c24672b,g56b687f8c9+585f0f68a2,g5c4744a4d9+78cd207961,g5ffd174ac0+bd4bf1dc76,g6075d09f38+3075de592a,g667d525e37+cacede5508,g6f3e93b5a3+da81c812ee,g71f27ac40c+cacede5508,g7212e027e3+eb621d73aa,g774830318a+18d2b9fa6c,g7985c39107+62d12e78cb,g79ca90bc5c+fa2cc03294,g881bdbfe6c+cacede5508,g91fc1fa0cf+82a115f028,g961520b1fb+2534687f64,g96f01af41f+f2060f23b6,g9ca82378b8+cacede5508,g9d27549199+78cd207961,gb065e2a02a+ad48cbcda4,gb1df4690d6+585f0f68a2,gb35d6563ee+62d12e78cb,gbc3249ced9+bd4bf1dc76,gbec6a3398f+bd4bf1dc76,gd01420fc67+bd4bf1dc76,gd59336e7c4+c7bb92e648,gf46e8334de+81c9a61069,gfed783d017+bd4bf1dc76,v25.0.1.rc3
LSST Data Management Base Package
Loading...
Searching...
No Matches
forcedPhotCoadd.py
Go to the documentation of this file.
1# This file is part of meas_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22import warnings
23
24import lsst.pex.config
25import lsst.afw.table
26
27import lsst.pipe.base as pipeBase
28from lsst.obs.base import ExposureIdInfo
29
30from .forcedMeasurement import ForcedMeasurementTask
31from .applyApCorr import ApplyApCorrTask
32from .catalogCalculation import CatalogCalculationTask
33
34__all__ = ("ForcedPhotCoaddConfig", "ForcedPhotCoaddTask")
35
36
37class ForcedPhotCoaddConnections(pipeBase.PipelineTaskConnections,
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",
45 )
46 outputSchema = pipeBase.connectionTypes.InitOutput(
47 doc="Schema for the output forced measurement catalogs.",
48 name="{outputCoaddName}Coadd_forced_src_schema",
49 storageClass="SourceCatalog",
50 )
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"],
56 )
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"],
62 )
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")
68 )
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")
74 )
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"),
80 )
81 refWcs = pipeBase.connectionTypes.Input(
82 doc="Reference world coordinate system.",
83 name="{inputCoaddName}Coadd.wcs",
84 storageClass="Wcs",
85 dimensions=["band", "skymap", "tract", "patch"],
86 ) # used in place of a skymap wcs because of DM-28880
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"],
92 )
93
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")
100
101
102class ForcedPhotCoaddConfig(pipeBase.PipelineTaskConfig,
103 pipelineConnections=ForcedPhotCoaddConnections):
105 target=ForcedMeasurementTask,
106 doc="subtask to do forced measurement"
107 )
108 coaddName = lsst.pex.config.Field(
109 doc="coadd name: typically one of deep or goodSeeing",
110 dtype=str,
111 default="deep",
112 )
113 doApCorr = lsst.pex.config.Field(
114 dtype=bool,
115 default=True,
116 doc="Run subtask to apply aperture corrections"
117 )
119 target=ApplyApCorrTask,
120 doc="Subtask to apply aperture corrections"
121 )
122 catalogCalculation = lsst.pex.config.ConfigurableField(
123 target=CatalogCalculationTask,
124 doc="Subtask to run catalogCalculation plugins on catalog"
125 )
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.",
130 dtype=str,
131 default="ScarletModelData",
132 optional=True
133 )
134 doConserveFlux = lsst.pex.config.Field(
135 dtype=bool,
136 default=True,
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(
141 dtype=bool,
142 default=True,
143 doc="Whether to strip footprints from the output catalog before "
144 "saving to disk. "
145 "This is usually done when using scarlet models to save disk space.")
146 hasFakes = lsst.pex.config.Field(
147 dtype=bool,
148 default=False,
149 doc="Should be set to True if fake sources have been inserted into the input data."
150 )
151
152 def setDefaults(self):
153 # Docstring inherited.
154 # Make catalogCalculation a no-op by default as no modelFlux is setup by default in
155 # ForcedMeasurementTask
156 super().setDefaults()
157
158 self.catalogCalculation.plugins.names = []
159 self.measurement.copyColumns["id"] = "id"
160 self.measurement.copyColumns["parent"] = "parent"
161 self.measurement.plugins.names |= ['base_InputCount', 'base_Variance']
162 self.measurement.plugins['base_PixelFlags'].masksFpAnywhere = ['CLIPPED', 'SENSOR_EDGE',
163 'REJECTED', 'INEXACT_PSF']
164 self.measurement.plugins['base_PixelFlags'].masksFpCenter = ['CLIPPED', 'SENSOR_EDGE',
165 'REJECTED', 'INEXACT_PSF']
166
167
168class ForcedPhotCoaddTask(pipeBase.PipelineTask):
169 """A pipeline task for performing forced measurement on coadd images.
170
171 Parameters
172 ----------
173 butler : `None`
174 Deprecated and unused. Should always be `None`.
175 refSchema : `lsst.afw.table.Schema`, optional
176 The schema of the reference catalog, passed to the constructor of the
177 references subtask. Optional, but must be specified if ``initInputs``
178 is not; if both are specified, ``initInputs`` takes precedence.
179 initInputs : `dict`
180 Dictionary that can contain a key ``inputSchema`` containing the
181 schema. If present will override the value of ``refSchema``.
182 **kwds
183 Keyword arguments are passed to the supertask constructor.
184 """
185
186 ConfigClass = ForcedPhotCoaddConfig
187 _DefaultName = "forcedPhotCoadd"
188 dataPrefix = "deepCoadd_"
189
190 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
191 super().__init__(**kwds)
192
193 if butler is not None:
194 warnings.warn("The 'butler' parameter is no longer used and can be safely removed.",
195 category=FutureWarning, stacklevel=2)
196 butler = None
197
198 if initInputs is not None:
199 refSchema = initInputs['inputSchema'].schema
200
201 if refSchema is None:
202 raise ValueError("No reference schema provided.")
203 self.makeSubtask("measurement", refSchema=refSchema)
204 # It is necessary to get the schema internal to the forced measurement task until such a time
205 # that the schema is not owned by the measurement task, but is passed in by an external caller
206 if self.config.doApCorr:
207 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
208 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
209 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
210
211 def runQuantum(self, butlerQC, inputRefs, outputRefs):
212 inputs = butlerQC.get(inputRefs)
213
214 refCatInBand = inputs.pop('refCatInBand')
215 if self.config.footprintDatasetName == "ScarletModelData":
216 footprintData = inputs.pop("scarletModels")
217 elif self.config.footprintDatasetName == "DeblendedFlux":
218 footprintData = inputs.pop("footprintCatIndBand")
219 else:
220 footprintData = None
221 inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
222 inputs['exposure'],
223 inputs['refCat'],
224 refCatInBand,
225 inputs['refWcs'],
226 "tract_patch",
227 footprintData)
228 outputs = self.run(**inputs)
229 # Strip HeavyFootprints to save space on disk
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)
235
236 def generateMeasCat(self, exposureDataId, exposure, refCat, refCatInBand, refWcs, idPackerName,
237 footprintData):
238 """Generate a measurement catalog.
239
240 Parameters
241 ----------
242 exposureDataId : `DataId`
243 Butler dataId for this exposure.
245 Exposure to generate the catalog for.
247 Catalog of shapes and positions at which to force photometry.
248 refCatInBand : `lsst.afw.table.SourceCatalog`
249 Catalog of shapes and position in the band forced photometry is
250 currently being performed
251 refWcs : `lsst.afw.image.SkyWcs`
252 Reference world coordinate system.
253 idPackerName : `str`
254 Type of ID packer to construct from the registry.
255 footprintData : `ScarletDataModel` or `lsst.afw.table.SourceCatalog`
256 Either the scarlet data models or the deblended catalog
257 containing footprints.
258 If `footprintData` is `None` then the footprints contained
259 in `refCatInBand` are used.
260
261 Returns
262 -------
264 Catalog of forced sources to measure.
265 expId : `int`
266 Unique binary id associated with the input exposure
267
268 Raises
269 ------
270 LookupError
271 Raised if a footprint with a given source id was in the reference
272 catalog but not in the reference catalog in band (meaning there
273 was some sort of mismatch in the two input catalogs)
274 """
275 exposureIdInfo = ExposureIdInfo.fromDataId(exposureDataId, idPackerName)
276 idFactory = exposureIdInfo.makeSourceIdFactory()
277
278 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
279 idFactory=idFactory)
280 # attach footprints here as this can naturally live inside this method
281 if self.config.footprintDatasetName == "ScarletModelData":
282 # Load the scarlet models
283 self._attachScarletFootprints(
284 catalog=measCat,
285 modelData=footprintData,
286 exposure=exposure,
287 band=exposureDataId["band"]
288 )
289 else:
290 if self.config.footprintDatasetName is None:
291 footprintCat = refCatInBand
292 else:
293 footprintCat = footprintData
294 for srcRecord in measCat:
295 fpRecord = footprintCat.find(srcRecord.getId())
296 if fpRecord is None:
297 raise LookupError("Cannot find Footprint for source {}; please check that {} "
298 "IDs are compatible with reference source IDs"
299 .format(srcRecord.getId(), footprintCat))
300 srcRecord.setFootprint(fpRecord.getFootprint())
301 return measCat, exposureIdInfo.expId
302
303 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
304 """Perform forced measurement on a single exposure.
305
306 Parameters
307 ----------
309 The measurement catalog, based on the sources listed in the
310 reference catalog.
311 exposure : `lsst.afw.image.Exposure`
312 The measurement image upon which to perform forced detection.
314 The reference catalog of sources to measure.
315 refWcs : `lsst.afw.image.SkyWcs`
316 The WCS for the references.
317 exposureId : `int`
318 Optional unique exposureId used for random seed in measurement
319 task.
320
321 Returns
322 -------
323 result : ~`lsst.pipe.base.Struct`
324 Structure with fields:
325
326 ``measCat``
327 Catalog of forced measurement results
329 """
330 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
331 if self.config.doApCorr:
332 self.applyApCorr.run(
333 catalog=measCat,
334 apCorrMap=exposure.getInfo().getApCorrMap()
335 )
336 self.catalogCalculation.run(measCat)
337
338 return pipeBase.Struct(measCat=measCat)
339
340 def _attachScarletFootprints(self, catalog, modelData, exposure, band):
341 """Attach scarlet models as HeavyFootprints
342 """
343 if self.config.doConserveFlux:
344 redistributeImage = exposure.image
345 else:
346 redistributeImage = None
347 # Attach the footprints
348 modelData.updateCatalogFootprints(
349 catalog=catalog,
350 band=band,
351 psfModel=exposure.getPsf(),
352 redistributeImage=redistributeImage,
353 removeScarletData=True,
354 updateFluxColumns=False,
355 )
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
Defines the fields and offsets for a table.
Definition: Schema.h:51