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."""
24import logging
26# Need to enable PSFs to be instantiated
27import lsst.afw.detection # noqa: F401
28from lsst.afw.image import makeExposure, makeMaskedImage
30from lsst.daf.butler import StorageClassDelegate
32log = logging.getLogger(__name__)
35class ExposureAssembler(StorageClassDelegate):
37 EXPOSURE_COMPONENTS = set(("image", "variance", "mask", "wcs", "psf"))
38 EXPOSURE_INFO_COMPONENTS = set(("apCorrMap", "coaddInputs", "photoCalib", "metadata",
39 "filterLabel", "transmissionCurve", "visitInfo",
40 "detector", "validPolygon", "summaryStats", "id"))
41 EXPOSURE_READ_COMPONENTS = {"bbox", "dimensions", "xy0", "filter"}
43 COMPONENT_MAP = {"bbox": "BBox", "xy0": "XY0"}
44 """Map component name to actual getter name."""
46 def _groupRequestedComponents(self):
47 """Group requested components into top level and ExposureInfo.
49 Returns
50 -------
51 expComps : `set` [`str`]
52 Components associated with the top level Exposure.
53 expInfoComps : `set` [`str`]
54 Components associated with the ExposureInfo
56 Raises
57 ------
58 ValueError
59 There are components defined in the storage class that are not
60 expected by this assembler.
61 """
62 requested = set(self.storageClass.components.keys())
64 # Check that we are requesting something that we support
65 unknown = requested - (self.EXPOSURE_COMPONENTS | self.EXPOSURE_INFO_COMPONENTS)
66 if unknown:
67 raise ValueError("Asking for unrecognized component: {}".format(unknown))
69 expItems = requested & self.EXPOSURE_COMPONENTS
70 expInfoItems = requested & self.EXPOSURE_INFO_COMPONENTS
71 return expItems, expInfoItems
73 def getComponent(self, composite, componentName):
74 """Get a component from an Exposure
76 Parameters
77 ----------
78 composite : `~lsst.afw.image.Exposure`
79 `Exposure` to access component.
80 componentName : `str`
81 Name of component to retrieve.
83 Returns
84 -------
85 component : `object`
86 The component. Can be None.
88 Raises
89 ------
90 AttributeError
91 The component can not be found.
92 """
93 if componentName in self.EXPOSURE_COMPONENTS or componentName in self.EXPOSURE_READ_COMPONENTS:
94 # Use getter translation if relevant or the name itself
95 return super().getComponent(composite, self.COMPONENT_MAP.get(componentName, componentName))
96 elif componentName in self.EXPOSURE_INFO_COMPONENTS:
97 if hasattr(composite, "getInfo"):
98 # it is possible for this method to be called with
99 # an ExposureInfo composite so trap for that and only get
100 # the ExposureInfo if the method is supported
101 composite = composite.getInfo()
102 return super().getComponent(composite, self.COMPONENT_MAP.get(componentName, componentName))
103 else:
104 raise AttributeError("Do not know how to retrieve component {} from {}".format(componentName,
105 type(composite)))
107 def getValidComponents(self, composite):
108 """Extract all non-None components from a composite.
110 Parameters
111 ----------
112 composite : `object`
113 Composite from which to extract components.
115 Returns
116 -------
117 comps : `dict`
118 Non-None components extracted from the composite, indexed by the
119 component name as derived from the `self.storageClass`.
120 """
121 # For Exposure we call the generic version twice: once for top level
122 # components, and again for ExposureInfo.
123 expItems, expInfoItems = self._groupRequestedComponents()
125 components = super().getValidComponents(composite)
126 infoComps = super().getValidComponents(composite.getInfo())
127 components.update(infoComps)
128 return components
130 def disassemble(self, composite):
131 """Disassemble an afw Exposure.
133 This implementation attempts to extract components from the parent
134 by looking for attributes of the same name or getter methods derived
135 from the component name.
137 Parameters
138 ----------
139 composite : `~lsst.afw.image.Exposure`
140 `Exposure` composite object consisting of components to be
141 extracted.
143 Returns
144 -------
145 components : `dict`
146 `dict` with keys matching the components defined in
147 `self.storageClass` and values being `DatasetComponent` instances
148 describing the component.
150 Raises
151 ------
152 ValueError
153 A requested component can not be found in the parent using generic
154 lookups.
155 TypeError
156 The parent object does not match the supplied `self.storageClass`.
158 Notes
159 -----
160 If a PSF is present but is not persistable, the PSF will not be
161 included in the returned components.
162 """
163 if not self.storageClass.validateInstance(composite):
164 raise TypeError("Unexpected type mismatch between parent and StorageClass"
165 " ({} != {})".format(type(composite), self.storageClass.pytype))
167 # Only look for components that are defined by the StorageClass
168 components = {}
169 expItems, expInfoItems = self._groupRequestedComponents()
171 fromExposure = super().disassemble(composite, subset=expItems)
172 components.update(fromExposure)
174 fromExposureInfo = super().disassemble(composite,
175 subset=expInfoItems, override=composite.getInfo())
176 components.update(fromExposureInfo)
178 if "psf" in components and not components["psf"].component.isPersistable():
179 log.warning("PSF of type %s is not persistable and has been ignored.",
180 type(components["psf"].component).__name__)
181 del components["psf"]
183 return components
185 def assemble(self, components):
186 """Construct an Exposure from components.
188 Parameters
189 ----------
190 components : `dict`
191 All the components from which to construct the Exposure.
192 Some can be missing.
194 Returns
195 -------
196 exposure : `~lsst.afw.image.Exposure`
197 Assembled exposure.
199 Raises
200 ------
201 ValueError
202 Some supplied components are not recognized.
203 """
204 components = components.copy()
205 maskedImageComponents = {}
206 hasMaskedImage = False
207 for component in ("image", "variance", "mask"):
208 value = None
209 if component in components:
210 hasMaskedImage = True
211 value = components.pop(component)
212 maskedImageComponents[component] = value
214 wcs = None
215 if "wcs" in components:
216 wcs = components.pop("wcs")
218 pytype = self.storageClass.pytype
219 if hasMaskedImage:
220 maskedImage = makeMaskedImage(**maskedImageComponents)
221 exposure = makeExposure(maskedImage, wcs=wcs)
223 if not isinstance(exposure, pytype):
224 raise RuntimeError("Unexpected type created in assembly;"
225 " was {} expected {}".format(type(exposure), pytype))
227 else:
228 exposure = pytype()
229 if wcs is not None:
230 exposure.setWcs(wcs)
232 # Set other components
233 exposure.setPsf(components.pop("psf", None))
234 exposure.setPhotoCalib(components.pop("photoCalib", None))
236 info = exposure.getInfo()
237 if "visitInfo" in components:
238 info.setVisitInfo(components.pop("visitInfo"))
239 # Override ID set in visitInfo, if necessary
240 if "id" in components:
241 info.id = components.pop("id")
242 info.setApCorrMap(components.pop("apCorrMap", None))
243 info.setCoaddInputs(components.pop("coaddInputs", None))
244 info.setMetadata(components.pop("metadata", None))
245 info.setValidPolygon(components.pop("validPolygon", None))
246 info.setDetector(components.pop("detector", None))
247 info.setTransmissionCurve(components.pop("transmissionCurve", None))
248 info.setSummaryStats(components.pop("summaryStats", None))
250 # TODO: switch back to "filter" as primary component in DM-27177
251 info.setFilterLabel(components.pop("filterLabel", None))
253 # If we have some components left over that is a problem
254 if components:
255 raise ValueError("The following components were not understood:"
256 " {}".format(list(components.keys())))
258 return exposure
260 def handleParameters(self, inMemoryDataset, parameters=None):
261 """Modify the in-memory dataset using the supplied parameters,
262 returning a possibly new object.
264 Parameters
265 ----------
266 inMemoryDataset : `object`
267 Object to modify based on the parameters.
268 parameters : `dict`, optional
269 Parameters to apply. Values are specific to the parameter.
270 Supported parameters are defined in the associated
271 `StorageClass`. If no relevant parameters are specified the
272 inMemoryDataset will be return unchanged.
274 Returns
275 -------
276 inMemoryDataset : `object`
277 Updated form of supplied in-memory dataset, after parameters
278 have been used.
279 """
280 # Understood by *this* subset command
281 understood = ("bbox", "origin")
282 use = self.storageClass.filterParameters(parameters, subset=understood)
283 if use:
284 inMemoryDataset = inMemoryDataset.subset(**use)
286 return inMemoryDataset
288 @classmethod
289 def selectResponsibleComponent(cls, readComponent: str, fromComponents) -> str:
290 imageComponents = ["mask", "image", "variance"]
291 forwarderMap = {
292 "bbox": imageComponents,
293 "dimensions": imageComponents,
294 "xy0": imageComponents,
295 "filter": ["filterLabel"],
296 "id": ["metadata"],
297 }
298 forwarder = forwarderMap.get(readComponent)
299 if forwarder is not None:
300 for c in forwarder:
301 if c in fromComponents:
302 return c
303 raise ValueError(f"Can not calculate read component {readComponent} from {fromComponents}")