lsst.obs.base  20.0.0-21-g52834e7+3
exposureAssembler.py
Go to the documentation of this file.
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
25 import lsst.afw.detection # noqa: F401
26 from lsst.afw.image import makeExposure, makeMaskedImage
27 
28 from lsst.daf.butler import CompositeAssembler
29 
30 
31 class ExposureAssembler(CompositeAssembler):
32 
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"}
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 : `dict`
48  Components associated with the top level Exposure.
49  expInfoComps : `dict`
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, 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.setFilter(components.pop("filter", None))
229 
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())))
234 
235  return exposure
236 
237  def handleParameters(self, inMemoryDataset, parameters=None):
238  """Modify the in-memory dataset using the supplied parameters,
239  returning a possibly new object.
240 
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.
250 
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)
262 
263  return inMemoryDataset
264 
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}")
lsst.obs.base.exposureAssembler.ExposureAssembler
Definition: exposureAssembler.py:31
lsst.obs.base.exposureAssembler.ExposureAssembler.EXPOSURE_READ_COMPONENTS
dictionary EXPOSURE_READ_COMPONENTS
Definition: exposureAssembler.py:37
lsst.obs.base.exposureAssembler.ExposureAssembler.EXPOSURE_INFO_COMPONENTS
EXPOSURE_INFO_COMPONENTS
Definition: exposureAssembler.py:34
lsst.obs.base.exposureAssembler.ExposureAssembler._groupRequestedComponents
def _groupRequestedComponents(self)
Definition: exposureAssembler.py:42
lsst.obs.base.exposureAssembler.ExposureAssembler.getComponent
def getComponent(self, composite, componentName)
Definition: exposureAssembler.py:69
lsst.obs.base.exposureAssembler.ExposureAssembler.getValidComponents
def getValidComponents(self, composite)
Definition: exposureAssembler.py:103
lsst.obs.base.exposureAssembler.ExposureAssembler.selectResponsibleComponent
str selectResponsibleComponent(cls, str readComponent, fromComponents)
Definition: exposureAssembler.py:266
lsst.obs.base.exposureAssembler.ExposureAssembler.disassemble
def disassemble(self, composite)
Definition: exposureAssembler.py:126
lsst.obs.base.exposureAssembler.ExposureAssembler.handleParameters
def handleParameters(self, inMemoryDataset, parameters=None)
Definition: exposureAssembler.py:237
lsst.obs.base.exposureAssembler.ExposureAssembler.EXPOSURE_COMPONENTS
EXPOSURE_COMPONENTS
Definition: exposureAssembler.py:33
lsst.obs.base.exposureAssembler.ExposureAssembler.assemble
def assemble(self, components)
Definition: exposureAssembler.py:171
lsst.obs.base.exposureAssembler.ExposureAssembler.COMPONENT_MAP
dictionary COMPONENT_MAP
Definition: exposureAssembler.py:39