Coverage for python/lsst/obs/base/exposureAssembler.py : 14%

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/>.
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",
36 "detector", "validPolygon"))
37 EXPOSURE_READ_COMPONENTS = {"bbox", "dimensions", "xy0"}
39 COMPONENT_MAP = {"bbox": "BBox", "xy0": "XY0"}
40 """Map component name to actual getter name."""
42 def _groupRequestedComponents(self):
43 """Group requested components into top level and ExposureInfo.
45 Returns
46 -------
47 expComps : `dict`
48 Components associated with the top level Exposure.
49 expInfoComps : `dict`
50 Components associated with the ExposureInfo
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())
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))
65 expItems = requested & self.EXPOSURE_COMPONENTS
66 expInfoItems = requested & self.EXPOSURE_INFO_COMPONENTS
67 return expItems, expInfoItems
69 def getComponent(self, composite, componentName):
70 """Get a component from an Exposure
72 Parameters
73 ----------
74 composite : `~lsst.afw.image.Exposure`
75 `Exposure` to access component.
76 componentName : `str`
77 Name of component to retrieve.
79 Returns
80 -------
81 component : `object`
82 The component. Can be None.
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, componentName)
99 else:
100 raise AttributeError("Do not know how to retrieve component {} from {}".format(componentName,
101 type(composite)))
103 def getValidComponents(self, composite):
104 """Extract all non-None components from a composite.
106 Parameters
107 ----------
108 composite : `object`
109 Composite from which to extract components.
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()
121 components = super().getValidComponents(composite)
122 infoComps = super().getValidComponents(composite.getInfo())
123 components.update(infoComps)
124 return components
126 def disassemble(self, composite):
127 """Disassemble an afw Exposure.
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.
133 Parameters
134 ----------
135 composite : `~lsst.afw.image.Exposure`
136 `Exposure` composite object consisting of components to be
137 extracted.
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.
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))
158 # Only look for components that are defined by the StorageClass
159 components = {}
160 expItems, expInfoItems = self._groupRequestedComponents()
162 fromExposure = super().disassemble(composite, subset=expItems)
163 components.update(fromExposure)
165 fromExposureInfo = super().disassemble(composite,
166 subset=expInfoItems, override=composite.getInfo())
167 components.update(fromExposureInfo)
169 return components
171 def assemble(self, components):
172 """Construct an Exposure from components.
174 Parameters
175 ----------
176 components : `dict`
177 All the components from which to construct the Exposure.
178 Some can be missing.
180 Returns
181 -------
182 exposure : `~lsst.afw.image.Exposure`
183 Assembled exposure.
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
200 wcs = None
201 if "wcs" in components:
202 wcs = components.pop("wcs")
204 pytype = self.storageClass.pytype
205 if hasMaskedImage:
206 maskedImage = makeMaskedImage(**maskedImageComponents)
207 exposure = makeExposure(maskedImage, wcs=wcs)
209 if not isinstance(exposure, pytype):
210 raise RuntimeError("Unexpected type created in assembly;"
211 " was {} expected {}".format(type(exposure), pytype))
213 else:
214 exposure = pytype()
215 if wcs is not None:
216 exposure.setWcs(wcs)
218 # Set other components
219 exposure.setPsf(components.pop("psf", None))
220 exposure.setPhotoCalib(components.pop("photoCalib", None))
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.setFilter(components.pop("filter", None))
230 # If we have some components left over that is a problem
231 if components:
232 raise ValueError("The following components were not understood:"
233 " {}".format(list(components.keys())))
235 return exposure
237 def handleParameters(self, inMemoryDataset, parameters=None):
238 """Modify the in-memory dataset using the supplied parameters,
239 returning a possibly new object.
241 Parameters
242 ----------
243 inMemoryDataset : `object`
244 Object to modify based on the parameters.
245 parameters : `dict`, optional
246 Parameters to apply. Values are specific to the parameter.
247 Supported parameters are defined in the associated
248 `StorageClass`. If no relevant parameters are specified the
249 inMemoryDataset will be return unchanged.
251 Returns
252 -------
253 inMemoryDataset : `object`
254 Updated form of supplied in-memory dataset, after parameters
255 have been used.
256 """
257 # Understood by *this* subset command
258 understood = ("bbox", "origin")
259 use = self.storageClass.filterParameters(parameters, subset=understood)
260 if use:
261 inMemoryDataset = inMemoryDataset.subset(**use)
263 return inMemoryDataset
265 @classmethod
266 def selectResponsibleComponent(cls, readComponent: str, fromComponents) -> str:
267 imageComponents = ["mask", "image", "variance"]
268 forwarderMap = {
269 "bbox": imageComponents,
270 "dimensions": imageComponents,
271 "xy0": imageComponents,
272 "filter": ["metadata"],
273 }
274 forwarder = forwarderMap.get(readComponent)
275 if forwarder is not None:
276 for c in forwarder:
277 if c in fromComponents:
278 return c
279 raise ValueError(f"Can not calculate read component {readComponent} from {fromComponents}")