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