Coverage for python/lsst/daf/butler/assemblers/exposureAssembler.py : 77%

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/>.
22"""Support for assembling and disassembling afw Exposures."""
24# Need to enable PSFs to be instantiated
25import lsst.afw.detection # noqa: F401
26from lsst.afw.image import makeExposure, makeMaskedImage
28from lsst.daf.butler import CompositeAssembler
31class ExposureAssembler(CompositeAssembler):
33 EXPOSURE_COMPONENTS = set(("image", "variance", "mask", "wcs", "psf"))
34 EXPOSURE_INFO_COMPONENTS = set(("apCorrMap", "coaddInputs", "photoCalib", "metadata",
35 "filter", "transmissionCurve", "visitInfo"))
37 def _groupRequestedComponents(self):
38 """Group requested components into top level and ExposureInfo.
40 Returns
41 -------
42 expComps : `dict`
43 Components associated with the top level Exposure.
44 expInfoComps : `dict`
45 Components associated with the ExposureInfo
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())
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))
60 expItems = requested & self.EXPOSURE_COMPONENTS
61 expInfoItems = requested & self.EXPOSURE_INFO_COMPONENTS
62 return expItems, expInfoItems
64 def getComponent(self, composite, componentName):
65 """Get a component from an Exposure
67 Parameters
68 ----------
69 composite : `~lsst.afw.image.Exposure`
70 `Exposure` to access component.
71 componentName : `str`
72 Name of component to retrieve.
74 Returns
75 -------
76 component : `object`
77 The component. Can be None.
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)))
97 def getValidComponents(self, composite):
98 """Extract all non-None components from a composite.
100 Parameters
101 ----------
102 composite : `object`
103 Composite from which to extract components.
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()
115 components = super().getValidComponents(composite)
116 infoComps = super().getValidComponents(composite.getInfo())
117 components.update(infoComps)
118 return components
120 def disassemble(self, composite):
121 """Disassemble an afw Exposure.
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.
127 Parameters
128 ----------
129 composite : `~lsst.afw.image.Exposure`
130 `Exposure` composite object consisting of components to be
131 extracted.
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.
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))
152 # Only look for components that are defined by the StorageClass
153 components = {}
154 expItems, expInfoItems = self._groupRequestedComponents()
156 fromExposure = super().disassemble(composite, subset=expItems)
157 components.update(fromExposure)
159 fromExposureInfo = super().disassemble(composite,
160 subset=expInfoItems, override=composite.getInfo())
161 components.update(fromExposureInfo)
163 return components
165 def assemble(self, components):
166 """Construct an Exposure from components.
168 Parameters
169 ----------
170 components : `dict`
171 All the components from which to construct the Exposure.
172 Some can be missing.
174 Returns
175 -------
176 exposure : `~lsst.afw.image.Exposure`
177 Assembled exposure.
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
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")
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)
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))
207 else:
208 exposure = pytype()
209 if wcs is not None:
210 exposure.setWcs(wcs)
212 # Set other components
213 exposure.setPsf(components.pop("psf", None))
214 exposure.setPhotoCalib(components.pop("photoCalib", None))
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))
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())))
228 return exposure
230 def handleParameters(self, inMemoryDataset, parameters=None):
231 """Modify the in-memory dataset using the supplied parameters,
232 returning a possibly new object.
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.
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)
256 return inMemoryDataset