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

56 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-16 04:05 -0700

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.geom as geom 

28 

29from lsst.meas.algorithms import ScaleVarianceTask 

30from .selectImages import PsfWcsSelectImagesTask 

31from .coaddInputRecorder import CoaddInputRecorderTask 

32 

33 

34class CoaddBaseConfig(pexConfig.Config): 

35 """Configuration parameters for CoaddBaseTask 

36 

37 Configuration parameters shared between MakeCoaddTempExp and AssembleCoadd 

38 """ 

39 

40 coaddName = pexConfig.Field( 

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

42 dtype=str, 

43 default="deep", 

44 ) 

45 select = pexConfig.ConfigurableField( 

46 doc="Image selection subtask.", 

47 target=PsfWcsSelectImagesTask, 

48 ) 

49 badMaskPlanes = pexConfig.ListField( 

50 dtype=str, 

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

52 default=("NO_DATA",), 

53 ) 

54 inputRecorder = pexConfig.ConfigurableField( 

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

56 target=CoaddInputRecorderTask 

57 ) 

58 includeCalibVar = pexConfig.Field( 

59 dtype=bool, 

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

61 default=False 

62 ) 

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

64 dtype=int, 

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

66 default=21, 

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

68 ) 

69 

70 

71class CoaddBaseTask(pipeBase.PipelineTask): 

72 """Base class for coaddition. 

73 

74 Subclasses must specify _DefaultName 

75 """ 

76 

77 ConfigClass = CoaddBaseConfig 

78 

79 def __init__(self, **kwargs): 

80 super().__init__(**kwargs) 

81 self.makeSubtask("select") 

82 self.makeSubtask("inputRecorder") 

83 

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

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

86 

87 Parameters 

88 ---------- 

89 warpType : `str` 

90 Either 'direct' or 'psfMatched'. 

91 

92 Returns 

93 ------- 

94 WarpDatasetName : `str` 

95 """ 

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

97 

98 def getBadPixelMask(self): 

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

100 """ 

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

102 

103 

104def makeSkyInfo(skyMap, tractId, patchId): 

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

106 patchId formats. 

107 

108 Parameters 

109 ---------- 

110 skyMap : `lsst.skyMap.SkyMap` 

111 Sky map. 

112 tractId : `int` 

113 The ID of the tract. 

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

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

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

117 

118 Returns 

119 ------- 

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

121 pipe_base Struct with attributes: 

122 

123 ``skyMap`` 

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

125 ``tractInfo`` 

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

127 ``patchInfo`` 

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

129 ``wcs`` 

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

131 ``bbox`` 

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

133 """ 

134 tractInfo = skyMap[tractId] 

135 

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

137 # patch format is "xIndex,yIndex" 

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

139 else: 

140 patchIndex = patchId 

141 

142 patchInfo = tractInfo.getPatchInfo(patchIndex) 

143 

144 return pipeBase.Struct( 

145 skyMap=skyMap, 

146 tractInfo=tractInfo, 

147 patchInfo=patchInfo, 

148 wcs=tractInfo.getWcs(), 

149 bbox=patchInfo.getOuterBBox(), 

150 ) 

151 

152 

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

154 """Scale the variance in a maskedImage 

155 

156 This is deprecated. Use the ScaleVarianceTask instead. 

157 

158 Parameters 

159 ---------- 

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

161 MaskedImage to operate on; variance will be scaled. 

162 maskPlanes : `list` 

163 List of mask planes for pixels to reject. 

164 log : `Unknown` 

165 Log for reporting the renormalization factor; or None. 

166 

167 Returns 

168 ------- 

169 task.run : `Unknown` 

170 Renormalization factor. 

171 

172 Notes 

173 ----- 

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

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

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

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

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

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

180 """ 

181 config = ScaleVarianceTask.ConfigClass() 

182 config.maskPlanes = maskPlanes 

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

184 return task.run(maskedImage) 

185 

186 

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

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

189 

190 Parameters 

191 ---------- 

192 inputList : `list` 

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

194 inputKeys : `iterable` 

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

196 outputKeys : `iterable` 

197 Iterable of values to be compared with inputKeys. 

198 padWith : `Unknown` 

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

200 

201 Returns 

202 ------- 

203 outputList : `list` 

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

205 so that the length matches length of outputKeys. 

206 """ 

207 outputList = [] 

208 for d in outputKeys: 

209 if d in inputKeys: 

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

211 else: 

212 outputList.append(padWith) 

213 return outputList 

214 

215 

216def subBBoxIter(bbox, subregionSize): 

217 """Iterate over subregions of a bbox. 

218 

219 Parameters 

220 ---------- 

221 bbox : `lsst.geom.Box2I` 

222 Bounding box over which to iterate. 

223 subregionSize : `lsst.geom.Extent2I` 

224 Size of sub-bboxes. 

225 

226 Yields 

227 ------ 

228 subBBox : `lsst.geom.Box2I` 

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

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

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

232 

233 Raises 

234 ------ 

235 RuntimeError 

236 Raised if any of the following occur: 

237 - The given bbox is empty. 

238 - The subregionSize is 0. 

239 """ 

240 if bbox.isEmpty(): 

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

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

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

244 

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

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

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

248 subBBox.clip(bbox) 

249 if subBBox.isEmpty(): 

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

251 "colShift=%s, rowShift=%s" % 

252 (bbox, subregionSize, colShift, rowShift)) 

253 yield subBBox