Hide keyboard shortcuts

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# 

22 

23import numpy as np 

24 

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 

30 

31__all__ = ["GetCoaddAsTemplateTask", "GetCoaddAsTemplateConfig", 

32 "GetCalexpAsTemplateTask", "GetCalexpAsTemplateConfig"] 

33 

34 

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 ) 

56 

57 

58class 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 

194 def getCoaddDatasetName(self): 

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 

208class 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 

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. 

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)