Hide keyboard shortcuts

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/>. 

21 

22__all__ = ("FitsExposureFormatter", ) 

23 

24from astro_metadata_translator import fix_header 

25from lsst.daf.butler import Formatter 

26 

27 

28class FitsExposureFormatter(Formatter): 

29 """Interface for reading and writing Exposures to and from FITS files. 

30 """ 

31 extension = ".fits" 

32 _metadata = None 

33 

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 

43 

44 def readImageComponent(self, component): 

45 """Read the image, mask, or variance component of an Exposure. 

46 

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". 

54 

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) 

64 

65 def readMetadata(self): 

66 """Read all header metadata directly into a PropertyList. 

67 

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 

77 

78 def stripMetadata(self): 

79 """Remove metadata entries that are parsed into components. 

80 

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. 

86 

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) 

98 

99 def readInfoComponent(self, component): 

100 """Read a component held by ExposureInfo. 

101 

102 Parameters 

103 ---------- 

104 component : `str`, optional 

105 Component to read from the file. 

106 

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) 

117 

118 def readFull(self, parameters=None): 

119 """Read the full Exposure object. 

120 

121 Parameters 

122 ---------- 

123 parameters : `dict`, optional 

124 If specified a dictionary of slicing parameters that overrides 

125 those in ``fileDescriptor`. 

126 

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) 

139 

140 def read(self, component=None): 

141 """Read data from a file. 

142 

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. 

149 

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. 

155 

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() 

179 

180 def write(self, inMemoryDataset): 

181 """Write a Python object to a file. 

182 

183 Parameters 

184 ---------- 

185 inMemoryDataset : `object` 

186 The Python object to store. 

187 

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