lsst.ip.diffim  14.0-11-g7664582+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 from __future__ import absolute_import, division, print_function
23 
24 import numpy as np
25 
26 import lsst.pex.config as pexConfig
27 import lsst.pipe.base as pipeBase
28 import lsst.afw.geom as afwGeom
29 import lsst.afw.image as afwImage
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 or goodSeeing",
43  dtype=str,
44  default="deep",
45  )
46  warpType = pexConfig.Field(
47  doc="Warp type of the coadd template: one of 'direct' or 'psfMatched'",
48  dtype=str,
49  default="direct",
50  )
51 
52 
53 class GetCoaddAsTemplateTask(pipeBase.Task):
54  """Subtask to retrieve coadd for use as an image difference template.
55 
56  This is the default getTemplate Task to be run as a subtask by
57  pipe.tasks.ImageDifferenceTask. The main method is run().
58  It assumes that coadds reside in the repository given by sensorRef.
59  """
60  ConfigClass = GetCoaddAsTemplateConfig
61  _DefaultName = "GetCoaddAsTemplateTask"
62 
63  def run(self, exposure, sensorRef, templateIdList=None):
64  """!Retrieve and mosaic a template coadd exposure that overlaps the exposure
65 
66  \param[in] exposure -- an exposure for which to generate an overlapping template
67  \param[in] sensorRef -- a Butler data reference that can be used to obtain coadd data
68  \param[in] templateIdList -- list of data ids (unused)
69 
70  \return a pipeBase.Struct
71  - exposure: a template coadd exposure assembled out of patches
72  - sources: None for this subtask
73  """
74  skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap")
75  expWcs = exposure.getWcs()
76  expBoxD = afwGeom.Box2D(exposure.getBBox())
77  expBoxD.grow(self.config.templateBorderSize)
78  ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())
79  tractInfo = skyMap.findTract(ctrSkyPos)
80  self.log.info("Using skyMap tract %s" % (tractInfo.getId(),))
81  skyCorners = [expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners()]
82  patchList = tractInfo.findPatchList(skyCorners)
83 
84  if not patchList:
85  raise RuntimeError("No suitable tract found")
86  self.log.info("Assembling %s coadd patches" % (len(patchList),))
87 
88  # compute coadd bbox
89  coaddWcs = tractInfo.getWcs()
90  coaddBBox = afwGeom.Box2D()
91  for skyPos in skyCorners:
92  coaddBBox.include(coaddWcs.skyToPixel(skyPos))
93  coaddBBox = afwGeom.Box2I(coaddBBox)
94  self.log.info("exposure dimensions=%s; coadd dimensions=%s" %
95  (exposure.getDimensions(), coaddBBox.getDimensions()))
96 
97  # assemble coadd exposure from subregions of patches
98  coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
99  coaddExposure.getMaskedImage().set(np.nan, afwImage.Mask\
100  .getPlaneBitMask("NO_DATA"), np.nan)
101  nPatchesFound = 0
102  coaddFilter = None
103  coaddPsf = None
104  for patchInfo in patchList:
105  patchSubBBox = patchInfo.getOuterBBox()
106  patchSubBBox.clip(coaddBBox)
107  patchArgDict = dict(
108  datasetType=self.getCoaddDatasetName() + "_sub",
109  bbox=patchSubBBox,
110  tract=tractInfo.getId(),
111  patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
112  )
113  if patchSubBBox.isEmpty():
114  self.log.info("skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels" % patchArgDict)
115  continue
116  if not sensorRef.datasetExists(**patchArgDict):
117  self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist"
118  % patchArgDict)
119  continue
120 
121  nPatchesFound += 1
122  self.log.info("Reading patch %s" % patchArgDict)
123  coaddPatch = sensorRef.get(**patchArgDict)
124  coaddExposure.getMaskedImage().assign(coaddPatch.getMaskedImage(), coaddPatch.getBBox())
125  if coaddFilter is None:
126  coaddFilter = coaddPatch.getFilter()
127 
128  # Retrieve the PSF for this coadd tract, if not already retrieved
129  if coaddPsf is None and coaddPatch.hasPsf():
130  coaddPsf = coaddPatch.getPsf()
131 
132  if nPatchesFound == 0:
133  raise RuntimeError("No patches found!")
134 
135  if coaddPsf is None:
136  raise RuntimeError("No coadd Psf found!")
137 
138  coaddExposure.setPsf(coaddPsf)
139  coaddExposure.setFilter(coaddFilter)
140  return pipeBase.Struct(exposure=coaddExposure,
141  sources=None)
142 
144  """Return coadd name for given task config
145 
146  Returns
147  -------
148  CoaddDatasetName : `string`
149 
150  TODO: This nearly duplicates a method in CoaddBaseTask (DM-11985)
151  """
152  warpType = self.config.warpType
153  suffix = "" if warpType == "direct" else warpType[0].upper() + warpType[1:]
154  return self.config.coaddName + "Coadd" + suffix
155 
156 
157 class GetCalexpAsTemplateConfig(pexConfig.Config):
158  doAddCalexpBackground = pexConfig.Field(
159  dtype=bool,
160  default=True,
161  doc="Add background to calexp before processing it."
162  )
163 
164 
165 class GetCalexpAsTemplateTask(pipeBase.Task):
166  """Subtask to retrieve calexp of the same ccd number as the science image SensorRef
167  for use as an image difference template.
168 
169  To be run as a subtask by pipe.tasks.ImageDifferenceTask.
170  Intended for use with simulations and surveys that repeatedly visit the same pointing.
171  This code was originally part of Winter2013ImageDifferenceTask.
172  """
173 
174  ConfigClass = GetCalexpAsTemplateConfig
175  _DefaultName = "GetCalexpAsTemplateTask"
176 
177  def run(self, exposure, sensorRef, templateIdList):
178  """!Return a calexp exposure with based on input sensorRef.
179 
180  Construct a dataId based on the sensorRef.dataId combined
181  with the specifications from the first dataId in templateIdList
182 
183  \param[in] exposure -- exposure (unused)
184  \param[in] sensorRef -- a Butler data reference
185  \param[in] templateIdList -- list of data ids, which should contain a single item.
186  If there are multiple items, only the first is used.
187 
188  \return a pipeBase.Struct
189  - exposure: a template calexp
190  - sources: source catalog measured on the template
191  """
192 
193  if len(templateIdList) == 0:
194  raise RuntimeError("No template supplied! Please supply a template visit id.")
195  if len(templateIdList) > 1:
196  self.log.warn("Multiple template visits supplied. Getting template from first visit: %s" %
197  (templateIdList[0]['visit']))
198 
199  templateId = sensorRef.dataId.copy()
200  templateId.update(templateIdList[0])
201 
202  self.log.info("Fetching calexp (%s) as template." % (templateId))
203 
204  butler = sensorRef.getButler()
205  template = butler.get(datasetType="calexp", dataId=templateId)
206  if self.config.doAddCalexpBackground:
207  templateBg = butler.get(datasetType="calexpBackground", dataId=templateId)
208  mi = template.getMaskedImage()
209  mi += templateBg.getImage()
210 
211  if not template.hasPsf():
212  raise pipeBase.TaskError("Template has no psf")
213 
214  templateSources = butler.get(datasetType="src", dataId=templateId)
215  return pipeBase.Struct(exposure=template,
216  sources=templateSources)
def run(self, exposure, sensorRef, templateIdList)
Return a calexp exposure with based on input sensorRef.
Definition: getTemplate.py:177
def run(self, exposure, sensorRef, templateIdList=None)
Retrieve and mosaic a template coadd exposure that overlaps the exposure.
Definition: getTemplate.py:63