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 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__all__ = ("FitsExposureFormatter", ) 

23 

24from astro_metadata_translator import fix_header 

25from lsst.daf.butler import Formatter 

26from lsst.afw.image import ExposureFitsReader 

27 

28 

29class FitsExposureFormatter(Formatter): 

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

31 """ 

32 extension = ".fits" 

33 _metadata = None 

34 

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 

44 

45 def readMetadata(self): 

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

47 

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 

57 

58 def stripMetadata(self): 

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

60 

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. 

66 

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) 

78 

79 def readComponent(self, component, parameters=None): 

80 """Read a component held by the Exposure. 

81 

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

89 

90 Returns 

91 ------- 

92 obj : component-dependent 

93 In-memory component object. 

94 

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) 

120 

121 if method: 

122 reader = ExposureFitsReader(self.fileDescriptor.location.path) 

123 caller = getattr(reader, method, None) 

124 

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) 

131 

132 if hasParams and parameters: 

133 return caller(**parameters) 

134 else: 

135 return caller() 

136 else: 

137 raise KeyError(f"Unknown component requested: {component}") 

138 

139 def readFull(self, parameters=None): 

140 """Read the full Exposure object. 

141 

142 Parameters 

143 ---------- 

144 parameters : `dict`, optional 

145 If specified a dictionary of slicing parameters that overrides 

146 those in ``fileDescriptor`. 

147 

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 

165 

166 def read(self, component=None, parameters=None): 

167 """Read data from a file. 

168 

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

178 

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. 

184 

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

206 

207 def write(self, inMemoryDataset): 

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

209 

210 Parameters 

211 ---------- 

212 inMemoryDataset : `object` 

213 The Python object to store. 

214 

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