lsst.meas.base  18.1.0-3-g62b5e86+6
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
35 import lsst.pipe.base.connectionTypes as cT
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 
45 class 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  )
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", butlerQC.registry)
182  outputs = self.run(**inputs)
183  butlerQC.put(outputs, outputRefs)
184 
185  def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName, registry):
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  Type of ID packer to construct from the registry.
200  registry : `lsst.daf.persistence.butler.Registry`
201  Registry to use to construct id packer.
202 
203  Returns
204  -------
205  measCat : `lsst.afw.table.SourceCatalog`
206  Catalog of forced sources to measure.
207  """
208  packer = registry.makeDataIdPacker(idPackerName, exposureDataId)
209  expId = packer.pack(exposureDataId)
210  expBits = packer.maxBits
211  idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)
212 
213  measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
214  idFactory=idFactory)
215  return measCat
216 
217  def runDataRef(self, dataRef, psfCache=None):
218  """Perform forced measurement on a single exposure.
219 
220  Parameters
221  ----------
222  dataRef : `lsst.daf.persistence.ButlerDataRef`
223  Passed to the ``references`` subtask to obtain the reference WCS,
224  the ``getExposure`` method (implemented by derived classes) to
225  read the measurment image, and the ``fetchReferences`` method to
226  get the exposure and load the reference catalog (see
227  :lsst-task`lsst.meas.base.references.CoaddSrcReferencesTask`).
228  Refer to derived class documentation for details of the datasets
229  and data ID keys which are used.
230  psfCache : `int`, optional
231  Size of PSF cache, or `None`. The size of the PSF cache can have
232  a significant effect upon the runtime for complicated PSF models.
233 
234  Notes
235  -----
236  Sources are generated with ``generateMeasCat`` in the ``measurement``
237  subtask. These are passed to ``measurement``'s ``run`` method, which
238  fills the source catalog with the forced measurement results. The
239  sources are then passed to the ``writeOutputs`` method (implemented by
240  derived classes) which writes the outputs.
241  """
242  refWcs = self.references.getWcs(dataRef)
243  exposure = self.getExposure(dataRef)
244  if psfCache is not None:
245  exposure.getPsf().setCacheSize(psfCache)
246  refCat = self.fetchReferences(dataRef, exposure)
247 
248  measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
249  idFactory=self.makeIdFactory(dataRef))
250  self.log.info("Performing forced measurement on %s" % (dataRef.dataId,))
251  self.attachFootprints(measCat, refCat, exposure, refWcs, dataRef)
252 
253  exposureId = self.getExposureId(dataRef)
254 
255  forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
256 
257  self.writeOutput(dataRef, forcedPhotResult.measCat)
258 
259  def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
260  """Perform forced measurement on a single exposure.
261 
262  Parameters
263  ----------
264  measCat : `lsst.afw.table.SourceCatalog`
265  The measurement catalog, based on the sources listed in the
266  reference catalog.
267  exposure : `lsst.afw.image.Exposure`
268  The measurement image upon which to perform forced detection.
269  refCat : `lsst.afw.table.SourceCatalog`
270  The reference catalog of sources to measure.
271  refWcs : `lsst.afw.image.SkyWcs`
272  The WCS for the references.
273  exposureId : `int`
274  Optional unique exposureId used for random seed in measurement
275  task.
276 
277  Returns
278  -------
279  result : `lsst.pipe.base.Struct`
280  Structure with fields:
281 
282  ``measCat``
283  Catalog of forced measurement results
284  (`lsst.afw.table.SourceCatalog`).
285  """
286  self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
287  if self.config.doApCorr:
288  self.applyApCorr.run(
289  catalog=measCat,
290  apCorrMap=exposure.getInfo().getApCorrMap()
291  )
292  self.catalogCalculation.run(measCat)
293 
294  return lsst.pipe.base.Struct(measCat=measCat)
295 
296  def makeIdFactory(self, dataRef):
297  """Hook for derived classes to make an ID factory for forced sources.
298 
299  Notes
300  -----
301  That this applies to forced *source* IDs, not object IDs, which are
302  usually handled by the ``measurement.copyColumns`` config option.
303 
304  """
305  raise NotImplementedError()
306 
307  def getExposureId(self, dataRef):
308  raise NotImplementedError()
309 
310  def fetchReferences(self, dataRef, exposure):
311  """Hook for derived classes to define how to get reference objects.
312 
313  Notes
314  -----
315  Derived classes should call one of the ``fetch*`` methods on the
316  ``references`` subtask, but which one they call depends on whether the
317  region to get references for is a easy to describe in patches (as it
318  would be when doing forced measurements on a coadd), or is just an
319  arbitrary box (as it would be for CCD forced measurements).
320  """
321  raise NotImplementedError()
322 
323  def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef):
324  r"""Attach footprints to blank sources prior to measurements.
325 
326  Notes
327  -----
328  `~lsst.afw.detection.Footprint`\ s for forced photometry must be in the
329  pixel coordinate system of the image being measured, while the actual
330  detections may start out in a different coordinate system.
331 
332  Subclasses of this class must implement this method to define how
333  those `~lsst.afw.detection.Footprint`\ s should be generated.
334 
335  This default implementation transforms the
336  `~lsst.afw.detection.Footprint`\ s from the reference catalog from the
337  reference WCS to the exposure's WcS, which downgrades
338  `lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s into regular
339  `~lsst.afw.detection.Footprint`\ s, destroying deblend information.
340  """
341  return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
342 
343  def getExposure(self, dataRef):
344  """Read input exposure on which measurement will be performed.
345 
346  Parameters
347  ----------
348  dataRef : `lsst.daf.persistence.ButlerDataRef`
349  Butler data reference.
350  """
351  return dataRef.get(self.dataPrefix + "calexp", immediate=True)
352 
353  def writeOutput(self, dataRef, sources):
354  """Write forced source table
355 
356  Parameters
357  ----------
358  dataRef : `lsst.daf.persistence.ButlerDataRef`
359  Butler data reference. The forced_src dataset (with
360  self.dataPrefix prepended) is all that will be modified.
361  sources : `lsst.afw.table.SourceCatalog`
362  Catalog of sources to save.
363  """
364  dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
365 
366  def getSchemaCatalogs(self):
367  """The schema catalogs that will be used by this task.
368 
369  Returns
370  -------
371  schemaCatalogs : `dict`
372  Dictionary mapping dataset type to schema catalog.
373 
374  Notes
375  -----
376  There is only one schema for each type of forced measurement. The
377  dataset type for this measurement is defined in the mapper.
378  """
379  catalog = lsst.afw.table.SourceCatalog(self.measurement.schema)
380  catalog.getTable().setMetadata(self.measurement.algMetadata)
381  datasetType = self.dataPrefix + "forced_src"
382  return {datasetType: catalog}
383 
384  def _getConfigName(self):
385  # Documented in superclass
386  return self.dataPrefix + "forced_config"
387 
388  def _getMetadataName(self):
389  # Documented in superclass
390  return self.dataPrefix + "forced_metadata"
def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef)
def runDataRef(self, dataRef, psfCache=None)
def writeOutput(self, dataRef, sources)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
static std::shared_ptr< IdFactory > makeSource(RecordId expId, int reserved)
def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName, registry)
def run(self, measCat, exposure, refCat, refWcs, exposureId=None)
def fetchReferences(self, dataRef, exposure)