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

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
26from lsst.afw.image import ExposureFitsReader
29class FitsExposureFormatter(Formatter):
30 """Interface for reading and writing Exposures to and from FITS files.
31 """
32 extension = ".fits"
33 _metadata = None
35 @property
36 def metadata(self):
37 """The metadata read from this file. It will be stripped as
38 components are extracted from it
39 (`lsst.daf.base.PropertyList`).
40 """
41 if self._metadata is None:
42 self._metadata = self.readMetadata()
43 return self._metadata
45 def readMetadata(self):
46 """Read all header metadata directly into a PropertyList.
48 Returns
49 -------
50 metadata : `~lsst.daf.base.PropertyList`
51 Header metadata.
52 """
53 from lsst.afw.image import readMetadata
54 md = readMetadata(self.fileDescriptor.location.path)
55 fix_header(md)
56 return md
58 def stripMetadata(self):
59 """Remove metadata entries that are parsed into components.
61 This is only called when just the metadata is requested; stripping
62 entries there forces code that wants other components to ask for those
63 components directly rather than trying to extract them from the
64 metadata manually, which is fragile. This behavior is an intentional
65 change from Gen2.
67 Parameters
68 ----------
69 metadata : `~lsst.daf.base.PropertyList`
70 Header metadata, to be modified in-place.
71 """
72 # TODO: make sure this covers everything, by delegating to something
73 # that doesn't yet exist in afw.image.ExposureInfo.
74 from lsst.afw.image import bboxFromMetadata
75 from lsst.afw.geom import makeSkyWcs
76 bboxFromMetadata(self.metadata) # always strips
77 makeSkyWcs(self.metadata, strip=True)
79 def readComponent(self, component, parameters=None):
80 """Read a component held by the Exposure.
82 Parameters
83 ----------
84 component : `str`, optional
85 Component to read from the file.
86 parameters : `dict`, optional
87 If specified, a dictionary of slicing parameters that
88 overrides those in ``fileDescriptor``.
90 Returns
91 -------
92 obj : component-dependent
93 In-memory component object.
95 Raises
96 ------
97 KeyError
98 Raised if the requested component cannot be handled.
99 """
100 componentMap = {'wcs': ('readWcs', False),
101 'coaddInputs': ('readCoaddInputs', False),
102 'psf': ('readPsf', False),
103 'image': ('readImage', True),
104 'mask': ('readMask', True),
105 'variance': ('readVariance', True),
106 }
107 method, hasParams = componentMap.get(component, None)
109 if method: 109 ↛ 125line 109 didn't jump to line 125, because the condition on line 109 was never false
110 reader = ExposureFitsReader(self.fileDescriptor.location.path)
111 caller = getattr(reader, method, None)
113 if caller: 113 ↛ exitline 113 didn't return from function 'readComponent', because the condition on line 113 was never false
114 if parameters is None: 114 ↛ 116line 114 didn't jump to line 116, because the condition on line 114 was never false
115 parameters = self.fileDescriptor.parameters
116 if parameters is None: 116 ↛ 118line 116 didn't jump to line 118, because the condition on line 116 was never false
117 parameters = {}
118 self.fileDescriptor.storageClass.validateParameters(parameters)
120 if hasParams and parameters: 120 ↛ 121line 120 didn't jump to line 121, because the condition on line 120 was never true
121 return caller(**parameters)
122 else:
123 return caller()
124 else:
125 raise KeyError(f"Unknown component requested: {component}")
127 def readFull(self, parameters=None):
128 """Read the full Exposure object.
130 Parameters
131 ----------
132 parameters : `dict`, optional
133 If specified a dictionary of slicing parameters that overrides
134 those in ``fileDescriptor`.
136 Returns
137 -------
138 exposure : `~lsst.afw.image.Exposure`
139 Complete in-memory exposure.
140 """
141 fileDescriptor = self.fileDescriptor
142 if parameters is None: 142 ↛ 144line 142 didn't jump to line 144, because the condition on line 142 was never false
143 parameters = fileDescriptor.parameters
144 if parameters is None:
145 parameters = {}
146 fileDescriptor.storageClass.validateParameters(parameters)
147 try:
148 output = fileDescriptor.storageClass.pytype(fileDescriptor.location.path, **parameters)
149 except TypeError:
150 reader = ExposureFitsReader(fileDescriptor.location.path)
151 output = reader.read(**parameters)
152 return output
154 def read(self, component=None, parameters=None):
155 """Read data from a file.
157 Parameters
158 ----------
159 component : `str`, optional
160 Component to read from the file. Only used if the `StorageClass`
161 for reading differed from the `StorageClass` used to write the
162 file.
163 parameters : `dict`, optional
164 If specified, a dictionary of slicing parameters that
165 overrides those in ``fileDescriptor``.
167 Returns
168 -------
169 inMemoryDataset : `object`
170 The requested data as a Python object. The type of object
171 is controlled by the specific formatter.
173 Raises
174 ------
175 ValueError
176 Component requested but this file does not seem to be a concrete
177 composite.
178 KeyError
179 Raised when parameters passed with fileDescriptor are not
180 supported.
181 """
182 fileDescriptor = self.fileDescriptor
183 if fileDescriptor.readStorageClass != fileDescriptor.storageClass:
184 if component == "metadata": 184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true
185 self.stripMetadata()
186 return self.metadata
187 elif component is not None: 187 ↛ 190line 187 didn't jump to line 190, because the condition on line 187 was never false
188 return self.readComponent(component, parameters)
189 else:
190 raise ValueError("Storage class inconsistency ({} vs {}) but no"
191 " component requested".format(fileDescriptor.readStorageClass.name,
192 fileDescriptor.storageClass.name))
193 return self.readFull()
195 def write(self, inMemoryDataset):
196 """Write a Python object to a file.
198 Parameters
199 ----------
200 inMemoryDataset : `object`
201 The Python object to store.
203 Returns
204 -------
205 path : `str`
206 The `URI` where the primary file is stored.
207 """
208 # Update the location with the formatter-preferred file extension
209 self.fileDescriptor.location.updateExtension(self.extension)
210 inMemoryDataset.writeFits(self.fileDescriptor.location.path)
211 return self.fileDescriptor.location.pathInStore