LSSTApplications  20.0.0
LSSTDataManagementBasePackage
forcedPhotImage.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 
22 """Base command-line driver task for forced measurement.
23 
24 Must be inherited to specialize for a specific dataset to be used (see
25 `ForcedPhotCcdTask`, `ForcedPhotCoaddTask`).
26 """
27 
28 import lsst.afw.table
29 import lsst.pex.config
30 import lsst.daf.base
31 import lsst.pipe.base
32 import lsst.pex.config
33 
34 from lsst.pipe.base import PipelineTaskConnections, PipelineTaskConfig
36 
37 from .references import MultiBandReferencesTask
38 from .forcedMeasurement import ForcedMeasurementTask
39 from .applyApCorr import ApplyApCorrTask
40 from .catalogCalculation import CatalogCalculationTask
41 
42 __all__ = ("ForcedPhotImageConfig", "ForcedPhotImageTask", "ForcedPhotImageConnections")
43 
44 
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  )
83 
84 
85 class 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  )
114 
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()
120 
121  self.catalogCalculation.plugins.names = []
122 
123 
124 class ForcedPhotImageTask(lsst.pipe.base.PipelineTask, lsst.pipe.base.CmdLineTask):
125  """A base class for command-line forced measurement drivers.
126 
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.
140 
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.
147 
148  This task is not directly usable as a command line task. Subclasses must:
149 
150  - Set the `_DefaultName` class attribute;
151  - Implement `makeIdFactory`;
152  - Implement `fetchReferences`;
153  - Optionally, implement `attachFootprints`.
154  """
155 
156  ConfigClass = ForcedPhotImageConfig
157  _DefaultName = "processImageForcedTask"
158 
159  def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
160  super().__init__(**kwds)
161 
162  if initInputs is not None:
163  refSchema = initInputs['inputSchema'].schema
164 
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)
175 
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)
184 
185  def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName):
186  """Generate a measurement catalog for Gen3.
187 
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.
201 
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)
209 
210  measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
211  idFactory=idFactory)
212  return measCat
213 
214  def runDataRef(self, dataRef, psfCache=None):
215  """Perform forced measurement on a single exposure.
216 
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.
230 
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)
244 
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)
249 
250  exposureId = self.getExposureId(dataRef)
251 
252  forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
253 
254  self.writeOutput(dataRef, forcedPhotResult.measCat)
255 
256  def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
257  """Perform forced measurement on a single exposure.
258 
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.
273 
274  Returns
275  -------
276  result : `lsst.pipe.base.Struct`
277  Structure with fields:
278 
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)
290 
291  return lsst.pipe.base.Struct(measCat=measCat)
292 
293  def makeIdFactory(self, dataRef):
294  """Hook for derived classes to make an ID factory for forced sources.
295 
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.
300 
301  """
302  raise NotImplementedError()
303 
304  def getExposureId(self, dataRef):
305  raise NotImplementedError()
306 
307  def fetchReferences(self, dataRef, exposure):
308  """Hook for derived classes to define how to get reference objects.
309 
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()
319 
320  def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef):
321  r"""Attach footprints to blank sources prior to measurements.
322 
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.
328 
329  Subclasses of this class must implement this method to define how
330  those `~lsst.afw.detection.Footprint`\ s should be generated.
331 
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)
339 
340  def getExposure(self, dataRef):
341  """Read input exposure on which measurement will be performed.
342 
343  Parameters
344  ----------
345  dataRef : `lsst.daf.persistence.ButlerDataRef`
346  Butler data reference.
347  """
348  return dataRef.get(self.dataPrefix + "calexp", immediate=True)
349 
350  def writeOutput(self, dataRef, sources):
351  """Write forced source table
352 
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)
362 
363  def getSchemaCatalogs(self):
364  """The schema catalogs that will be used by this task.
365 
366  Returns
367  -------
368  schemaCatalogs : `dict`
369  Dictionary mapping dataset type to schema catalog.
370 
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}
380 
381  def _getConfigName(self):
382  # Documented in superclass
383  return self.dataPrefix + "forced_config"
384 
385  def _getMetadataName(self):
386  # Documented in superclass
387  return self.dataPrefix + "forced_metadata"
lsst::afw::table::IdFactory::makeSource
static std::shared_ptr< IdFactory > makeSource(RecordId expId, int reserved)
Return an IdFactory that includes another, fixed ID in the higher-order bits.
Definition: IdFactory.cc:72
lsst::log.log.logContinued.info
def info(fmt, *args)
Definition: logContinued.py:198
lsst.pipe.tasks.assembleCoadd.run
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
Definition: assembleCoadd.py:712
lsst.pipe.base.pipelineTask.PipelineTask
Definition: pipelineTask.py:32
lsst.pipe.base.struct.Struct
Definition: struct.py:26
lsst::afw::table._source.SourceCatalog
Definition: _source.py:33
pex.config.wrap.setDefaults
setDefaults
Definition: wrap.py:293
lsst.pipe.base.connections.PipelineTaskConnections
Definition: connections.py:254
lsst::afw::table
Definition: table.dox:3
lsst.pipe.base.config.PipelineTaskConfig
Definition: config.py:113
lsst::daf::base
Definition: Utils.h:47
lsst.pipe.base
Definition: __init__.py:1
lsst::meas::base.forcedPhotImage.ForcedPhotImageConnections
Definition: forcedPhotImage.py:47
lsst.pipe.base.connectionTypes
Definition: connectionTypes.py:1
lsst.pipe.base.cmdLineTask.CmdLineTask
Definition: cmdLineTask.py:492