Coverage for python/lsst/ip/diffim/getTemplate.py : 15%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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#
23import numpy as np
25import lsst.afw.image as afwImage
26import lsst.geom as geom
27import lsst.pex.config as pexConfig
28import lsst.pipe.base as pipeBase
29from lsst.ip.diffim.dcrModel import DcrModel
31__all__ = ["GetCoaddAsTemplateTask", "GetCoaddAsTemplateConfig",
32 "GetCalexpAsTemplateTask", "GetCalexpAsTemplateConfig"]
35class 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 )
58class GetCoaddAsTemplateTask(pipeBase.Task):
59 """Subtask to retrieve coadd for use as an image difference template.
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 """
66 ConfigClass = GetCoaddAsTemplateConfig
67 _DefaultName = "GetCoaddAsTemplateTask"
69 def run(self, exposure, sensorRef, templateIdList=None):
70 """Retrieve and mosaic a template coadd exposure that overlaps the exposure
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)
81 Returns
82 -------
83 result : `struct`
84 return a pipeBase.Struct:
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)
99 if not patchList:
100 raise RuntimeError("No suitable tract found")
101 self.log.info("Assembling %s coadd patches" % (len(patchList),))
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()))
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
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)
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()
171 # Retrieve the PSF for this coadd tract, if not already retrieved
172 if coaddPsf is None and coaddPatch.hasPsf():
173 coaddPsf = coaddPatch.getPsf()
175 # Retrieve the calibration for this coadd tract, if not already retrieved
176 if coaddPhotoCalib is None:
177 coaddPhotoCalib = coaddPatch.getPhotoCalib()
179 if nPatchesFound == 0:
180 raise RuntimeError("No patches found!")
182 if coaddPsf is None:
183 raise RuntimeError("No coadd Psf found!")
185 if coaddPhotoCalib is None:
186 raise RuntimeError("No coadd PhotoCalib found!")
188 coaddExposure.setPhotoCalib(coaddPhotoCalib)
189 coaddExposure.setPsf(coaddPsf)
190 coaddExposure.setFilter(coaddFilter)
191 return pipeBase.Struct(exposure=coaddExposure,
192 sources=None)
194 def getCoaddDatasetName(self):
195 """Return coadd name for given task config
197 Returns
198 -------
199 CoaddDatasetName : `string`
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
208class GetCalexpAsTemplateConfig(pexConfig.Config):
209 doAddCalexpBackground = pexConfig.Field(
210 dtype=bool,
211 default=True,
212 doc="Add background to calexp before processing it."
213 )
216class 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.
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 """
225 ConfigClass = GetCalexpAsTemplateConfig
226 _DefaultName = "GetCalexpAsTemplateTask"
228 def run(self, exposure, sensorRef, templateIdList):
229 """Return a calexp exposure with based on input sensorRef.
231 Construct a dataId based on the sensorRef.dataId combined
232 with the specifications from the first dataId in templateIdList
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.
245 Returns
246 -------
247 result : `struct`
249 return a pipeBase.Struct:
251 - ``exposure`` : a template calexp
252 - ``sources`` : source catalog measured on the template
253 """
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.")
260 templateId = sensorRef.dataId.copy()
261 templateId.update(templateIdList[0])
263 self.log.info("Fetching calexp (%s) as template." % (templateId))
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()
272 if not template.hasPsf():
273 raise pipeBase.TaskError("Template has no psf")
275 templateSources = butler.get(datasetType="src", dataId=templateId)
276 return pipeBase.Struct(exposure=template,
277 sources=templateSources)