LSSTApplications  19.0.0-14-gb0260a2+72efe9b372,20.0.0+7927753e06,20.0.0+8829bf0056,20.0.0+995114c5d2,20.0.0+b6f4b2abd1,20.0.0+bddc4f4cbe,20.0.0-1-g253301a+8829bf0056,20.0.0-1-g2b7511a+0d71a2d77f,20.0.0-1-g5b95a8c+7461dd0434,20.0.0-12-g321c96ea+23efe4bbff,20.0.0-16-gfab17e72e+fdf35455f6,20.0.0-2-g0070d88+ba3ffc8f0b,20.0.0-2-g4dae9ad+ee58a624b3,20.0.0-2-g61b8584+5d3db074ba,20.0.0-2-gb780d76+d529cf1a41,20.0.0-2-ged6426c+226a441f5f,20.0.0-2-gf072044+8829bf0056,20.0.0-2-gf1f7952+ee58a624b3,20.0.0-20-geae50cf+e37fec0aee,20.0.0-25-g3dcad98+544a109665,20.0.0-25-g5eafb0f+ee58a624b3,20.0.0-27-g64178ef+f1f297b00a,20.0.0-3-g4cc78c6+e0676b0dc8,20.0.0-3-g8f21e14+4fd2c12c9a,20.0.0-3-gbd60e8c+187b78b4b8,20.0.0-3-gbecbe05+48431fa087,20.0.0-38-ge4adf513+a12e1f8e37,20.0.0-4-g97dc21a+544a109665,20.0.0-4-gb4befbc+087873070b,20.0.0-4-gf910f65+5d3db074ba,20.0.0-5-gdfe0fee+199202a608,20.0.0-5-gfbfe500+d529cf1a41,20.0.0-6-g64f541c+d529cf1a41,20.0.0-6-g9a5b7a1+a1cd37312e,20.0.0-68-ga3f3dda+5fca18c6a4,20.0.0-9-g4aef684+e18322736b,w.2020.45
LSSTDataManagementBasePackage
makeCoaddTempExp.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import numpy
23 
24 import lsst.pex.config as pexConfig
25 import lsst.daf.persistence as dafPersist
26 import lsst.afw.image as afwImage
27 import lsst.coadd.utils as coaddUtils
28 import lsst.pipe.base as pipeBase
30 import lsst.log as log
31 import lsst.utils as utils
32 from lsst.meas.algorithms import CoaddPsf, CoaddPsfConfig
33 from .coaddBase import CoaddBaseTask, makeSkyInfo
34 from .warpAndPsfMatch import WarpAndPsfMatchTask
35 from .coaddHelpers import groupPatchExposures, getGroupDataRef
36 
37 __all__ = ["MakeCoaddTempExpTask", "MakeWarpTask", "MakeWarpConfig"]
38 
39 
40 class MissingExposureError(Exception):
41  """Raised when data cannot be retrieved for an exposure.
42  When processing patches, sometimes one exposure is missing; this lets us
43  distinguish bewteen that case, and other errors.
44  """
45  pass
46 
47 
48 class MakeCoaddTempExpConfig(CoaddBaseTask.ConfigClass):
49  """Config for MakeCoaddTempExpTask
50  """
51  warpAndPsfMatch = pexConfig.ConfigurableField(
52  target=WarpAndPsfMatchTask,
53  doc="Task to warp and PSF-match calexp",
54  )
55  doWrite = pexConfig.Field(
56  doc="persist <coaddName>Coadd_<warpType>Warp",
57  dtype=bool,
58  default=True,
59  )
60  bgSubtracted = pexConfig.Field(
61  doc="Work with a background subtracted calexp?",
62  dtype=bool,
63  default=True,
64  )
65  coaddPsf = pexConfig.ConfigField(
66  doc="Configuration for CoaddPsf",
67  dtype=CoaddPsfConfig,
68  )
69  makeDirect = pexConfig.Field(
70  doc="Make direct Warp/Coadds",
71  dtype=bool,
72  default=True,
73  )
74  makePsfMatched = pexConfig.Field(
75  doc="Make Psf-Matched Warp/Coadd?",
76  dtype=bool,
77  default=False,
78  )
79 
80  doWriteEmptyWarps = pexConfig.Field(
81  dtype=bool,
82  default=False,
83  doc="Write out warps even if they are empty"
84  )
85 
86  hasFakes = pexConfig.Field(
87  doc="Should be set to True if fake sources have been inserted into the input data.",
88  dtype=bool,
89  default=False,
90  )
91  doApplySkyCorr = pexConfig.Field(dtype=bool, default=False, doc="Apply sky correction?")
92 
93  def validate(self):
94  CoaddBaseTask.ConfigClass.validate(self)
95  if not self.makePsfMatched and not self.makeDirect:
96  raise RuntimeError("At least one of config.makePsfMatched and config.makeDirect must be True")
97  if self.doPsfMatch:
98  # Backwards compatibility.
99  log.warn("Config doPsfMatch deprecated. Setting makePsfMatched=True and makeDirect=False")
100  self.makePsfMatched = True
101  self.makeDirect = False
102 
103  def setDefaults(self):
104  CoaddBaseTask.ConfigClass.setDefaults(self)
105  self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize = self.matchingKernelSize
106 
107 
113 
114 
116  r"""!Warp and optionally PSF-Match calexps onto an a common projection.
117 
118  @anchor MakeCoaddTempExpTask_
119 
120  @section pipe_tasks_makeCoaddTempExp_Contents Contents
121 
122  - @ref pipe_tasks_makeCoaddTempExp_Purpose
123  - @ref pipe_tasks_makeCoaddTempExp_Initialize
124  - @ref pipe_tasks_makeCoaddTempExp_IO
125  - @ref pipe_tasks_makeCoaddTempExp_Config
126  - @ref pipe_tasks_makeCoaddTempExp_Debug
127  - @ref pipe_tasks_makeCoaddTempExp_Example
128 
129  @section pipe_tasks_makeCoaddTempExp_Purpose Description
130 
131  Warp and optionally PSF-Match calexps onto a common projection, by
132  performing the following operations:
133  - Group calexps by visit/run
134  - For each visit, generate a Warp by calling method @ref makeTempExp.
135  makeTempExp loops over the visit's calexps calling @ref WarpAndPsfMatch
136  on each visit
137 
138  The result is a `directWarp` (and/or optionally a `psfMatchedWarp`).
139 
140  @section pipe_tasks_makeCoaddTempExp_Initialize Task Initialization
141 
142  @copydoc \_\_init\_\_
143 
144  This task has one special keyword argument: passing reuse=True will cause
145  the task to skip the creation of warps that are already present in the
146  output repositories.
147 
148  @section pipe_tasks_makeCoaddTempExp_IO Invoking the Task
149 
150  This task is primarily designed to be run from the command line.
151 
152  The main method is `runDataRef`, which takes a single butler data reference for the patch(es)
153  to process.
154 
155  @copydoc run
156 
157  WarpType identifies the types of convolutions applied to Warps (previously CoaddTempExps).
158  Only two types are available: direct (for regular Warps/Coadds) and psfMatched
159  (for Warps/Coadds with homogenized PSFs). We expect to add a third type, likelihood,
160  for generating likelihood Coadds with Warps that have been correlated with their own PSF.
161 
162  @section pipe_tasks_makeCoaddTempExp_Config Configuration parameters
163 
164  See @ref MakeCoaddTempExpConfig and parameters inherited from
165  @link lsst.pipe.tasks.coaddBase.CoaddBaseConfig CoaddBaseConfig @endlink
166 
167  @subsection pipe_tasks_MakeCoaddTempExp_psfMatching Guide to PSF-Matching Configs
168 
169  To make `psfMatchedWarps`, select `config.makePsfMatched=True`. The subtask
170  @link lsst.ip.diffim.modelPsfMatch.ModelPsfMatchTask ModelPsfMatchTask @endlink
171  is responsible for the PSF-Matching, and its config is accessed via `config.warpAndPsfMatch.psfMatch`.
172  The optimal configuration depends on aspects of dataset: the pixel scale, average PSF FWHM and
173  dimensions of the PSF kernel. These configs include the requested model PSF, the matching kernel size,
174  padding of the science PSF thumbnail and spatial sampling frequency of the PSF.
175 
176  *Config Guidelines*: The user must specify the size of the model PSF to which to match by setting
177  `config.modelPsf.defaultFwhm` in units of pixels. The appropriate values depends on science case.
178  In general, for a set of input images, this config should equal the FWHM of the visit
179  with the worst seeing. The smallest it should be set to is the median FWHM. The defaults
180  of the other config options offer a reasonable starting point.
181  The following list presents the most common problems that arise from a misconfigured
182  @link lsst.ip.diffim.modelPsfMatch.ModelPsfMatchTask ModelPsfMatchTask @endlink
183  and corresponding solutions. All assume the default Alard-Lupton kernel, with configs accessed via
184  ```config.warpAndPsfMatch.psfMatch.kernel['AL']```. Each item in the list is formatted as:
185  Problem: Explanation. *Solution*
186 
187  *Troublshooting PSF-Matching Configuration:*
188  - Matched PSFs look boxy: The matching kernel is too small. _Increase the matching kernel size.
189  For example:_
190 
191  config.warpAndPsfMatch.psfMatch.kernel['AL'].kernelSize=27 # default 21
192 
193  Note that increasing the kernel size also increases runtime.
194  - Matched PSFs look ugly (dipoles, quadropoles, donuts): unable to find good solution
195  for matching kernel. _Provide the matcher with more data by either increasing
196  the spatial sampling by decreasing the spatial cell size,_
197 
198  config.warpAndPsfMatch.psfMatch.kernel['AL'].sizeCellX = 64 # default 128
199  config.warpAndPsfMatch.psfMatch.kernel['AL'].sizeCellY = 64 # default 128
200 
201  _or increasing the padding around the Science PSF, for example:_
202 
203  config.warpAndPsfMatch.psfMatch.autoPadPsfTo=1.6 # default 1.4
204 
205  Increasing `autoPadPsfTo` increases the minimum ratio of input PSF dimensions to the
206  matching kernel dimensions, thus increasing the number of pixels available to fit
207  after convolving the PSF with the matching kernel.
208  Optionally, for debugging the effects of padding, the level of padding may be manually
209  controlled by setting turning off the automatic padding and setting the number
210  of pixels by which to pad the PSF:
211 
212  config.warpAndPsfMatch.psfMatch.doAutoPadPsf = False # default True
213  config.warpAndPsfMatch.psfMatch.padPsfBy = 6 # pixels. default 0
214 
215  - Deconvolution: Matching a large PSF to a smaller PSF produces
216  a telltale noise pattern which looks like ripples or a brain.
217  _Increase the size of the requested model PSF. For example:_
218 
219  config.modelPsf.defaultFwhm = 11 # Gaussian sigma in units of pixels.
220 
221  - High frequency (sometimes checkered) noise: The matching basis functions are too small.
222  _Increase the width of the Gaussian basis functions. For example:_
223 
224  config.warpAndPsfMatch.psfMatch.kernel['AL'].alardSigGauss=[1.5, 3.0, 6.0]
225  # from default [0.7, 1.5, 3.0]
226 
227 
228  @section pipe_tasks_makeCoaddTempExp_Debug Debug variables
229 
230  MakeCoaddTempExpTask has no debug output, but its subtasks do.
231 
232  @section pipe_tasks_makeCoaddTempExp_Example A complete example of using MakeCoaddTempExpTask
233 
234  This example uses the package ci_hsc to show how MakeCoaddTempExp fits
235  into the larger Data Release Processing.
236  Set up by running:
237 
238  setup ci_hsc
239  cd $CI_HSC_DIR
240  # if not built already:
241  python $(which scons) # this will take a while
242 
243  The following assumes that `processCcd.py` and `makeSkyMap.py` have previously been run
244  (e.g. by building `ci_hsc` above) to generate a repository of calexps and an
245  output respository with the desired SkyMap. The command,
246 
247  makeCoaddTempExp.py $CI_HSC_DIR/DATA --rerun ci_hsc \
248  --id patch=5,4 tract=0 filter=HSC-I \
249  --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 \
250  --selectId visit=903988 ccd=23 --selectId visit=903988 ccd=24 \
251  --config doApplyExternalPhotoCalib=False doApplyExternalSkyWcs=False \
252  makePsfMatched=True modelPsf.defaultFwhm=11
253 
254  writes a direct and PSF-Matched Warp to
255  - `$CI_HSC_DIR/DATA/rerun/ci_hsc/deepCoadd/HSC-I/0/5,4/warp-HSC-I-0-5,4-903988.fits` and
256  - `$CI_HSC_DIR/DATA/rerun/ci_hsc/deepCoadd/HSC-I/0/5,4/psfMatchedWarp-HSC-I-0-5,4-903988.fits`
257  respectively.
258 
259  @note PSF-Matching in this particular dataset would benefit from adding
260  `--configfile ./matchingConfig.py` to
261  the command line arguments where `matchingConfig.py` is defined by:
262 
263  echo "
264  config.warpAndPsfMatch.psfMatch.kernel['AL'].kernelSize=27
265  config.warpAndPsfMatch.psfMatch.kernel['AL'].alardSigGauss=[1.5, 3.0, 6.0]" > matchingConfig.py
266 
267 
268  Add the option `--help` to see more options.
269  """
270  ConfigClass = MakeCoaddTempExpConfig
271  _DefaultName = "makeCoaddTempExp"
272 
273  def __init__(self, reuse=False, **kwargs):
274  CoaddBaseTask.__init__(self, **kwargs)
275  self.reuse = reuse
276  self.makeSubtask("warpAndPsfMatch")
277  if self.config.hasFakes:
278  self.calexpType = "fakes_calexp"
279  else:
280  self.calexpType = "calexp"
281 
282  @pipeBase.timeMethod
283  def runDataRef(self, patchRef, selectDataList=[]):
284  """!Produce <coaddName>Coadd_<warpType>Warp images by warping and optionally PSF-matching.
285 
286  @param[in] patchRef: data reference for sky map patch. Must include keys "tract", "patch",
287  plus the camera-specific filter key (e.g. "filter" or "band")
288  @return: dataRefList: a list of data references for the new <coaddName>Coadd_directWarps
289  if direct or both warp types are requested and <coaddName>Coadd_psfMatchedWarps if only psfMatched
290  warps are requested.
291 
292  @warning: this task assumes that all exposures in a warp (coaddTempExp) have the same filter.
293 
294  @warning: this task sets the PhotoCalib of the coaddTempExp to the PhotoCalib of the first calexp
295  with any good pixels in the patch. For a mosaic camera the resulting PhotoCalib should be ignored
296  (assembleCoadd should determine zeropoint scaling without referring to it).
297  """
298  skyInfo = self.getSkyInfo(patchRef)
299 
300  # DataRefs to return are of type *_directWarp unless only *_psfMatchedWarp requested
301  if self.config.makePsfMatched and not self.config.makeDirect:
302  primaryWarpDataset = self.getTempExpDatasetName("psfMatched")
303  else:
304  primaryWarpDataset = self.getTempExpDatasetName("direct")
305 
306  calExpRefList = self.selectExposures(patchRef, skyInfo, selectDataList=selectDataList)
307 
308  if len(calExpRefList) == 0:
309  self.log.warn("No exposures to coadd for patch %s", patchRef.dataId)
310  return None
311  self.log.info("Selected %d calexps for patch %s", len(calExpRefList), patchRef.dataId)
312  calExpRefList = [calExpRef for calExpRef in calExpRefList if calExpRef.datasetExists(self.calexpType)]
313  self.log.info("Processing %d existing calexps for patch %s", len(calExpRefList), patchRef.dataId)
314 
315  groupData = groupPatchExposures(patchRef, calExpRefList, self.getCoaddDatasetName(),
316  primaryWarpDataset)
317  self.log.info("Processing %d warp exposures for patch %s", len(groupData.groups), patchRef.dataId)
318 
319  dataRefList = []
320  for i, (tempExpTuple, calexpRefList) in enumerate(groupData.groups.items()):
321  tempExpRef = getGroupDataRef(patchRef.getButler(), primaryWarpDataset,
322  tempExpTuple, groupData.keys)
323  if self.reuse and tempExpRef.datasetExists(datasetType=primaryWarpDataset, write=True):
324  self.log.info("Skipping makeCoaddTempExp for %s; output already exists.", tempExpRef.dataId)
325  dataRefList.append(tempExpRef)
326  continue
327  self.log.info("Processing Warp %d/%d: id=%s", i, len(groupData.groups), tempExpRef.dataId)
328 
329  # TODO: mappers should define a way to go from the "grouping keys" to a numeric ID (#2776).
330  # For now, we try to get a long integer "visit" key, and if we can't, we just use the index
331  # of the visit in the list.
332  try:
333  visitId = int(tempExpRef.dataId["visit"])
334  except (KeyError, ValueError):
335  visitId = i
336 
337  calExpList = []
338  ccdIdList = []
339  dataIdList = []
340 
341  for calExpInd, calExpRef in enumerate(calexpRefList):
342  self.log.info("Reading calexp %s of %s for Warp id=%s", calExpInd+1, len(calexpRefList),
343  calExpRef.dataId)
344  try:
345  ccdId = calExpRef.get("ccdExposureId", immediate=True)
346  except Exception:
347  ccdId = calExpInd
348  try:
349  # We augment the dataRef here with the tract, which is harmless for loading things
350  # like calexps that don't need the tract, and necessary for meas_mosaic outputs,
351  # which do.
352  calExpRef = calExpRef.butlerSubset.butler.dataRef(self.calexpType,
353  dataId=calExpRef.dataId,
354  tract=skyInfo.tractInfo.getId())
355  calExp = self.getCalibratedExposure(calExpRef, bgSubtracted=self.config.bgSubtracted)
356  except Exception as e:
357  self.log.warn("Calexp %s not found; skipping it: %s", calExpRef.dataId, e)
358  continue
359 
360  if self.config.doApplySkyCorr:
361  self.applySkyCorr(calExpRef, calExp)
362 
363  calExpList.append(calExp)
364  ccdIdList.append(ccdId)
365  dataIdList.append(calExpRef.dataId)
366 
367  exps = self.run(calExpList, ccdIdList, skyInfo, visitId, dataIdList).exposures
368 
369  if any(exps.values()):
370  dataRefList.append(tempExpRef)
371  else:
372  self.log.warn("Warp %s could not be created", tempExpRef.dataId)
373 
374  if self.config.doWrite:
375  for (warpType, exposure) in exps.items(): # compatible w/ Py3
376  if exposure is not None:
377  self.log.info("Persisting %s" % self.getTempExpDatasetName(warpType))
378  tempExpRef.put(exposure, self.getTempExpDatasetName(warpType))
379 
380  return dataRefList
381 
382  @pipeBase.timeMethod
383  def run(self, calExpList, ccdIdList, skyInfo, visitId=0, dataIdList=None):
384  """Create a Warp from inputs
385 
386  We iterate over the multiple calexps in a single exposure to construct
387  the warp (previously called a coaddTempExp) of that exposure to the
388  supplied tract/patch.
389 
390  Pixels that receive no pixels are set to NAN; this is not correct
391  (violates LSST algorithms group policy), but will be fixed up by
392  interpolating after the coaddition.
393 
394  @param calexpRefList: List of data references for calexps that (may)
395  overlap the patch of interest
396  @param skyInfo: Struct from CoaddBaseTask.getSkyInfo() with geometric
397  information about the patch
398  @param visitId: integer identifier for visit, for the table that will
399  produce the CoaddPsf
400  @return a pipeBase Struct containing:
401  - exposures: a dictionary containing the warps requested:
402  "direct": direct warp if config.makeDirect
403  "psfMatched": PSF-matched warp if config.makePsfMatched
404  """
405  warpTypeList = self.getWarpTypeList()
406 
407  totGoodPix = {warpType: 0 for warpType in warpTypeList}
408  didSetMetadata = {warpType: False for warpType in warpTypeList}
409  coaddTempExps = {warpType: self._prepareEmptyExposure(skyInfo) for warpType in warpTypeList}
410  inputRecorder = {warpType: self.inputRecorder.makeCoaddTempExpRecorder(visitId, len(calExpList))
411  for warpType in warpTypeList}
412 
413  modelPsf = self.config.modelPsf.apply() if self.config.makePsfMatched else None
414  if dataIdList is None:
415  dataIdList = ccdIdList
416 
417  for calExpInd, (calExp, ccdId, dataId) in enumerate(zip(calExpList, ccdIdList, dataIdList)):
418  self.log.info("Processing calexp %d of %d for this Warp: id=%s",
419  calExpInd+1, len(calExpList), dataId)
420 
421  try:
422  warpedAndMatched = self.warpAndPsfMatch.run(calExp, modelPsf=modelPsf,
423  wcs=skyInfo.wcs, maxBBox=skyInfo.bbox,
424  makeDirect=self.config.makeDirect,
425  makePsfMatched=self.config.makePsfMatched)
426  except Exception as e:
427  self.log.warn("WarpAndPsfMatch failed for calexp %s; skipping it: %s", dataId, e)
428  continue
429  try:
430  numGoodPix = {warpType: 0 for warpType in warpTypeList}
431  for warpType in warpTypeList:
432  exposure = warpedAndMatched.getDict()[warpType]
433  if exposure is None:
434  continue
435  coaddTempExp = coaddTempExps[warpType]
436  if didSetMetadata[warpType]:
437  mimg = exposure.getMaskedImage()
438  mimg *= (coaddTempExp.getPhotoCalib().getInstFluxAtZeroMagnitude()
439  / exposure.getPhotoCalib().getInstFluxAtZeroMagnitude())
440  del mimg
441  numGoodPix[warpType] = coaddUtils.copyGoodPixels(
442  coaddTempExp.getMaskedImage(), exposure.getMaskedImage(), self.getBadPixelMask())
443  totGoodPix[warpType] += numGoodPix[warpType]
444  self.log.debug("Calexp %s has %d good pixels in this patch (%.1f%%) for %s",
445  dataId, numGoodPix[warpType],
446  100.0*numGoodPix[warpType]/skyInfo.bbox.getArea(), warpType)
447  if numGoodPix[warpType] > 0 and not didSetMetadata[warpType]:
448  coaddTempExp.setPhotoCalib(exposure.getPhotoCalib())
449  coaddTempExp.setFilter(exposure.getFilter())
450  coaddTempExp.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
451  # PSF replaced with CoaddPsf after loop if and only if creating direct warp
452  coaddTempExp.setPsf(exposure.getPsf())
453  didSetMetadata[warpType] = True
454 
455  # Need inputRecorder for CoaddApCorrMap for both direct and PSF-matched
456  inputRecorder[warpType].addCalExp(calExp, ccdId, numGoodPix[warpType])
457 
458  except Exception as e:
459  self.log.warn("Error processing calexp %s; skipping it: %s", dataId, e)
460  continue
461 
462  for warpType in warpTypeList:
463  self.log.info("%sWarp has %d good pixels (%.1f%%)",
464  warpType, totGoodPix[warpType], 100.0*totGoodPix[warpType]/skyInfo.bbox.getArea())
465 
466  if totGoodPix[warpType] > 0 and didSetMetadata[warpType]:
467  inputRecorder[warpType].finish(coaddTempExps[warpType], totGoodPix[warpType])
468  if warpType == "direct":
469  coaddTempExps[warpType].setPsf(
470  CoaddPsf(inputRecorder[warpType].coaddInputs.ccds, skyInfo.wcs,
471  self.config.coaddPsf.makeControl()))
472  else:
473  if not self.config.doWriteEmptyWarps:
474  # No good pixels. Exposure still empty
475  coaddTempExps[warpType] = None
476 
477  result = pipeBase.Struct(exposures=coaddTempExps)
478  return result
479 
480  def getCalibratedExposure(self, dataRef, bgSubtracted):
481  """Return one calibrated Exposure, possibly with an updated SkyWcs.
482 
483  @param[in] dataRef a sensor-level data reference
484  @param[in] bgSubtracted return calexp with background subtracted? If False get the
485  calexp's background background model and add it to the calexp.
486  @return calibrated exposure
487 
488  @raises MissingExposureError If data for the exposure is not available.
489 
490  If config.doApplyExternalPhotoCalib is `True`, the photometric calibration
491  (`photoCalib`) is taken from `config.externalPhotoCalibName` via the
492  `name_photoCalib` dataset. Otherwise, the photometric calibration is
493  retrieved from the processed exposure. When
494  `config.doApplyExternalSkyWcs` is `True`, the astrometric calibration
495  is taken from `config.externalSkyWcsName` with the `name_wcs` dataset.
496  Otherwise, the astrometric calibration is taken from the processed
497  exposure.
498  """
499  try:
500  exposure = dataRef.get(self.calexpType, immediate=True)
501  except dafPersist.NoResults as e:
502  raise MissingExposureError('Exposure not found: %s ' % str(e)) from e
503 
504  if not bgSubtracted:
505  background = dataRef.get("calexpBackground", immediate=True)
506  mi = exposure.getMaskedImage()
507  mi += background.getImage()
508  del mi
509 
510  if self.config.doApplyExternalPhotoCalib:
511  source = f"{self.config.externalPhotoCalibName}_photoCalib"
512  self.log.debug("Applying external photoCalib to %s from %s", dataRef.dataId, source)
513  photoCalib = dataRef.get(source)
514  exposure.setPhotoCalib(photoCalib)
515  else:
516  photoCalib = exposure.getPhotoCalib()
517 
518  if self.config.doApplyExternalSkyWcs:
519  source = f"{self.config.externalSkyWcsName}_wcs"
520  self.log.debug("Applying external skyWcs to %s from %s", dataRef.dataId, source)
521  skyWcs = dataRef.get(source)
522  exposure.setWcs(skyWcs)
523 
524  exposure.maskedImage = photoCalib.calibrateImage(exposure.maskedImage,
525  includeScaleUncertainty=self.config.includeCalibVar)
526  exposure.maskedImage /= photoCalib.getCalibrationMean()
527  # TODO: The images will have a calibration of 1.0 everywhere once RFC-545 is implemented.
528  # exposure.setCalib(afwImage.Calib(1.0))
529  return exposure
530 
531  @staticmethod
532  def _prepareEmptyExposure(skyInfo):
533  """Produce an empty exposure for a given patch"""
534  exp = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
535  exp.getMaskedImage().set(numpy.nan, afwImage.Mask
536  .getPlaneBitMask("NO_DATA"), numpy.inf)
537  return exp
538 
539  def getWarpTypeList(self):
540  """Return list of requested warp types per the config.
541  """
542  warpTypeList = []
543  if self.config.makeDirect:
544  warpTypeList.append("direct")
545  if self.config.makePsfMatched:
546  warpTypeList.append("psfMatched")
547  return warpTypeList
548 
549  def applySkyCorr(self, dataRef, calexp):
550  """Apply correction to the sky background level
551 
552  Sky corrections can be generated with the 'skyCorrection.py'
553  executable in pipe_drivers. Because the sky model used by that
554  code extends over the entire focal plane, this can produce
555  better sky subtraction.
556 
557  The calexp is updated in-place.
558 
559  Parameters
560  ----------
561  dataRef : `lsst.daf.persistence.ButlerDataRef`
562  Data reference for calexp.
563  calexp : `lsst.afw.image.Exposure` or `lsst.afw.image.MaskedImage`
564  Calibrated exposure.
565  """
566  bg = dataRef.get("skyCorr")
567  self.log.debug("Applying sky correction to %s", dataRef.dataId)
568  if isinstance(calexp, afwImage.Exposure):
569  calexp = calexp.getMaskedImage()
570  calexp -= bg.getImage()
571 
572 
573 class MakeWarpConnections(pipeBase.PipelineTaskConnections,
574  dimensions=("tract", "patch", "skymap", "instrument", "visit"),
575  defaultTemplates={"coaddName": "deep"}):
576  calExpList = cT.Input(
577  doc="Input exposures to be resampled and optionally PSF-matched onto a SkyMap projection/patch",
578  name="calexp",
579  storageClass="ExposureF",
580  dimensions=("instrument", "visit", "detector"),
581  multiple=True,
582  )
583  backgroundList = cT.Input(
584  doc="Input backgrounds to be added back into the calexp if bgSubtracted=False",
585  name="calexpBackground",
586  storageClass="Background",
587  dimensions=("instrument", "visit", "detector"),
588  multiple=True,
589  )
590  skyCorrList = cT.Input(
591  doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
592  name="skyCorr",
593  storageClass="Background",
594  dimensions=("instrument", "visit", "detector"),
595  multiple=True,
596  )
597  skyMap = cT.Input(
598  doc="Input definition of geometry/bbox and projection/wcs for warped exposures",
599  name="{coaddName}Coadd_skyMap",
600  storageClass="SkyMap",
601  dimensions=("skymap",),
602  )
603  direct = cT.Output(
604  doc=("Output direct warped exposure (previously called CoaddTempExp), produced by resampling ",
605  "calexps onto the skyMap patch geometry."),
606  name="{coaddName}Coadd_directWarp",
607  storageClass="ExposureF",
608  dimensions=("tract", "patch", "skymap", "visit", "instrument"),
609  )
610  psfMatched = cT.Output(
611  doc=("Output PSF-Matched warped exposure (previously called CoaddTempExp), produced by resampling ",
612  "calexps onto the skyMap patch geometry and PSF-matching to a model PSF."),
613  name="{coaddName}Coadd_psfMatchedWarp",
614  storageClass="ExposureF",
615  dimensions=("tract", "patch", "skymap", "visit", "instrument"),
616  )
617 
618  def __init__(self, *, config=None):
619  super().__init__(config=config)
620  if config.bgSubtracted:
621  self.inputs.remove("backgroundList")
622  if not config.doApplySkyCorr:
623  self.inputs.remove("skyCorrList")
624  if not config.makeDirect:
625  self.outputs.remove("direct")
626  if not config.makePsfMatched:
627  self.outputs.remove("psfMatched")
628 
629 
630 class MakeWarpConfig(pipeBase.PipelineTaskConfig, MakeCoaddTempExpConfig,
631  pipelineConnections=MakeWarpConnections):
632 
633  def validate(self):
634  super().validate()
635  # TODO: Remove this constraint after DM-17062
636  if self.doApplyExternalPhotoCalib:
637  raise RuntimeError("Gen3 MakeWarpTask cannot apply external PhotoCalib results. "
638  "Please set doApplyExternalPhotoCalib=False.")
639  if self.doApplyExternalSkyWcs:
640  raise RuntimeError("Gen3 MakeWarpTask cannot apply external SkyWcs results. "
641  "Please set doApplyExternalSkyWcs=False.")
642 
643 
644 class MakeWarpTask(MakeCoaddTempExpTask, pipeBase.PipelineTask):
645  """Warp and optionally PSF-Match calexps onto an a common projection
646 
647  First Draft of a Gen3 compatible MakeWarpTask which
648  currently does not handle doApplyExternalPhotoCalib=True or
649  doApplyExternalSkyWcs=True.
650  """
651  ConfigClass = MakeWarpConfig
652  _DefaultName = "makeWarp"
653 
654  @utils.inheritDoc(pipeBase.PipelineTask)
655  def runQuantum(self, butlerQC, inputRefs, outputRefs):
656  """
657  Notes
658  ----
659  Construct warps for requested warp type for single epoch
660 
661  PipelineTask (Gen3) entry point to warp and optionally PSF-match
662  calexps. This method is analogous to `runDataRef`.
663  """
664  # Read in all inputs.
665  inputs = butlerQC.get(inputRefs)
666 
667  # Construct skyInfo expected by `run`. We remove the SkyMap itself
668  # from the dictionary so we can pass it as kwargs later.
669  skyMap = inputs.pop("skyMap")
670  quantumDataId = butlerQC.quantum.dataId
671  skyInfo = makeSkyInfo(skyMap, tractId=quantumDataId['tract'], patchId=quantumDataId['patch'])
672 
673  # Construct list of input DataIds expected by `run`
674  dataIdList = [ref.dataId for ref in inputRefs.calExpList]
675 
676  # Construct list of packed integer IDs expected by `run`
677  ccdIdList = [dataId.pack("visit_detector") for dataId in dataIdList]
678 
679  # Extract integer visitId requested by `run`
680  visits = [dataId['visit'] for dataId in dataIdList]
681  assert(all(visits[0] == visit for visit in visits))
682  visitId = visits[0]
683 
684  self.prepareCalibratedExposures(**inputs)
685  results = self.run(**inputs, visitId=visitId, ccdIdList=ccdIdList, dataIdList=dataIdList,
686  skyInfo=skyInfo)
687  if self.config.makeDirect:
688  butlerQC.put(results.exposures["direct"], outputRefs.direct)
689  if self.config.makePsfMatched:
690  butlerQC.put(results.exposures["psfMatched"], outputRefs.psfMatched)
691 
692  def prepareCalibratedExposures(self, calExpList, backgroundList=None, skyCorrList=None):
693  """Calibrate and add backgrounds to input calExpList in place
694 
695  TODO DM-17062: apply jointcal/meas_mosaic here
696 
697  Parameters
698  ----------
699  calExpList : `list` of `lsst.afw.image.Exposure`
700  Sequence of calexps to be modified in place
701  backgroundList : `list` of `lsst.afw.math.backgroundList`
702  Sequence of backgrounds to be added back in if bgSubtracted=False
703  skyCorrList : `list` of `lsst.afw.math.backgroundList`
704  Sequence of background corrections to be subtracted if doApplySkyCorr=True
705  """
706  backgroundList = len(calExpList)*[None] if backgroundList is None else backgroundList
707  skyCorrList = len(calExpList)*[None] if skyCorrList is None else skyCorrList
708  for calexp, background, skyCorr in zip(calExpList, backgroundList, skyCorrList):
709  mi = calexp.maskedImage
710  if not self.config.bgSubtracted:
711  mi += background.getImage()
712  if self.config.doApplySkyCorr:
713  mi -= skyCorr.getImage()
lsst::afw::image
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Definition: imageAlgorithm.dox:1
lsst::log.log.logContinued.warn
def warn(fmt, *args)
Definition: logContinued.py:205
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.__init__
def __init__(self, reuse=False, **kwargs)
Definition: makeCoaddTempExp.py:273
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpConfig.makePsfMatched
makePsfMatched
Definition: makeCoaddTempExp.py:74
lsst::log.log.logContinued.info
def info(fmt, *args)
Definition: logContinued.py:201
lsst::afw::image::Mask
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
lsst::afw::image::Exposure
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
lsst.pipe.tasks.coaddBase.CoaddBaseTask.selectExposures
def selectExposures(self, patchRef, skyInfo=None, selectDataList=[])
Select exposures to coadd.
Definition: coaddBase.py:144
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.runDataRef
def runDataRef(self, patchRef, selectDataList=[])
Produce <coaddName>Coadd_<warpType>Warp images by warping and optionally PSF-matching.
Definition: makeCoaddTempExp.py:283
lsst.gdb.afw.printers.debug
bool debug
Definition: printers.py:9
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.getCalibratedExposure
def getCalibratedExposure(self, dataRef, bgSubtracted)
Definition: makeCoaddTempExp.py:480
lsst.pipe.tasks.coaddBase.CoaddBaseTask.getBadPixelMask
def getBadPixelMask(self)
Convenience method to provide the bitmask from the mask plane names.
Definition: coaddBase.py:229
lsst::coadd::utils::copyGoodPixels
int copyGoodPixels(lsst::afw::image::MaskedImage< ImagePixelT, lsst::afw::image::MaskPixel, lsst::afw::image::VariancePixel > &destImage, lsst::afw::image::MaskedImage< ImagePixelT, lsst::afw::image::MaskPixel, lsst::afw::image::VariancePixel > const &srcImage, lsst::afw::image::MaskPixel const badPixelMask)
copy good pixels from one masked image to another
Definition: copyGoodPixels.cc:126
lsst.pipe.tasks.coaddBase.makeSkyInfo
def makeSkyInfo(skyMap, tractId, patchId)
Definition: coaddBase.py:279
lsst.pipe.tasks.coaddBase.CoaddBaseTask.getCoaddDatasetName
def getCoaddDatasetName(self, warpType="direct")
Definition: coaddBase.py:180
lsst::geom::all
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
Definition: CoordinateExpr.h:81
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.run
def run(self, calExpList, ccdIdList, skyInfo, visitId=0, dataIdList=None)
Definition: makeCoaddTempExp.py:383
lsst.pex.config
Definition: __init__.py:1
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpConfig.warpAndPsfMatch
warpAndPsfMatch
Definition: makeCoaddTempExp.py:51
lsst.pipe.tasks.coaddBase.CoaddBaseTask.getSkyInfo
def getSkyInfo(self, patchRef)
Use getSkyinfo to return the skyMap, tract and patch information, wcs and the outer bbox of the patch...
Definition: coaddBase.py:164
lsst.pipe.tasks.coaddBase.CoaddBaseTask.getTempExpDatasetName
def getTempExpDatasetName(self, warpType="direct")
Definition: coaddBase.py:194
lsst::geom::any
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
Definition: CoordinateExpr.h:89
lsst::log
Definition: Log.h:706
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpConfig
Definition: makeCoaddTempExp.py:48
lsst::utils
Definition: Backtrace.h:29
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpConfig.validate
def validate(self)
Definition: makeCoaddTempExp.py:93
lsst.pipe.tasks.makeCoaddTempExp.MissingExposureError
Definition: makeCoaddTempExp.py:40
lsst.pipe.tasks.coaddHelpers.getGroupDataRef
def getGroupDataRef(butler, datasetType, groupTuple, keys)
Definition: coaddHelpers.py:99
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.applySkyCorr
def applySkyCorr(self, dataRef, calexp)
Definition: makeCoaddTempExp.py:549
lsst.pipe.tasks.makeCoaddTempExp.MakeWarpConnections
Definition: makeCoaddTempExp.py:575
lsst::meas::algorithms::CoaddPsf
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Definition: CoaddPsf.h:58
lsst.pipe.tasks.coaddBase.CoaddBaseTask
Base class for coaddition.
Definition: coaddBase.py:131
lsst::daf::persistence
Definition: Utils.h:50
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpConfig.setDefaults
def setDefaults(self)
Definition: makeCoaddTempExp.py:103
lsst.pipe.base
Definition: __init__.py:1
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpConfig.makeDirect
makeDirect
Definition: makeCoaddTempExp.py:69
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask
Warp and optionally PSF-Match calexps onto an a common projection.
Definition: makeCoaddTempExp.py:115
lsst::meas::algorithms
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
Definition: CoaddBoundedField.h:34
lsst.pex.config.wrap.validate
validate
Definition: wrap.py:295
lsst.pipe.tasks.coaddHelpers.groupPatchExposures
def groupPatchExposures(patchDataRef, calexpDataRefList, coaddDatasetType="deepCoadd", tempExpDatasetType="deepCoadd_directWarp")
Definition: coaddHelpers.py:59
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.calexpType
calexpType
Definition: makeCoaddTempExp.py:278
set
daf::base::PropertySet * set
Definition: fits.cc:912
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.getWarpTypeList
def getWarpTypeList(self)
Definition: makeCoaddTempExp.py:539
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask.reuse
reuse
Definition: makeCoaddTempExp.py:275
lsst::daf::persistence.butlerExceptions.NoResults
Definition: butlerExceptions.py:28
lsst.pipe.base.connectionTypes
Definition: connectionTypes.py:1
lsst::coadd::utils
Definition: addToCoadd.h:37
lsst.pipe.tasks.makeCoaddTempExp.MakeCoaddTempExpTask._prepareEmptyExposure
def _prepareEmptyExposure(skyInfo)
Definition: makeCoaddTempExp.py:532