Coverage for python/lsst/obs/base/fitsExposureFormatter.py : 16%

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__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 'photoCalib': ('readPhotoCalib', False),
107 'bbox': ('readBBox', True),
108 'xy0': ('readXY0', True),
109 'metadata': ('readMetadata', False),
110 'filter': ('readFilter', False),
111 'polygon': ('readValidPolygon', False),
112 'apCorrMap': ('readApCorrMap', False),
113 'visitInfo': ('readVisitInfo', False),
114 'transmissionCurve': ('readTransmissionCurve', False),
115 'detector': ('readDetector', False),
116 'extras': ('readExtraComponents', False),
117 'exposureInfo': ('readExposureInfo', False),
118 }
119 method, hasParams = componentMap.get(component, None)
121 if method:
122 reader = ExposureFitsReader(self.fileDescriptor.location.path)
123 caller = getattr(reader, method, None)
125 if caller:
126 if parameters is None:
127 parameters = self.fileDescriptor.parameters
128 if parameters is None:
129 parameters = {}
130 self.fileDescriptor.storageClass.validateParameters(parameters)
132 if hasParams and parameters:
133 return caller(**parameters)
134 else:
135 return caller()
136 else:
137 raise KeyError(f"Unknown component requested: {component}")
139 def readFull(self, parameters=None):
140 """Read the full Exposure object.
142 Parameters
143 ----------
144 parameters : `dict`, optional
145 If specified a dictionary of slicing parameters that overrides
146 those in ``fileDescriptor`.
148 Returns
149 -------
150 exposure : `~lsst.afw.image.Exposure`
151 Complete in-memory exposure.
152 """
153 fileDescriptor = self.fileDescriptor
154 if parameters is None:
155 parameters = fileDescriptor.parameters
156 if parameters is None:
157 parameters = {}
158 fileDescriptor.storageClass.validateParameters(parameters)
159 try:
160 output = fileDescriptor.storageClass.pytype(fileDescriptor.location.path, **parameters)
161 except TypeError:
162 reader = ExposureFitsReader(fileDescriptor.location.path)
163 output = reader.read(**parameters)
164 return output
166 def read(self, component=None, parameters=None):
167 """Read data from a file.
169 Parameters
170 ----------
171 component : `str`, optional
172 Component to read from the file. Only used if the `StorageClass`
173 for reading differed from the `StorageClass` used to write the
174 file.
175 parameters : `dict`, optional
176 If specified, a dictionary of slicing parameters that
177 overrides those in ``fileDescriptor``.
179 Returns
180 -------
181 inMemoryDataset : `object`
182 The requested data as a Python object. The type of object
183 is controlled by the specific formatter.
185 Raises
186 ------
187 ValueError
188 Component requested but this file does not seem to be a concrete
189 composite.
190 KeyError
191 Raised when parameters passed with fileDescriptor are not
192 supported.
193 """
194 fileDescriptor = self.fileDescriptor
195 if fileDescriptor.readStorageClass != fileDescriptor.storageClass:
196 if component == "metadata":
197 self.stripMetadata()
198 return self.metadata
199 elif component is not None:
200 return self.readComponent(component, parameters)
201 else:
202 raise ValueError("Storage class inconsistency ({} vs {}) but no"
203 " component requested".format(fileDescriptor.readStorageClass.name,
204 fileDescriptor.storageClass.name))
205 return self.readFull()
207 def write(self, inMemoryDataset):
208 """Write a Python object to a file.
210 Parameters
211 ----------
212 inMemoryDataset : `object`
213 The Python object to store.
215 Returns
216 -------
217 path : `str`
218 The `URI` where the primary file is stored.
219 """
220 # Update the location with the formatter-preferred file extension
221 self.fileDescriptor.location.updateExtension(self.extension)
222 inMemoryDataset.writeFits(self.fileDescriptor.location.path)
223 return self.fileDescriptor.location.pathInStore