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 obs_base. 

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 StorageClassDelegate 

29 

30 

31class ExposureAssembler(StorageClassDelegate): 

32 

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

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

35 "filterLabel", "transmissionCurve", "visitInfo", 

36 "detector", "validPolygon")) 

37 EXPOSURE_READ_COMPONENTS = {"bbox", "dimensions", "xy0", "filter"} 

38 

39 COMPONENT_MAP = {"bbox": "BBox", "xy0": "XY0"} 

40 """Map component name to actual getter name.""" 

41 

42 def _groupRequestedComponents(self): 

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

44 

45 Returns 

46 ------- 

47 expComps : `set` [`str`] 

48 Components associated with the top level Exposure. 

49 expInfoComps : `set` [`str`] 

50 Components associated with the ExposureInfo 

51 

52 Raises 

53 ------ 

54 ValueError 

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

56 expected by this assembler. 

57 """ 

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

59 

60 # Check that we are requesting something that we support 

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

62 if unknown: 

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

64 

65 expItems = requested & self.EXPOSURE_COMPONENTS 

66 expInfoItems = requested & self.EXPOSURE_INFO_COMPONENTS 

67 return expItems, expInfoItems 

68 

69 def getComponent(self, composite, componentName): 

70 """Get a component from an Exposure 

71 

72 Parameters 

73 ---------- 

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

75 `Exposure` to access component. 

76 componentName : `str` 

77 Name of component to retrieve. 

78 

79 Returns 

80 ------- 

81 component : `object` 

82 The component. Can be None. 

83 

84 Raises 

85 ------ 

86 AttributeError 

87 The component can not be found. 

88 """ 

89 if componentName in self.EXPOSURE_COMPONENTS or componentName in self.EXPOSURE_READ_COMPONENTS: 

90 # Use getter translation if relevant or the name itself 

91 return super().getComponent(composite, self.COMPONENT_MAP.get(componentName, componentName)) 

92 elif componentName in self.EXPOSURE_INFO_COMPONENTS: 

93 if hasattr(composite, "getInfo"): 

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

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

96 # the ExposureInfo if the method is supported 

97 composite = composite.getInfo() 

98 return super().getComponent(composite, self.COMPONENT_MAP.get(componentName, componentName)) 

99 else: 

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

101 type(composite))) 

102 

103 def getValidComponents(self, composite): 

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

105 

106 Parameters 

107 ---------- 

108 composite : `object` 

109 Composite from which to extract components. 

110 

111 Returns 

112 ------- 

113 comps : `dict` 

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

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

116 """ 

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

118 # components, and again for ExposureInfo. 

119 expItems, expInfoItems = self._groupRequestedComponents() 

120 

121 components = super().getValidComponents(composite) 

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

123 components.update(infoComps) 

124 return components 

125 

126 def disassemble(self, composite): 

127 """Disassemble an afw Exposure. 

128 

129 This implementation attempts to extract components from the parent 

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

131 from the component name. 

132 

133 Parameters 

134 ---------- 

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

136 `Exposure` composite object consisting of components to be 

137 extracted. 

138 

139 Returns 

140 ------- 

141 components : `dict` 

142 `dict` with keys matching the components defined in 

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

144 describing the component. 

145 

146 Raises 

147 ------ 

148 ValueError 

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

150 lookups. 

151 TypeError 

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

153 """ 

154 if not self.storageClass.validateInstance(composite): 

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

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

157 

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

159 components = {} 

160 expItems, expInfoItems = self._groupRequestedComponents() 

161 

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

163 components.update(fromExposure) 

164 

165 fromExposureInfo = super().disassemble(composite, 

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

167 components.update(fromExposureInfo) 

168 

169 return components 

170 

171 def assemble(self, components): 

172 """Construct an Exposure from components. 

173 

174 Parameters 

175 ---------- 

176 components : `dict` 

177 All the components from which to construct the Exposure. 

178 Some can be missing. 

179 

180 Returns 

181 ------- 

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

183 Assembled exposure. 

184 

185 Raises 

186 ------ 

187 ValueError 

188 Some supplied components are not recognized. 

189 """ 

190 components = components.copy() 

191 maskedImageComponents = {} 

192 hasMaskedImage = False 

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

194 value = None 

195 if component in components: 

196 hasMaskedImage = True 

197 value = components.pop(component) 

198 maskedImageComponents[component] = value 

199 

200 wcs = None 

201 if "wcs" in components: 

202 wcs = components.pop("wcs") 

203 

204 pytype = self.storageClass.pytype 

205 if hasMaskedImage: 

206 maskedImage = makeMaskedImage(**maskedImageComponents) 

207 exposure = makeExposure(maskedImage, wcs=wcs) 

208 

209 if not isinstance(exposure, pytype): 

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

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

212 

213 else: 

214 exposure = pytype() 

215 if wcs is not None: 

216 exposure.setWcs(wcs) 

217 

218 # Set other components 

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

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

221 

222 info = exposure.getInfo() 

223 if "visitInfo" in components: 

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

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

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

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

228 info.setValidPolygon(components.pop("validPolygon", None)) 

229 info.setDetector(components.pop("detector", None)) 

230 info.setTransmissionCurve(components.pop("transmissionCurve", None)) 

231 

232 # TODO: switch back to "filter" as primary component in DM-27177 

233 info.setFilterLabel(components.pop("filterLabel", None)) 

234 

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

236 if components: 

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

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

239 

240 return exposure 

241 

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

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

244 returning a possibly new object. 

245 

246 Parameters 

247 ---------- 

248 inMemoryDataset : `object` 

249 Object to modify based on the parameters. 

250 parameters : `dict`, optional 

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

252 Supported parameters are defined in the associated 

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

254 inMemoryDataset will be return unchanged. 

255 

256 Returns 

257 ------- 

258 inMemoryDataset : `object` 

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

260 have been used. 

261 """ 

262 # Understood by *this* subset command 

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

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

265 if use: 

266 inMemoryDataset = inMemoryDataset.subset(**use) 

267 

268 return inMemoryDataset 

269 

270 @classmethod 

271 def selectResponsibleComponent(cls, readComponent: str, fromComponents) -> str: 

272 imageComponents = ["mask", "image", "variance"] 

273 forwarderMap = { 

274 "bbox": imageComponents, 

275 "dimensions": imageComponents, 

276 "xy0": imageComponents, 

277 "filter": ["filterLabel"], 

278 } 

279 forwarder = forwarderMap.get(readComponent) 

280 if forwarder is not None: 

281 for c in forwarder: 

282 if c in fromComponents: 

283 return c 

284 raise ValueError(f"Can not calculate read component {readComponent} from {fromComponents}")