LSSTApplications  18.1.0
LSSTDataManagementBasePackage
getTemplate.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2016 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 
23 import numpy as np
24 
25 import lsst.pex.config as pexConfig
26 import lsst.pipe.base as pipeBase
27 import lsst.afw.geom as afwGeom
28 import lsst.afw.image as afwImage
29 from lsst.ip.diffim.dcrModel import DcrModel
30 
31 __all__ = ["GetCoaddAsTemplateTask", "GetCoaddAsTemplateConfig",
32  "GetCalexpAsTemplateTask", "GetCalexpAsTemplateConfig"]
33 
34 
35 class GetCoaddAsTemplateConfig(pexConfig.Config):
36  templateBorderSize = pexConfig.Field(
37  dtype=int,
38  default=10,
39  doc="Number of pixels to grow the requested template image to account for warping"
40  )
41  coaddName = pexConfig.Field(
42  doc="coadd name: typically one of 'deep', 'goodSeeing', or 'dcr'",
43  dtype=str,
44  default="deep",
45  )
46  numSubfilters = pexConfig.Field(
47  doc="Number of subfilters in the DcrCoadd, used only if ``coaddName``='dcr'",
48  dtype=int,
49  default=3,
50  )
51  warpType = pexConfig.Field(
52  doc="Warp type of the coadd template: one of 'direct' or 'psfMatched'",
53  dtype=str,
54  default="direct",
55  )
56 
57 
58 class GetCoaddAsTemplateTask(pipeBase.Task):
59  """Subtask to retrieve coadd for use as an image difference template.
60 
61  This is the default getTemplate Task to be run as a subtask by
62  ``pipe.tasks.ImageDifferenceTask``. The main method is ``run()``.
63  It assumes that coadds reside in the repository given by sensorRef.
64  """
65 
66  ConfigClass = GetCoaddAsTemplateConfig
67  _DefaultName = "GetCoaddAsTemplateTask"
68 
69  def run(self, exposure, sensorRef, templateIdList=None):
70  """Retrieve and mosaic a template coadd exposure that overlaps the exposure
71 
72  Parameters
73  ----------
74  exposure: `lsst.afw.image.Exposure`
75  an exposure for which to generate an overlapping template
76  sensorRef : TYPE
77  a Butler data reference that can be used to obtain coadd data
78  templateIdList : TYPE, optional
79  list of data ids (unused)
80 
81  Returns
82  -------
83  result : `struct`
84  return a pipeBase.Struct:
85 
86  - ``exposure`` : a template coadd exposure assembled out of patches
87  - ``sources`` : None for this subtask
88  """
89  skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap")
90  expWcs = exposure.getWcs()
91  expBoxD = afwGeom.Box2D(exposure.getBBox())
92  expBoxD.grow(self.config.templateBorderSize)
93  ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())
94  tractInfo = skyMap.findTract(ctrSkyPos)
95  self.log.info("Using skyMap tract %s" % (tractInfo.getId(),))
96  skyCorners = [expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners()]
97  patchList = tractInfo.findPatchList(skyCorners)
98 
99  if not patchList:
100  raise RuntimeError("No suitable tract found")
101  self.log.info("Assembling %s coadd patches" % (len(patchList),))
102 
103  # compute coadd bbox
104  coaddWcs = tractInfo.getWcs()
105  coaddBBox = afwGeom.Box2D()
106  for skyPos in skyCorners:
107  coaddBBox.include(coaddWcs.skyToPixel(skyPos))
108  coaddBBox = afwGeom.Box2I(coaddBBox)
109  self.log.info("exposure dimensions=%s; coadd dimensions=%s" %
110  (exposure.getDimensions(), coaddBBox.getDimensions()))
111 
112  # assemble coadd exposure from subregions of patches
113  coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
114  coaddExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan)
115  nPatchesFound = 0
116  coaddFilter = None
117  coaddPsf = None
118  for patchInfo in patchList:
119  patchSubBBox = patchInfo.getOuterBBox()
120  patchSubBBox.clip(coaddBBox)
121  patchArgDict = dict(
122  datasetType=self.getCoaddDatasetName() + "_sub",
123  bbox=patchSubBBox,
124  tract=tractInfo.getId(),
125  patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
126  numSubfilters=self.config.numSubfilters,
127  )
128  if patchSubBBox.isEmpty():
129  self.log.info("skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels" % patchArgDict)
130  continue
131 
132  if self.config.coaddName == 'dcr':
133  if not sensorRef.datasetExists(subfilter=0, **patchArgDict):
134  self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s,"
135  " numSubfilters=%(numSubfilters)s, subfilter=0 does not exist"
136  % patchArgDict)
137  continue
138  self.log.info("Constructing DCR-matched template for patch %s" % patchArgDict)
139  dcrModel = DcrModel.fromDataRef(sensorRef, **patchArgDict)
140  # The edge pixels of the DcrCoadd may contain artifacts due to missing data.
141  # Each patch has significant overlap, and the contaminated edge pixels in
142  # a new patch will overwrite good pixels in the overlap region from
143  # previous patches.
144  # Shrink the BBox to remove the contaminated pixels,
145  # but make sure it is only the overlap region that is reduced.
146  patchInnerBBox = patchInfo.getInnerBBox()
147  patchInnerBBox.clip(coaddBBox)
148  dcrBBox = afwGeom.Box2I(patchSubBBox)
149  dcrBBox.grow(-self.config.templateBorderSize)
150  dcrBBox.include(patchInnerBBox)
151  coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox,
152  wcs=coaddWcs,
153  visitInfo=exposure.getInfo().getVisitInfo())
154  else:
155  if not sensorRef.datasetExists(**patchArgDict):
156  self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist"
157  % patchArgDict)
158  continue
159  self.log.info("Reading patch %s" % patchArgDict)
160  coaddPatch = sensorRef.get(**patchArgDict)
161  nPatchesFound += 1
162  coaddExposure.maskedImage.assign(coaddPatch.maskedImage, coaddPatch.getBBox())
163  if coaddFilter is None:
164  coaddFilter = coaddPatch.getFilter()
165 
166  # Retrieve the PSF for this coadd tract, if not already retrieved
167  if coaddPsf is None and coaddPatch.hasPsf():
168  coaddPsf = coaddPatch.getPsf()
169 
170  if nPatchesFound == 0:
171  raise RuntimeError("No patches found!")
172 
173  if coaddPsf is None:
174  raise RuntimeError("No coadd Psf found!")
175 
176  coaddExposure.setPsf(coaddPsf)
177  coaddExposure.setFilter(coaddFilter)
178  return pipeBase.Struct(exposure=coaddExposure,
179  sources=None)
180 
182  """Return coadd name for given task config
183 
184  Returns
185  -------
186  CoaddDatasetName : `string`
187 
188  TODO: This nearly duplicates a method in CoaddBaseTask (DM-11985)
189  """
190  warpType = self.config.warpType
191  suffix = "" if warpType == "direct" else warpType[0].upper() + warpType[1:]
192  return self.config.coaddName + "Coadd" + suffix
193 
194 
195 class GetCalexpAsTemplateConfig(pexConfig.Config):
196  doAddCalexpBackground = pexConfig.Field(
197  dtype=bool,
198  default=True,
199  doc="Add background to calexp before processing it."
200  )
201 
202 
203 class GetCalexpAsTemplateTask(pipeBase.Task):
204  """Subtask to retrieve calexp of the same ccd number as the science image SensorRef
205  for use as an image difference template.
206 
207  To be run as a subtask by pipe.tasks.ImageDifferenceTask.
208  Intended for use with simulations and surveys that repeatedly visit the same pointing.
209  This code was originally part of Winter2013ImageDifferenceTask.
210  """
211 
212  ConfigClass = GetCalexpAsTemplateConfig
213  _DefaultName = "GetCalexpAsTemplateTask"
214 
215  def run(self, exposure, sensorRef, templateIdList):
216  """Return a calexp exposure with based on input sensorRef.
217 
218  Construct a dataId based on the sensorRef.dataId combined
219  with the specifications from the first dataId in templateIdList
220 
221  Parameters
222  ----------
223  exposure : `lsst.afw.image.Exposure`
224  exposure (unused)
225  sensorRef : `list` of `lsst.daf.persistence.ButlerDataRef`
226  Data reference of the calexp(s) to subtract from.
227  templateIdList : `list` of `lsst.daf.persistence.ButlerDataRef`
228  Data reference of the template calexp to be subtraced.
229  Can be incomplete, fields are initialized from `sensorRef`.
230  If there are multiple items, only the first one is used.
231 
232  Returns
233  -------
234  result : `struct`
235 
236  return a pipeBase.Struct:
237 
238  - ``exposure`` : a template calexp
239  - ``sources`` : source catalog measured on the template
240  """
241 
242  if len(templateIdList) == 0:
243  raise RuntimeError("No template data reference supplied.")
244  if len(templateIdList) > 1:
245  self.log.warn("Multiple template data references supplied. Using the first one only.")
246 
247  templateId = sensorRef.dataId.copy()
248  templateId.update(templateIdList[0])
249 
250  self.log.info("Fetching calexp (%s) as template." % (templateId))
251 
252  butler = sensorRef.getButler()
253  template = butler.get(datasetType="calexp", dataId=templateId)
254  if self.config.doAddCalexpBackground:
255  templateBg = butler.get(datasetType="calexpBackground", dataId=templateId)
256  mi = template.getMaskedImage()
257  mi += templateBg.getImage()
258 
259  if not template.hasPsf():
260  raise pipeBase.TaskError("Template has no psf")
261 
262  templateSources = butler.get(datasetType="src", dataId=templateId)
263  return pipeBase.Struct(exposure=template,
264  sources=templateSources)
A floating-point coordinate rectangle geometry.
Definition: Box.h:305
def run(self, exposure, sensorRef, templateIdList)
Definition: getTemplate.py:215
def run(self, exposure, sensorRef, templateIdList=None)
Definition: getTemplate.py:69
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
An integer coordinate rectangle.
Definition: Box.h:54