LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
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.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  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  patchInnerBBox = patchInfo.getInnerBBox()
139  patchInnerBBox.clip(coaddBBox)
140  if np.min(patchInnerBBox.getDimensions()) <= 2*self.config.templateBorderSize:
141  self.log.info("skip tract=%(tract)s, patch=%(patch)s; too few pixels." % patchArgDict)
142  continue
143  self.log.info("Constructing DCR-matched template for patch %s" % patchArgDict)
144 
145  dcrModel = DcrModel.fromDataRef(sensorRef, **patchArgDict)
146  # The edge pixels of the DcrCoadd may contain artifacts due to missing data.
147  # Each patch has significant overlap, and the contaminated edge pixels in
148  # a new patch will overwrite good pixels in the overlap region from
149  # previous patches.
150  # Shrink the BBox to remove the contaminated pixels,
151  # but make sure it is only the overlap region that is reduced.
152  dcrBBox = geom.Box2I(patchSubBBox)
153  dcrBBox.grow(-self.config.templateBorderSize)
154  dcrBBox.include(patchInnerBBox)
155  coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox,
156  wcs=coaddWcs,
157  visitInfo=exposure.getInfo().getVisitInfo())
158  else:
159  if not sensorRef.datasetExists(**patchArgDict):
160  self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist"
161  % patchArgDict)
162  continue
163  self.log.info("Reading patch %s" % patchArgDict)
164  coaddPatch = sensorRef.get(**patchArgDict)
165  nPatchesFound += 1
166  coaddExposure.maskedImage.assign(coaddPatch.maskedImage, coaddPatch.getBBox())
167  if coaddFilter is None:
168  coaddFilter = coaddPatch.getFilter()
169 
170  # Retrieve the PSF for this coadd tract, if not already retrieved
171  if coaddPsf is None and coaddPatch.hasPsf():
172  coaddPsf = coaddPatch.getPsf()
173 
174  if nPatchesFound == 0:
175  raise RuntimeError("No patches found!")
176 
177  if coaddPsf is None:
178  raise RuntimeError("No coadd Psf found!")
179 
180  coaddExposure.setPsf(coaddPsf)
181  coaddExposure.setFilter(coaddFilter)
182  return pipeBase.Struct(exposure=coaddExposure,
183  sources=None)
184 
186  """Return coadd name for given task config
187 
188  Returns
189  -------
190  CoaddDatasetName : `string`
191 
192  TODO: This nearly duplicates a method in CoaddBaseTask (DM-11985)
193  """
194  warpType = self.config.warpType
195  suffix = "" if warpType == "direct" else warpType[0].upper() + warpType[1:]
196  return self.config.coaddName + "Coadd" + suffix
197 
198 
199 class GetCalexpAsTemplateConfig(pexConfig.Config):
200  doAddCalexpBackground = pexConfig.Field(
201  dtype=bool,
202  default=True,
203  doc="Add background to calexp before processing it."
204  )
205 
206 
207 class GetCalexpAsTemplateTask(pipeBase.Task):
208  """Subtask to retrieve calexp of the same ccd number as the science image SensorRef
209  for use as an image difference template.
210 
211  To be run as a subtask by pipe.tasks.ImageDifferenceTask.
212  Intended for use with simulations and surveys that repeatedly visit the same pointing.
213  This code was originally part of Winter2013ImageDifferenceTask.
214  """
215 
216  ConfigClass = GetCalexpAsTemplateConfig
217  _DefaultName = "GetCalexpAsTemplateTask"
218 
219  def run(self, exposure, sensorRef, templateIdList):
220  """Return a calexp exposure with based on input sensorRef.
221 
222  Construct a dataId based on the sensorRef.dataId combined
223  with the specifications from the first dataId in templateIdList
224 
225  Parameters
226  ----------
227  exposure : `lsst.afw.image.Exposure`
228  exposure (unused)
229  sensorRef : `list` of `lsst.daf.persistence.ButlerDataRef`
230  Data reference of the calexp(s) to subtract from.
231  templateIdList : `list` of `lsst.daf.persistence.ButlerDataRef`
232  Data reference of the template calexp to be subtraced.
233  Can be incomplete, fields are initialized from `sensorRef`.
234  If there are multiple items, only the first one is used.
235 
236  Returns
237  -------
238  result : `struct`
239 
240  return a pipeBase.Struct:
241 
242  - ``exposure`` : a template calexp
243  - ``sources`` : source catalog measured on the template
244  """
245 
246  if len(templateIdList) == 0:
247  raise RuntimeError("No template data reference supplied.")
248  if len(templateIdList) > 1:
249  self.log.warn("Multiple template data references supplied. Using the first one only.")
250 
251  templateId = sensorRef.dataId.copy()
252  templateId.update(templateIdList[0])
253 
254  self.log.info("Fetching calexp (%s) as template." % (templateId))
255 
256  butler = sensorRef.getButler()
257  template = butler.get(datasetType="calexp", dataId=templateId)
258  if self.config.doAddCalexpBackground:
259  templateBg = butler.get(datasetType="calexpBackground", dataId=templateId)
260  mi = template.getMaskedImage()
261  mi += templateBg.getImage()
262 
263  if not template.hasPsf():
264  raise pipeBase.TaskError("Template has no psf")
265 
266  templateSources = butler.get(datasetType="src", dataId=templateId)
267  return pipeBase.Struct(exposure=template,
268  sources=templateSources)
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
def run(self, exposure, sensorRef, templateIdList)
Definition: getTemplate.py:219
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:55