Coverage for python/lsst/meas/base/forcedPhotImage.py : 43%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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/>.
22"""Base command-line driver task for forced measurement.
24Must be inherited to specialize for a specific dataset to be used (see
25`ForcedPhotCcdTask`, `ForcedPhotCoaddTask`).
26"""
28import lsst.afw.table
29import lsst.pex.config
30import lsst.daf.base
31import lsst.pipe.base
32import lsst.pex.config
34from lsst.pipe.base import PipelineTaskConnections, PipelineTaskConfig
35import lsst.pipe.base.connectionTypes as cT
37from .references import MultiBandReferencesTask
38from .forcedMeasurement import ForcedMeasurementTask
39from .applyApCorr import ApplyApCorrTask
40from .catalogCalculation import CatalogCalculationTask
42__all__ = ("ForcedPhotImageConfig", "ForcedPhotImageTask", "ForcedPhotImageConnections")
45class ForcedPhotImageConnections(PipelineTaskConnections,
46 dimensions=("abstract_filter", "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",
53 )
54 outputSchema = cT.InitOutput(
55 doc="Schema for the output forced measurement catalogs.",
56 name="{outputCoaddName}Coadd_forced_src_schema",
57 storageClass="SourceCatalog",
58 )
59 exposure = cT.Input(
60 doc="Input exposure to perform photometry on.",
61 name="{inputCoaddName}Coadd",
62 storageClass="ExposureF",
63 dimensions=["abstract_filter", "skymap", "tract", "patch"],
64 )
65 refCat = cT.Input(
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"],
70 )
71 refWcs = cT.Input(
72 doc="Reference world coordinate system.",
73 name="{inputCoaddName}Coadd.wcs",
74 storageClass="Wcs",
75 dimensions=["abstract_filter", "skymap", "tract", "patch"],
76 )
77 measCat = cT.Output(
78 doc="Output forced photometry catalog.",
79 name="{outputCoaddName}Coadd_forced_src",
80 storageClass="SourceCatalog",
81 dimensions=["abstract_filter", "skymap", "tract", "patch"],
82 )
85class ForcedPhotImageConfig(PipelineTaskConfig, pipelineConnections=ForcedPhotImageConnections):
86 """Config class for forced measurement driver task."""
87 # ForcedPhotImage options
88 references = lsst.pex.config.ConfigurableField(
89 target=MultiBandReferencesTask,
90 doc="subtask to retrieve reference source catalog"
91 )
92 measurement = lsst.pex.config.ConfigurableField(
93 target=ForcedMeasurementTask,
94 doc="subtask to do forced measurement"
95 )
96 coaddName = lsst.pex.config.Field(
97 doc="coadd name: typically one of deep or goodSeeing",
98 dtype=str,
99 default="deep",
100 )
101 doApCorr = lsst.pex.config.Field(
102 dtype=bool,
103 default=True,
104 doc="Run subtask to apply aperture corrections"
105 )
106 applyApCorr = lsst.pex.config.ConfigurableField(
107 target=ApplyApCorrTask,
108 doc="Subtask to apply aperture corrections"
109 )
110 catalogCalculation = lsst.pex.config.ConfigurableField(
111 target=CatalogCalculationTask,
112 doc="Subtask to run catalogCalculation plugins on catalog"
113 )
115 def setDefaults(self):
116 # Docstring inherited.
117 # Make catalogCalculation a no-op by default as no modelFlux is setup by default in
118 # ForcedMeasurementTask
119 super().setDefaults()
121 self.catalogCalculation.plugins.names = []
124class ForcedPhotImageTask(lsst.pipe.base.PipelineTask, lsst.pipe.base.CmdLineTask):
125 """A base class for command-line forced measurement drivers.
127 Parameters
128 ----------
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
133 precedence.
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.
138 **kwds
139 Keyword arguments are passed to the supertask constructor.
141 Notes
142 -----
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`.
154 """
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)
169 # It is necessary to get the schema internal to the forced measurement task until such a time
170 # that the schema is not owned by the measurement task, but is passed in by an external caller
171 if self.config.doApCorr:
172 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
173 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
174 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
176 def runQuantum(self, butlerQC, inputRefs, outputRefs):
177 inputs = butlerQC.get(inputRefs)
178 inputs['measCat'] = self.generateMeasCat(inputRefs.exposure.dataId,
179 inputs['exposure'],
180 inputs['refCat'], inputs['refWcs'],
181 "tract_patch")
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.
188 Parameters
189 ----------
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.
198 idPackerName : `str`
199 Name of DimensionPacker to use to generate the packed version
200 of the data ID to mangle into source IDs.
202 Returns
203 -------
204 measCat : `lsst.afw.table.SourceCatalog`
205 Catalog of forced sources to measure.
206 """
207 expId, expBits = exposureDataId.pack(idPackerName, returnMaxBits=True)
208 idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)
210 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
211 idFactory=idFactory)
212 return measCat
214 def runDataRef(self, dataRef, psfCache=None):
215 """Perform forced measurement on a single exposure.
217 Parameters
218 ----------
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.
231 Notes
232 -----
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.
238 """
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.
259 Parameters
260 ----------
261 measCat : `lsst.afw.table.SourceCatalog`
262 The measurement catalog, based on the sources listed in the
263 reference catalog.
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.
270 exposureId : `int`
271 Optional unique exposureId used for random seed in measurement
272 task.
274 Returns
275 -------
276 result : `lsst.pipe.base.Struct`
277 Structure with fields:
279 ``measCat``
280 Catalog of forced measurement results
281 (`lsst.afw.table.SourceCatalog`).
282 """
283 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
284 if self.config.doApCorr:
285 self.applyApCorr.run(
286 catalog=measCat,
287 apCorrMap=exposure.getInfo().getApCorrMap()
288 )
289 self.catalogCalculation.run(measCat)
291 return lsst.pipe.base.Struct(measCat=measCat)
293 def makeIdFactory(self, dataRef):
294 """Hook for derived classes to make an ID factory for forced sources.
296 Notes
297 -----
298 That this applies to forced *source* IDs, not object IDs, which are
299 usually handled by the ``measurement.copyColumns`` config option.
301 """
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.
310 Notes
311 -----
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).
317 """
318 raise NotImplementedError()
320 def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef):
321 r"""Attach footprints to blank sources prior to measurements.
323 Notes
324 -----
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.
337 """
338 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
340 def getExposure(self, dataRef):
341 """Read input exposure on which measurement will be performed.
343 Parameters
344 ----------
345 dataRef : `lsst.daf.persistence.ButlerDataRef`
346 Butler data reference.
347 """
348 return dataRef.get(self.dataPrefix + "calexp", immediate=True)
350 def writeOutput(self, dataRef, sources):
351 """Write forced source table
353 Parameters
354 ----------
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.
360 """
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.
366 Returns
367 -------
368 schemaCatalogs : `dict`
369 Dictionary mapping dataset type to schema catalog.
371 Notes
372 -----
373 There is only one schema for each type of forced measurement. The
374 dataset type for this measurement is defined in the mapper.
375 """
376 catalog = lsst.afw.table.SourceCatalog(self.measurement.schema)
377 catalog.getTable().setMetadata(self.measurement.algMetadata)
378 datasetType = self.dataPrefix + "forced_src"
379 return {datasetType: catalog}
381 def _getConfigName(self):
382 # Documented in superclass
383 return self.dataPrefix + "forced_config"
385 def _getMetadataName(self):
386 # Documented in superclass
387 return self.dataPrefix + "forced_metadata"