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# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21 

22"""Support for assembling and disassembling afw Exposures.""" 

23 

24# Need to enable PSFs to be instantiated 

25import lsst.afw.detection # noqa: F401 

26from lsst.afw.image import makeExposure, makeMaskedImage 

27 

28from lsst.daf.butler import CompositeAssembler 

29 

30 

31class ExposureAssembler(CompositeAssembler): 

32 

33 EXPOSURE_COMPONENTS = set(("image", "variance", "mask", "wcs", "psf")) 

34 EXPOSURE_INFO_COMPONENTS = set(("apCorrMap", "coaddInputs", "photoCalib", "metadata", 

35 "filter", "transmissionCurve", "visitInfo")) 

36 

37 def _groupRequestedComponents(self): 

38 """Group requested components into top level and ExposureInfo. 

39 

40 Returns 

41 ------- 

42 expComps : `dict` 

43 Components associated with the top level Exposure. 

44 expInfoComps : `dict` 

45 Components associated with the ExposureInfo 

46 

47 Raises 

48 ------ 

49 ValueError 

50 There are components defined in the storage class that are not 

51 expected by this assembler. 

52 """ 

53 requested = set(self.storageClass.components.keys()) 

54 

55 # Check that we are requesting something that we support 

56 unknown = requested - (self.EXPOSURE_COMPONENTS | self.EXPOSURE_INFO_COMPONENTS) 

57 if unknown: 57 ↛ 58line 57 didn't jump to line 58, because the condition on line 57 was never true

58 raise ValueError("Asking for unrecognized component: {}".format(unknown)) 

59 

60 expItems = requested & self.EXPOSURE_COMPONENTS 

61 expInfoItems = requested & self.EXPOSURE_INFO_COMPONENTS 

62 return expItems, expInfoItems 

63 

64 def getComponent(self, composite, componentName): 

65 """Get a component from an Exposure 

66 

67 Parameters 

68 ---------- 

69 composite : `~lsst.afw.image.Exposure` 

70 `Exposure` to access component. 

71 componentName : `str` 

72 Name of component to retrieve. 

73 

74 Returns 

75 ------- 

76 component : `object` 

77 The component. Can be None. 

78 

79 Raises 

80 ------ 

81 AttributeError 

82 The component can not be found. 

83 """ 

84 if componentName in self.EXPOSURE_COMPONENTS: 

85 return super().getComponent(composite, componentName) 

86 elif componentName in self.EXPOSURE_INFO_COMPONENTS: 86 ↛ 94line 86 didn't jump to line 94, because the condition on line 86 was never false

87 if hasattr(composite, "getInfo"): 

88 # it is possible for this method to be called with 

89 # an ExposureInfo composite so trap for that and only get 

90 # the ExposureInfo if the method is supported 

91 composite = composite.getInfo() 

92 return super().getComponent(composite, componentName) 

93 else: 

94 raise AttributeError("Do not know how to retrieve component {} from {}".format(componentName, 

95 type(composite))) 

96 

97 def getValidComponents(self, composite): 

98 """Extract all non-None components from a composite. 

99 

100 Parameters 

101 ---------- 

102 composite : `object` 

103 Composite from which to extract components. 

104 

105 Returns 

106 ------- 

107 comps : `dict` 

108 Non-None components extracted from the composite, indexed by the 

109 component name as derived from the `self.storageClass`. 

110 """ 

111 # For Exposure we call the generic version twice: once for top level 

112 # components, and again for ExposureInfo. 

113 expItems, expInfoItems = self._groupRequestedComponents() 

114 

115 components = super().getValidComponents(composite) 

116 infoComps = super().getValidComponents(composite.getInfo()) 

117 components.update(infoComps) 

118 return components 

119 

120 def disassemble(self, composite): 

121 """Disassemble an afw Exposure. 

122 

123 This implementation attempts to extract components from the parent 

124 by looking for attributes of the same name or getter methods derived 

125 from the component name. 

126 

127 Parameters 

128 ---------- 

129 composite : `~lsst.afw.image.Exposure` 

130 `Exposure` composite object consisting of components to be 

131 extracted. 

132 

133 Returns 

134 ------- 

135 components : `dict` 

136 `dict` with keys matching the components defined in 

137 `self.storageClass` and values being `DatasetComponent` instances 

138 describing the component. 

139 

140 Raises 

141 ------ 

142 ValueError 

143 A requested component can not be found in the parent using generic 

144 lookups. 

145 TypeError 

146 The parent object does not match the supplied `self.storageClass`. 

147 """ 

148 if not self.storageClass.validateInstance(composite): 148 ↛ 149line 148 didn't jump to line 149, because the condition on line 148 was never true

149 raise TypeError("Unexpected type mismatch between parent and StorageClass" 

150 " ({} != {})".format(type(composite), self.storageClass.pytype)) 

151 

152 # Only look for components that are defined by the StorageClass 

153 components = {} 

154 expItems, expInfoItems = self._groupRequestedComponents() 

155 

156 fromExposure = super().disassemble(composite, subset=expItems) 

157 components.update(fromExposure) 

158 

159 fromExposureInfo = super().disassemble(composite, 

160 subset=expInfoItems, override=composite.getInfo()) 

161 components.update(fromExposureInfo) 

162 

163 return components 

164 

165 def assemble(self, components): 

166 """Construct an Exposure from components. 

167 

168 Parameters 

169 ---------- 

170 components : `dict` 

171 All the components from which to construct the Exposure. 

172 Some can be missing. 

173 

174 Returns 

175 ------- 

176 exposure : `~lsst.afw.image.Exposure` 

177 Assembled exposure. 

178 

179 Raises 

180 ------ 

181 ValueError 

182 Some supplied components are not recognized. 

183 """ 

184 components = components.copy() 

185 maskedImageComponents = {} 

186 hasMaskedImage = False 

187 for component in ("image", "variance", "mask"): 

188 value = None 

189 if component in components: 189 ↛ 192line 189 didn't jump to line 192, because the condition on line 189 was never false

190 hasMaskedImage = True 

191 value = components.pop(component) 

192 maskedImageComponents[component] = value 

193 

194 wcs = None 

195 if "wcs" in components: 195 ↛ 198line 195 didn't jump to line 198, because the condition on line 195 was never false

196 wcs = components.pop("wcs") 

197 

198 pytype = self.storageClass.pytype 

199 if hasMaskedImage: 199 ↛ 208line 199 didn't jump to line 208, because the condition on line 199 was never false

200 maskedImage = makeMaskedImage(**maskedImageComponents) 

201 exposure = makeExposure(maskedImage, wcs=wcs) 

202 

203 if not isinstance(exposure, pytype): 203 ↛ 204line 203 didn't jump to line 204, because the condition on line 203 was never true

204 raise RuntimeError("Unexpected type created in assembly;" 

205 " was {} expected {}".format(type(exposure), pytype)) 

206 

207 else: 

208 exposure = pytype() 

209 if wcs is not None: 

210 exposure.setWcs(wcs) 

211 

212 # Set other components 

213 exposure.setPsf(components.pop("psf", None)) 

214 exposure.setPhotoCalib(components.pop("photoCalib", None)) 

215 

216 info = exposure.getInfo() 

217 if "visitInfo" in components: 217 ↛ 219line 217 didn't jump to line 219, because the condition on line 217 was never false

218 info.setVisitInfo(components.pop("visitInfo")) 

219 info.setApCorrMap(components.pop("apCorrMap", None)) 

220 info.setCoaddInputs(components.pop("coaddInputs", None)) 

221 info.setMetadata(components.pop("metadata", None)) 

222 

223 # If we have some components left over that is a problem 

224 if components: 224 ↛ 225line 224 didn't jump to line 225, because the condition on line 224 was never true

225 raise ValueError("The following components were not understood:" 

226 " {}".format(list(components.keys()))) 

227 

228 return exposure 

229 

230 def handleParameters(self, inMemoryDataset, parameters=None): 

231 """Modify the in-memory dataset using the supplied parameters, 

232 returning a possibly new object. 

233 

234 Parameters 

235 ---------- 

236 inMemoryDataset : `object` 

237 Object to modify based on the parameters. 

238 parameters : `dict`, optional 

239 Parameters to apply. Values are specific to the parameter. 

240 Supported parameters are defined in the associated 

241 `StorageClass`. If no relevant parameters are specified the 

242 inMemoryDataset will be return unchanged. 

243 

244 Returns 

245 ------- 

246 inMemoryDataset : `object` 

247 Updated form of supplied in-memory dataset, after parameters 

248 have been used. 

249 """ 

250 # Understood by *this* subset command 

251 understood = ("bbox", "origin") 

252 use = self.storageClass.filterParameters(parameters, subset=understood) 

253 if use: 

254 inMemoryDataset = inMemoryDataset.subset(**use) 

255 

256 return inMemoryDataset