Coverage for python/lsst/pipe/tasks/coaddBase.py: 39%

63 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 10:48 +0000

1# This file is part of pipe_tasks. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22__all__ = ["CoaddBaseTask", "makeSkyInfo"] 

23 

24import lsst.pex.config as pexConfig 

25import lsst.afw.image as afwImage 

26import lsst.pipe.base as pipeBase 

27import lsst.meas.algorithms as measAlg 

28import lsst.geom as geom 

29 

30from lsst.meas.algorithms import ScaleVarianceTask 

31from .selectImages import PsfWcsSelectImagesTask 

32from .coaddInputRecorder import CoaddInputRecorderTask 

33 

34 

35class CoaddBaseConfig(pexConfig.Config): 

36 """Configuration parameters for CoaddBaseTask 

37 

38 Configuration parameters shared between MakeCoaddTempExp and AssembleCoadd 

39 """ 

40 

41 coaddName = pexConfig.Field( 

42 doc="Coadd name: typically one of deep or goodSeeing.", 

43 dtype=str, 

44 default="deep", 

45 ) 

46 select = pexConfig.ConfigurableField( 

47 doc="Image selection subtask.", 

48 target=PsfWcsSelectImagesTask, 

49 ) 

50 badMaskPlanes = pexConfig.ListField( 

51 dtype=str, 

52 doc="Mask planes that, if set, the associated pixel should not be included in the coaddTempExp.", 

53 default=("NO_DATA",), 

54 ) 

55 inputRecorder = pexConfig.ConfigurableField( 

56 doc="Subtask that helps fill CoaddInputs catalogs added to the final Exposure", 

57 target=CoaddInputRecorderTask 

58 ) 

59 doPsfMatch = pexConfig.Field( 

60 dtype=bool, 

61 doc="Match to modelPsf? Sets makePsfMatched=True, makeDirect=False", 

62 deprecated="This field is no longer used. Will be removed after v26.", # TODO: DM-39841 

63 default=False 

64 ) 

65 modelPsf = measAlg.GaussianPsfFactory.makeField(doc="Model Psf factory") 

66 doApplyExternalPhotoCalib = pexConfig.Field( 

67 dtype=bool, 

68 default=False, 

69 doc=("Whether to apply external photometric calibration via an " 

70 "`lsst.afw.image.PhotoCalib` object. Uses the " 

71 "`externalPhotoCalibName` field to determine which calibration " 

72 "to load."), 

73 # TODO: remove on DM-39854. 

74 deprecated="Deprecated in favor of the 'visitSummary' connection. Will be removed after v26.", 

75 ) 

76 useGlobalExternalPhotoCalib = pexConfig.Field( 

77 dtype=bool, 

78 default=False, 

79 doc=("When using doApplyExternalPhotoCalib, use 'global' calibrations " 

80 "that are not run per-tract. When False, use per-tract photometric " 

81 "calibration files."), 

82 # TODO: remove on DM-39854. 

83 deprecated="Deprecated in favor of the 'visitSummary' connection. Will be removed after v26.", 

84 ) 

85 doApplyExternalSkyWcs = pexConfig.Field( 

86 dtype=bool, 

87 default=False, 

88 doc=("Whether to apply external astrometric calibration via an " 

89 "`lsst.afw.geom.SkyWcs` object. Uses `externalSkyWcsName` " 

90 "field to determine which calibration to load."), 

91 # TODO: remove on DM-39854. 

92 deprecated="Deprecated in favor of the 'visitSummary' connection. Will be removed after v26.", 

93 ) 

94 useGlobalExternalSkyWcs = pexConfig.Field( 

95 dtype=bool, 

96 default=False, 

97 doc=("When using doApplyExternalSkyWcs, use 'global' calibrations " 

98 "that are not run per-tract. When False, use per-tract wcs " 

99 "files."), 

100 # TODO: remove on DM-39854. 

101 deprecated="Deprecated in favor of the 'visitSummary' connection. Will be removed after v26.", 

102 ) 

103 includeCalibVar = pexConfig.Field( 

104 dtype=bool, 

105 doc="Add photometric calibration variance to warp variance plane.", 

106 default=False 

107 ) 

108 matchingKernelSize = pexConfig.Field( 108 ↛ exitline 108 didn't jump to the function exit

109 dtype=int, 

110 doc="Size in pixels of matching kernel. Must be odd.", 

111 default=21, 

112 check=lambda x: x % 2 == 1 

113 ) 

114 

115 

116class CoaddBaseTask(pipeBase.PipelineTask): 

117 """Base class for coaddition. 

118 

119 Subclasses must specify _DefaultName 

120 """ 

121 

122 ConfigClass = CoaddBaseConfig 

123 

124 def __init__(self, **kwargs): 

125 super().__init__(**kwargs) 

126 self.makeSubtask("select") 

127 self.makeSubtask("inputRecorder") 

128 

129 def getTempExpDatasetName(self, warpType="direct"): 

130 """Return warp name for given warpType and task config 

131 

132 Parameters 

133 ---------- 

134 warpType : `str` 

135 Either 'direct' or 'psfMatched'. 

136 

137 Returns 

138 ------- 

139 WarpDatasetName : `str` 

140 """ 

141 return self.config.coaddName + "Coadd_" + warpType + "Warp" 

142 

143 def getBadPixelMask(self): 

144 """Convenience method to provide the bitmask from the mask plane names 

145 """ 

146 return afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes) 

147 

148 

149def makeSkyInfo(skyMap, tractId, patchId): 

150 """Constructs SkyInfo used by coaddition tasks for multiple 

151 patchId formats. 

152 

153 Parameters 

154 ---------- 

155 skyMap : `lsst.skyMap.SkyMap` 

156 Sky map. 

157 tractId : `int` 

158 The ID of the tract. 

159 patchId : `str` or `int` or `tuple` of `int` 

160 Either Gen2-style comma delimited string (e.g. '4,5'), 

161 tuple of integers (e.g (4, 5), Gen3-style integer. 

162 

163 Returns 

164 ------- 

165 makeSkyInfo : `lsst.pipe.base.Struct` 

166 pipe_base Struct with attributes: 

167 

168 ``skyMap`` 

169 Sky map (`lsst.skyMap.SkyMap`). 

170 ``tractInfo`` 

171 Information for chosen tract of sky map (`lsst.skyMap.TractInfo`). 

172 ``patchInfo`` 

173 Information about chosen patch of tract (`lsst.skyMap.PatchInfo`). 

174 ``wcs`` 

175 WCS of tract (`lsst.afw.image.SkyWcs`). 

176 ``bbox`` 

177 Outer bbox of patch, as an geom Box2I (`lsst.afw.geom.Box2I`). 

178 """ 

179 tractInfo = skyMap[tractId] 

180 

181 if isinstance(patchId, str) and ',' in patchId: 

182 # patch format is "xIndex,yIndex" 

183 patchIndex = tuple(int(i) for i in patchId.split(",")) 

184 else: 

185 patchIndex = patchId 

186 

187 patchInfo = tractInfo.getPatchInfo(patchIndex) 

188 

189 return pipeBase.Struct( 

190 skyMap=skyMap, 

191 tractInfo=tractInfo, 

192 patchInfo=patchInfo, 

193 wcs=tractInfo.getWcs(), 

194 bbox=patchInfo.getOuterBBox(), 

195 ) 

196 

197 

198def scaleVariance(maskedImage, maskPlanes, log=None): 

199 """Scale the variance in a maskedImage 

200 

201 This is deprecated. Use the ScaleVarianceTask instead. 

202 

203 Parameters 

204 ---------- 

205 maskedImage : `lsst.afw.image.MaskedImage` 

206 MaskedImage to operate on; variance will be scaled. 

207 maskPlanes : `list` 

208 List of mask planes for pixels to reject. 

209 log : `Unknown` 

210 Log for reporting the renormalization factor; or None. 

211 

212 Returns 

213 ------- 

214 task.run : `Unknown` 

215 Renormalization factor. 

216 

217 Notes 

218 ----- 

219 The variance plane in a convolved or warped image (or a coadd derived 

220 from warped images) does not accurately reflect the noise properties of 

221 the image because variance has been lost to covariance. This function 

222 attempts to correct for this by scaling the variance plane to match 

223 the observed variance in the image. This is not perfect (because we're 

224 not tracking the covariance) but it's simple and is often good enough. 

225 """ 

226 config = ScaleVarianceTask.ConfigClass() 

227 config.maskPlanes = maskPlanes 

228 task = ScaleVarianceTask(config=config, name="scaleVariance", log=log) 

229 return task.run(maskedImage) 

230 

231 

232def reorderAndPadList(inputList, inputKeys, outputKeys, padWith=None): 

233 """Match the order of one list to another, padding if necessary 

234 

235 Parameters 

236 ---------- 

237 inputList : `list` 

238 List to be reordered and padded. Elements can be any type. 

239 inputKeys : `iterable` 

240 Iterable of values to be compared with outputKeys. Length must match `inputList`. 

241 outputKeys : `iterable` 

242 Iterable of values to be compared with inputKeys. 

243 padWith : `Unknown` 

244 Any value to be inserted where inputKey not in outputKeys. 

245 

246 Returns 

247 ------- 

248 outputList : `list` 

249 Copy of inputList reordered per outputKeys and padded with `padWith` 

250 so that the length matches length of outputKeys. 

251 """ 

252 outputList = [] 

253 for d in outputKeys: 

254 if d in inputKeys: 

255 outputList.append(inputList[inputKeys.index(d)]) 

256 else: 

257 outputList.append(padWith) 

258 return outputList 

259 

260 

261def subBBoxIter(bbox, subregionSize): 

262 """Iterate over subregions of a bbox. 

263 

264 Parameters 

265 ---------- 

266 bbox : `lsst.geom.Box2I` 

267 Bounding box over which to iterate. 

268 subregionSize : `lsst.geom.Extent2I` 

269 Size of sub-bboxes. 

270 

271 Yields 

272 ------ 

273 subBBox : `lsst.geom.Box2I` 

274 Next sub-bounding box of size ``subregionSize`` or smaller; each ``subBBox`` 

275 is contained within ``bbox``, so it may be smaller than ``subregionSize`` at 

276 the edges of ``bbox``, but it will never be empty. 

277 

278 Raises 

279 ------ 

280 RuntimeError 

281 Raised if any of the following occur: 

282 - The given bbox is empty. 

283 - The subregionSize is 0. 

284 """ 

285 if bbox.isEmpty(): 

286 raise RuntimeError("bbox %s is empty" % (bbox,)) 

287 if subregionSize[0] < 1 or subregionSize[1] < 1: 

288 raise RuntimeError("subregionSize %s must be nonzero" % (subregionSize,)) 

289 

290 for rowShift in range(0, bbox.getHeight(), subregionSize[1]): 

291 for colShift in range(0, bbox.getWidth(), subregionSize[0]): 

292 subBBox = geom.Box2I(bbox.getMin() + geom.Extent2I(colShift, rowShift), subregionSize) 

293 subBBox.clip(bbox) 

294 if subBBox.isEmpty(): 

295 raise RuntimeError("Bug: empty bbox! bbox=%s, subregionSize=%s, " 

296 "colShift=%s, rowShift=%s" % 

297 (bbox, subregionSize, colShift, rowShift)) 

298 yield subBBox