lsst.ip.diffim  19.0.0-11-g10f13ba+2
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.afw.image as afwImage
26 import lsst.geom as geom
27 import lsst.pex.config as pexConfig
28 import lsst.pipe.base as pipeBase
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 = geom.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 = geom.Box2D()
106  for skyPos in skyCorners:
107  coaddBBox.include(coaddWcs.skyToPixel(skyPos))
108  coaddBBox = geom.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  coaddPhotoCalib = None
119  for patchInfo in patchList:
120  patchSubBBox = patchInfo.getOuterBBox()
121  patchSubBBox.clip(coaddBBox)
122  patchArgDict = dict(
123  datasetType=self.getCoaddDatasetName() + "_sub",
124  bbox=patchSubBBox,
125  tract=tractInfo.getId(),
126  patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
127  numSubfilters=self.config.numSubfilters,
128  )
129  if patchSubBBox.isEmpty():
130  self.log.info("skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels" % patchArgDict)
131  continue
132 
133  if self.config.coaddName == 'dcr':
134  if not sensorRef.datasetExists(subfilter=0, **patchArgDict):
135  self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s,"
136  " numSubfilters=%(numSubfilters)s, subfilter=0 does not exist"
137  % patchArgDict)
138  continue
139  patchInnerBBox = patchInfo.getInnerBBox()
140  patchInnerBBox.clip(coaddBBox)
141  if np.min(patchInnerBBox.getDimensions()) <= 2*self.config.templateBorderSize:
142  self.log.info("skip tract=%(tract)s, patch=%(patch)s; too few pixels." % patchArgDict)
143  continue
144  self.log.info("Constructing DCR-matched template for patch %s" % patchArgDict)
145 
146  dcrModel = DcrModel.fromDataRef(sensorRef, **patchArgDict)
147  # The edge pixels of the DcrCoadd may contain artifacts due to missing data.
148  # Each patch has significant overlap, and the contaminated edge pixels in
149  # a new patch will overwrite good pixels in the overlap region from
150  # previous patches.
151  # Shrink the BBox to remove the contaminated pixels,
152  # but make sure it is only the overlap region that is reduced.
153  dcrBBox = geom.Box2I(patchSubBBox)
154  dcrBBox.grow(-self.config.templateBorderSize)
155  dcrBBox.include(patchInnerBBox)
156  coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox,
157  wcs=coaddWcs,
158  visitInfo=exposure.getInfo().getVisitInfo())
159  else:
160  if not sensorRef.datasetExists(**patchArgDict):
161  self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist"
162  % patchArgDict)
163  continue
164  self.log.info("Reading patch %s" % patchArgDict)
165  coaddPatch = sensorRef.get(**patchArgDict)
166  nPatchesFound += 1
167  coaddExposure.maskedImage.assign(coaddPatch.maskedImage, coaddPatch.getBBox())
168  if coaddFilter is None:
169  coaddFilter = coaddPatch.getFilter()
170 
171  # Retrieve the PSF for this coadd tract, if not already retrieved
172  if coaddPsf is None and coaddPatch.hasPsf():
173  coaddPsf = coaddPatch.getPsf()
174 
175  # Retrieve the calibration for this coadd tract, if not already retrieved
176  if coaddPhotoCalib is None:
177  coaddPhotoCalib = coaddPatch.getPhotoCalib()
178 
179  if nPatchesFound == 0:
180  raise RuntimeError("No patches found!")
181 
182  if coaddPsf is None:
183  raise RuntimeError("No coadd Psf found!")
184 
185  if coaddPhotoCalib is None:
186  raise RuntimeError("No coadd PhotoCalib found!")
187 
188  coaddExposure.setPhotoCalib(coaddPhotoCalib)
189  coaddExposure.setPsf(coaddPsf)
190  coaddExposure.setFilter(coaddFilter)
191  return pipeBase.Struct(exposure=coaddExposure,
192  sources=None)
193 
195  """Return coadd name for given task config
196 
197  Returns
198  -------
199  CoaddDatasetName : `string`
200 
201  TODO: This nearly duplicates a method in CoaddBaseTask (DM-11985)
202  """
203  warpType = self.config.warpType
204  suffix = "" if warpType == "direct" else warpType[0].upper() + warpType[1:]
205  return self.config.coaddName + "Coadd" + suffix
206 
207 
208 class GetCalexpAsTemplateConfig(pexConfig.Config):
209  doAddCalexpBackground = pexConfig.Field(
210  dtype=bool,
211  default=True,
212  doc="Add background to calexp before processing it."
213  )
214 
215 
216 class GetCalexpAsTemplateTask(pipeBase.Task):
217  """Subtask to retrieve calexp of the same ccd number as the science image SensorRef
218  for use as an image difference template.
219 
220  To be run as a subtask by pipe.tasks.ImageDifferenceTask.
221  Intended for use with simulations and surveys that repeatedly visit the same pointing.
222  This code was originally part of Winter2013ImageDifferenceTask.
223  """
224 
225  ConfigClass = GetCalexpAsTemplateConfig
226  _DefaultName = "GetCalexpAsTemplateTask"
227 
228  def run(self, exposure, sensorRef, templateIdList):
229  """Return a calexp exposure with based on input sensorRef.
230 
231  Construct a dataId based on the sensorRef.dataId combined
232  with the specifications from the first dataId in templateIdList
233 
234  Parameters
235  ----------
236  exposure : `lsst.afw.image.Exposure`
237  exposure (unused)
238  sensorRef : `list` of `lsst.daf.persistence.ButlerDataRef`
239  Data reference of the calexp(s) to subtract from.
240  templateIdList : `list` of `lsst.daf.persistence.ButlerDataRef`
241  Data reference of the template calexp to be subtraced.
242  Can be incomplete, fields are initialized from `sensorRef`.
243  If there are multiple items, only the first one is used.
244 
245  Returns
246  -------
247  result : `struct`
248 
249  return a pipeBase.Struct:
250 
251  - ``exposure`` : a template calexp
252  - ``sources`` : source catalog measured on the template
253  """
254 
255  if len(templateIdList) == 0:
256  raise RuntimeError("No template data reference supplied.")
257  if len(templateIdList) > 1:
258  self.log.warn("Multiple template data references supplied. Using the first one only.")
259 
260  templateId = sensorRef.dataId.copy()
261  templateId.update(templateIdList[0])
262 
263  self.log.info("Fetching calexp (%s) as template." % (templateId))
264 
265  butler = sensorRef.getButler()
266  template = butler.get(datasetType="calexp", dataId=templateId)
267  if self.config.doAddCalexpBackground:
268  templateBg = butler.get(datasetType="calexpBackground", dataId=templateId)
269  mi = template.getMaskedImage()
270  mi += templateBg.getImage()
271 
272  if not template.hasPsf():
273  raise pipeBase.TaskError("Template has no psf")
274 
275  templateSources = butler.get(datasetType="src", dataId=templateId)
276  return pipeBase.Struct(exposure=template,
277  sources=templateSources)
def run(self, exposure, sensorRef, templateIdList)
Definition: getTemplate.py:228
def run(self, exposure, sensorRef, templateIdList=None)
Definition: getTemplate.py:69