LSSTApplications  18.1.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 .references import MultiBandReferencesTask
35 from .forcedMeasurement import ForcedMeasurementTask
36 from .applyApCorr import ApplyApCorrTask
37 from .catalogCalculation import CatalogCalculationTask
38 
39 __all__ = ("ForcedPhotImageConfig", "ForcedPhotImageTask")
40 
41 
43  """Config class for forced measurement driver task."""
44  # Gen 3 options
45  inputSchema = lsst.pipe.base.InitInputDatasetField(
46  doc="Schema for the input measurement catalogs.",
47  nameTemplate="{inputCoaddName}Coadd_ref_schema",
48  storageClass="SourceCatalog",
49  )
50  outputSchema = lsst.pipe.base.InitOutputDatasetField(
51  doc="Schema for the output forced measurement catalogs.",
52  nameTemplate="{outputCoaddName}Coadd_forced_src_schema",
53  storageClass="SourceCatalog",
54  )
55  exposure = lsst.pipe.base.InputDatasetField(
56  doc="Input exposure to perform photometry on.",
57  nameTemplate="{inputCoaddName}Coadd",
58  scalar=True,
59  storageClass="ExposureF",
60  dimensions=["abstract_filter", "skymap", "tract", "patch"],
61  )
62  refCat = lsst.pipe.base.InputDatasetField(
63  doc="Catalog of shapes and positions at which to force photometry.",
64  nameTemplate="{inputCoaddName}Coadd_ref",
65  scalar=True,
66  storageClass="SourceCatalog",
67  dimensions=["skymap", "tract", "patch"],
68  )
69  refWcs = lsst.pipe.base.InputDatasetField(
70  doc="Reference world coordinate system.",
71  nameTemplate="{inputCoaddName}Coadd",
72  scalar=True,
73  manualLoad=True,
74  storageClass="ExposureF",
75  dimensions=["abstract_filter", "skymap", "tract", "patch"],
76  )
77  measCat = lsst.pipe.base.OutputDatasetField(
78  doc="Output forced photometry catalog.",
79  nameTemplate="{outputCoaddName}Coadd_forced_src",
80  scalar=True,
81  storageClass="SourceCatalog",
82  dimensions=["abstract_filter", "skymap", "tract", "patch"],
83  )
84 
85  # ForcedPhotImage options
87  target=MultiBandReferencesTask,
88  doc="subtask to retrieve reference source catalog"
89  )
91  target=ForcedMeasurementTask,
92  doc="subtask to do forced measurement"
93  )
94  coaddName = lsst.pex.config.Field(
95  doc="coadd name: typically one of deep or goodSeeing",
96  dtype=str,
97  default="deep",
98  )
100  dtype=bool,
101  default=True,
102  doc="Run subtask to apply aperture corrections"
103  )
105  target=ApplyApCorrTask,
106  doc="Subtask to apply aperture corrections"
107  )
108  catalogCalculation = lsst.pex.config.ConfigurableField(
109  target=CatalogCalculationTask,
110  doc="Subtask to run catalogCalculation plugins on catalog"
111  )
112 
113  def setDefaults(self):
114  # Docstring inherited.
115  # Make catalogCalculation a no-op by default as no modelFlux is setup by default in
116  # ForcedMeasurementTask
117  super().setDefaults()
118 
119  self.catalogCalculation.plugins.names = []
120  self.formatTemplateNames({"inputCoaddName": "deep",
121  "outputCoaddName": "deep",
122  "inputName": None})
123  self.quantum.dimensions = ("abstract_filter", "skymap", "tract", "patch")
124 
125 
127  """A base class for command-line forced measurement drivers.
128 
129  Parameters
130  ----------
131  butler : `lsst.daf.persistence.butler.Butler`, optional
132  A Butler which will be passed to the references subtask to allow it to
133  load its schema from disk. Optional, but must be specified if
134  ``refSchema`` is not; if both are specified, ``refSchema`` takes
135  precedence.
136  refSchema : `lsst.afw.table.Schema`, optional
137  The schema of the reference catalog, passed to the constructor of the
138  references subtask. Optional, but must be specified if ``butler`` is
139  not; if both are specified, ``refSchema`` takes precedence.
140  **kwds
141  Keyword arguments are passed to the supertask constructor.
142 
143  Notes
144  -----
145  This is a an abstract class, which is the common ancestor for
146  `ForcedPhotCcdTask` and `ForcedPhotCoaddTask`. It provides the
147  `runDataRef` method that does most of the work, while delegating a few
148  customization tasks to other methods that are overridden by subclasses.
149 
150  This task is not directly usable as a command line task. Subclasses must:
151 
152  - Set the `_DefaultName` class attribute;
153  - Implement `makeIdFactory`;
154  - Implement `fetchReferences`;
155  - Optionally, implement `attachFootprints`.
156  """
157 
158  ConfigClass = ForcedPhotImageConfig
159  _DefaultName = "processImageForcedTask"
160 
161  def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
162  super().__init__(**kwds)
163 
164  if initInputs is not None:
165  refSchema = initInputs['inputSchema'].schema
166 
167  self.makeSubtask("references", butler=butler, schema=refSchema)
168  if refSchema is None:
169  refSchema = self.references.schema
170  self.makeSubtask("measurement", refSchema=refSchema)
171  # It is necessary to get the schema internal to the forced measurement task until such a time
172  # that the schema is not owned by the measurement task, but is passed in by an external caller
173  if self.config.doApCorr:
174  self.makeSubtask("applyApCorr", schema=self.measurement.schema)
175  self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
176 
178  return {"outputSchema": lsst.afw.table.SourceCatalog(self.measurement.schema)}
179 
180  def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
181  inputData['refWcs'] = butler.get(f"{self.config.refWcs.name}.wcs", inputDataIds["refWcs"])
182  inputData['measCat'] = self.generateMeasCat(inputDataIds['exposure'],
183  inputData['exposure'],
184  inputData['refCat'], inputData['refWcs'],
185  "tract_patch", butler)
186 
187  return self.run(**inputData)
188 
189  def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName, butler):
190  """Generate a measurement catalog for Gen3.
191 
192  Parameters
193  ----------
194  exposureDataId : `DataId`
195  Butler dataId for this exposure.
196  exposure : `lsst.afw.image.exposure.Exposure`
197  Exposure to generate the catalog for.
198  refCat : `lsst.afw.table.SourceCatalog`
199  Catalog of shapes and positions at which to force photometry.
200  refWcs : `lsst.afw.image.SkyWcs`
201  Reference world coordinate system.
202  idPackerName : `str`
203  Type of ID packer to construct from the registry.
204  butler : `lsst.daf.persistence.butler.Butler`
205  Butler to use to construct id packer.
206 
207  Returns
208  -------
209  measCat : `lsst.afw.table.SourceCatalog`
210  Catalog of forced sources to measure.
211  """
212  packer = butler.registry.makeDataIdPacker(idPackerName, exposureDataId)
213  expId = packer.pack(exposureDataId)
214  expBits = packer.maxBits
215  idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)
216 
217  measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
218  idFactory=idFactory)
219  return measCat
220 
221  def runDataRef(self, dataRef, psfCache=None):
222  """Perform forced measurement on a single exposure.
223 
224  Parameters
225  ----------
226  dataRef : `lsst.daf.persistence.ButlerDataRef`
227  Passed to the ``references`` subtask to obtain the reference WCS,
228  the ``getExposure`` method (implemented by derived classes) to
229  read the measurment image, and the ``fetchReferences`` method to
230  get the exposure and load the reference catalog (see
231  :lsst-task`lsst.meas.base.references.CoaddSrcReferencesTask`).
232  Refer to derived class documentation for details of the datasets
233  and data ID keys which are used.
234  psfCache : `int`, optional
235  Size of PSF cache, or `None`. The size of the PSF cache can have
236  a significant effect upon the runtime for complicated PSF models.
237 
238  Notes
239  -----
240  Sources are generated with ``generateMeasCat`` in the ``measurement``
241  subtask. These are passed to ``measurement``'s ``run`` method, which
242  fills the source catalog with the forced measurement results. The
243  sources are then passed to the ``writeOutputs`` method (implemented by
244  derived classes) which writes the outputs.
245  """
246  refWcs = self.references.getWcs(dataRef)
247  exposure = self.getExposure(dataRef)
248  if psfCache is not None:
249  exposure.getPsf().setCacheSize(psfCache)
250  refCat = self.fetchReferences(dataRef, exposure)
251 
252  measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
253  idFactory=self.makeIdFactory(dataRef))
254  self.log.info("Performing forced measurement on %s" % (dataRef.dataId,))
255  self.attachFootprints(measCat, refCat, exposure, refWcs, dataRef)
256 
257  exposureId = self.getExposureId(dataRef)
258 
259  forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
260 
261  self.writeOutput(dataRef, forcedPhotResult.measCat)
262 
263  def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
264  """Perform forced measurement on a single exposure.
265 
266  Parameters
267  ----------
268  measCat : `lsst.afw.table.SourceCatalog`
269  The measurement catalog, based on the sources listed in the
270  reference catalog.
271  exposure : `lsst.afw.image.Exposure`
272  The measurement image upon which to perform forced detection.
273  refCat : `lsst.afw.table.SourceCatalog`
274  The reference catalog of sources to measure.
275  refWcs : `lsst.afw.image.SkyWcs`
276  The WCS for the references.
277  exposureId : `int`
278  Optional unique exposureId used for random seed in measurement
279  task.
280 
281  Returns
282  -------
283  result : `lsst.pipe.base.Struct`
284  Structure with fields:
285 
286  ``measCat``
287  Catalog of forced measurement results
288  (`lsst.afw.table.SourceCatalog`).
289  """
290  self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
291  if self.config.doApCorr:
292  self.applyApCorr.run(
293  catalog=measCat,
294  apCorrMap=exposure.getInfo().getApCorrMap()
295  )
296  self.catalogCalculation.run(measCat)
297 
298  return lsst.pipe.base.Struct(measCat=measCat)
299 
300  def makeIdFactory(self, dataRef):
301  """Hook for derived classes to make an ID factory for forced sources.
302 
303  Notes
304  -----
305  That this applies to forced *source* IDs, not object IDs, which are
306  usually handled by the ``measurement.copyColumns`` config option.
307 
308  """
309  raise NotImplementedError()
310 
311  def getExposureId(self, dataRef):
312  raise NotImplementedError()
313 
314  def fetchReferences(self, dataRef, exposure):
315  """Hook for derived classes to define how to get reference objects.
316 
317  Notes
318  -----
319  Derived classes should call one of the ``fetch*`` methods on the
320  ``references`` subtask, but which one they call depends on whether the
321  region to get references for is a easy to describe in patches (as it
322  would be when doing forced measurements on a coadd), or is just an
323  arbitrary box (as it would be for CCD forced measurements).
324  """
325  raise NotImplementedError()
326 
327  def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef):
328  r"""Attach footprints to blank sources prior to measurements.
329 
330  Notes
331  -----
332  `~lsst.afw.detection.Footprint`\ s for forced photometry must be in the
333  pixel coordinate system of the image being measured, while the actual
334  detections may start out in a different coordinate system.
335 
336  Subclasses of this class must implement this method to define how
337  those `~lsst.afw.detection.Footprint`\ s should be generated.
338 
339  This default implementation transforms the
340  `~lsst.afw.detection.Footprint`\ s from the reference catalog from the
341  reference WCS to the exposure's WcS, which downgrades
342  `lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s into regular
343  `~lsst.afw.detection.Footprint`\ s, destroying deblend information.
344  """
345  return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
346 
347  def getExposure(self, dataRef):
348  """Read input exposure on which measurement will be performed.
349 
350  Parameters
351  ----------
352  dataRef : `lsst.daf.persistence.ButlerDataRef`
353  Butler data reference.
354  """
355  return dataRef.get(self.dataPrefix + "calexp", immediate=True)
356 
357  def writeOutput(self, dataRef, sources):
358  """Write forced source table
359 
360  Parameters
361  ----------
362  dataRef : `lsst.daf.persistence.ButlerDataRef`
363  Butler data reference. The forced_src dataset (with
364  self.dataPrefix prepended) is all that will be modified.
365  sources : `lsst.afw.table.SourceCatalog`
366  Catalog of sources to save.
367  """
368  dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
369 
370  def getSchemaCatalogs(self):
371  """The schema catalogs that will be used by this task.
372 
373  Returns
374  -------
375  schemaCatalogs : `dict`
376  Dictionary mapping dataset type to schema catalog.
377 
378  Notes
379  -----
380  There is only one schema for each type of forced measurement. The
381  dataset type for this measurement is defined in the mapper.
382  """
383  catalog = lsst.afw.table.SourceCatalog(self.measurement.schema)
384  catalog.getTable().setMetadata(self.measurement.algMetadata)
385  datasetType = self.dataPrefix + "forced_src"
386  return {datasetType: catalog}
387 
388  def _getConfigName(self):
389  # Documented in superclass
390  return self.dataPrefix + "forced_config"
391 
392  def _getMetadataName(self):
393  # Documented in superclass
394  return self.dataPrefix + "forced_metadata"
def makeSubtask(self, name, keyArgs)
Definition: task.py:275
def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)
def formatTemplateNames(self, templateParamsDict)
Definition: config.py:326
def run(self, measCat, exposure, refCat, refWcs, exposureId=None)
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
def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName, butler)
def __init__(self, butler=None, refSchema=None, initInputs=None, kwds)
def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef)