Coverage for python/lsst/daf/butler/formatters/fitsExposureFormatter.py : 73%

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__all__ = ("FitsExposureFormatter", )
24from astro_metadata_translator import fix_header
25from lsst.daf.butler import Formatter
28class FitsExposureFormatter(Formatter):
29 """Interface for reading and writing Exposures to and from FITS files.
30 """
31 extension = ".fits"
32 _metadata = None
34 @property
35 def metadata(self):
36 """The metadata read from this file. It will be stripped as
37 components are extracted from it
38 (`lsst.daf.base.PropertyList`).
39 """
40 if self._metadata is None:
41 self._metadata = self.readMetadata()
42 return self._metadata
44 def readImageComponent(self, component):
45 """Read the image, mask, or variance component of an Exposure.
47 Parameters
48 ----------
49 fileDescriptor : `FileDescriptor`
50 Identifies the file to read and parameters to be used for reading.
51 component : `str`, optional
52 Component to read from the file. Always one of "image",
53 "variance", or "mask".
55 Returns
56 -------
57 image : `~lsst.afw.image.Image` or `~lsst.afw.image.Mask`
58 In-memory image, variance, or mask component.
59 """
60 # TODO: could be made more efficient *if* Exposure type objects
61 # held the class objects of their components.
62 full = self.readFull()
63 return self.fileDescriptor.storageClass.assembler().getComponent(full, component)
65 def readMetadata(self):
66 """Read all header metadata directly into a PropertyList.
68 Returns
69 -------
70 metadata : `~lsst.daf.base.PropertyList`
71 Header metadata.
72 """
73 from lsst.afw.image import readMetadata
74 md = readMetadata(self.fileDescriptor.location.path)
75 fix_header(md)
76 return md
78 def stripMetadata(self):
79 """Remove metadata entries that are parsed into components.
81 This is only called when just the metadata is requested; stripping
82 entries there forces code that wants other components to ask for those
83 components directly rather than trying to extract them from the
84 metadata manually, which is fragile. This behavior is an intentional
85 change from Gen2.
87 Parameters
88 ----------
89 metadata : `~lsst.daf.base.PropertyList`
90 Header metadata, to be modified in-place.
91 """
92 # TODO: make sure this covers everything, by delegating to something
93 # that doesn't yet exist in afw.image.ExposureInfo.
94 from lsst.afw.image import bboxFromMetadata
95 from lsst.afw.geom import makeSkyWcs
96 bboxFromMetadata(self.metadata) # always strips
97 makeSkyWcs(self.metadata, strip=True)
99 def readInfoComponent(self, component):
100 """Read a component held by ExposureInfo.
102 Parameters
103 ----------
104 component : `str`, optional
105 Component to read from the file.
107 Returns
108 -------
109 obj : component-dependent
110 In-memory component object.
111 """
112 from lsst.afw.image import LOCAL
113 from lsst.geom import Box2I, Point2I
114 parameters = dict(bbox=Box2I(minimum=Point2I(0, 0), maximum=Point2I(0, 0)), origin=LOCAL)
115 tiny = self.readFull(parameters)
116 return self.fileDescriptor.storageClass.assembler().getComponent(tiny, component)
118 def readFull(self, parameters=None):
119 """Read the full Exposure object.
121 Parameters
122 ----------
123 parameters : `dict`, optional
124 If specified a dictionary of slicing parameters that overrides
125 those in ``fileDescriptor`.
127 Returns
128 -------
129 exposure : `~lsst.afw.image.Exposure`
130 Complete in-memory exposure.
131 """
132 fileDescriptor = self.fileDescriptor
133 if parameters is None:
134 parameters = fileDescriptor.parameters
135 if parameters is None:
136 parameters = {}
137 fileDescriptor.storageClass.validateParameters(parameters)
138 return fileDescriptor.storageClass.pytype(fileDescriptor.location.path, **parameters)
140 def read(self, component=None):
141 """Read data from a file.
143 Parameters
144 ----------
145 component : `str`, optional
146 Component to read from the file. Only used if the `StorageClass`
147 for reading differed from the `StorageClass` used to write the
148 file.
150 Returns
151 -------
152 inMemoryDataset : `object`
153 The requested data as a Python object. The type of object
154 is controlled by the specific formatter.
156 Raises
157 ------
158 ValueError
159 Component requested but this file does not seem to be a concrete
160 composite.
161 KeyError
162 Raised when parameters passed with fileDescriptor are not
163 supported.
164 """
165 fileDescriptor = self.fileDescriptor
166 if fileDescriptor.readStorageClass != fileDescriptor.storageClass:
167 if component == "metadata": 167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true
168 self.stripMetadata()
169 return self.metadata
170 elif component in ("image", "variance", "mask"):
171 return self.readImageComponent(component)
172 elif component is not None: 172 ↛ 175line 172 didn't jump to line 175, because the condition on line 172 was never false
173 return self.readInfoComponent(component)
174 else:
175 raise ValueError("Storage class inconsistency ({} vs {}) but no"
176 " component requested".format(fileDescriptor.readStorageClass.name,
177 fileDescriptor.storageClass.name))
178 return self.readFull()
180 def write(self, inMemoryDataset):
181 """Write a Python object to a file.
183 Parameters
184 ----------
185 inMemoryDataset : `object`
186 The Python object to store.
188 Returns
189 -------
190 path : `str`
191 The `URI` where the primary file is stored.
192 """
193 # Update the location with the formatter-preferred file extension
194 self.fileDescriptor.location.updateExtension(self.extension)
195 inMemoryDataset.writeFits(self.fileDescriptor.location.path)
196 return self.fileDescriptor.location.pathInStore