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