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", "summaryStats")) 

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 info.setSummaryStats(components.pop("summaryStats", None)) 

232 

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

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

235 

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

237 if components: 

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

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

240 

241 return exposure 

242 

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

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

245 returning a possibly new object. 

246 

247 Parameters 

248 ---------- 

249 inMemoryDataset : `object` 

250 Object to modify based on the parameters. 

251 parameters : `dict`, optional 

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

253 Supported parameters are defined in the associated 

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

255 inMemoryDataset will be return unchanged. 

256 

257 Returns 

258 ------- 

259 inMemoryDataset : `object` 

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

261 have been used. 

262 """ 

263 # Understood by *this* subset command 

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

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

266 if use: 

267 inMemoryDataset = inMemoryDataset.subset(**use) 

268 

269 return inMemoryDataset 

270 

271 @classmethod 

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

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

274 forwarderMap = { 

275 "bbox": imageComponents, 

276 "dimensions": imageComponents, 

277 "xy0": imageComponents, 

278 "filter": ["filterLabel"], 

279 } 

280 forwarder = forwarderMap.get(readComponent) 

281 if forwarder is not None: 

282 for c in forwarder: 

283 if c in fromComponents: 

284 return c 

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