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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

# This file is part of astro_metadata_translator. 

# 

# Developed for the LSST Data Management System. 

# This product includes software developed by the LSST Project 

# (http://www.lsst.org). 

# See the COPYRIGHT file at the top-level directory of this distribution 

# for details of code ownership. 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program. If not, see <http://www.gnu.org/licenses/>. 

 

"""Represent standard metadata from instrument headers""" 

 

__all__ = ("ObservationInfo", ) 

 

import itertools 

import logging 

import copy 

 

import astropy.time 

 

from .translator import MetadataTranslator 

from .properties import PROPERTIES 

 

log = logging.getLogger(__name__) 

 

 

class ObservationInfo: 

"""Standardized representation of an instrument header for a single 

exposure observation. 

 

Parameters 

---------- 

header : `dict`-like 

Representation of an instrument header accessible as a `dict`. 

filename : `str`, optional 

Name of the file whose header is being translated. For some 

datasets with missing header information this can sometimes 

allow for some fixups in translations. 

translator_class : `MetadataTranslator`-class, optional 

If not `None`, the class to use to translate the supplied headers 

into standard form. Otherwise each registered translator class will 

be asked in turn if it knows how to translate the supplied header. 

pedantic : `bool`, optional 

If True the translation must succeed for all properties. If False 

individual property translations must all be implemented but can fail 

and a warning will be issued. 

 

Raises 

------ 

ValueError 

The supplied header was not recognized by any of the registered 

translators. 

TypeError 

The supplied translator class was not a MetadataTranslator. 

""" 

 

_PROPERTIES = PROPERTIES 

"""All the properties supported by this class with associated 

documentation.""" 

 

def __init__(self, header, filename=None, translator_class=None, pedantic=False): 

 

# Store the supplied header for later stripping 

self._header = header 

 

# Store the filename 

self.filename = filename 

 

# PropertyList is not dict-like so force to a dict here to simplify 

# the translation code. 

if hasattr(header, "toOrderedDict"): 

header = header.toOrderedDict() 

 

if translator_class is None: 

translator_class = MetadataTranslator.determine_translator(header, filename=filename) 

elif not issubclass(translator_class, MetadataTranslator): 

raise TypeError(f"Translator class must be a MetadataTranslator, not {translator_class}") 

 

# Create an instance for this header 

translator = translator_class(header, filename=filename) 

 

# Store the translator 

self._translator = translator 

self.translator_class_name = translator_class.__name__ 

 

# Loop over each property and request the translated form 

for t in self._PROPERTIES: 

# prototype code 

method = f"to_{t}" 

property = f"_{t}" 

 

try: 

setattr(self, property, getattr(translator, method)()) 

except NotImplementedError as e: 

raise NotImplementedError(f"No translation exists for property '{t}'" 

f" using translator {translator.__class__}") from e 

except KeyError as e: 

if filename: 

file_info = f" and file {filename}" 

else: 

file_info = "" 

err_msg = f"Error calculating property '{t}' using translator {translator.__class__}" \ 

f"{file_info}" 

if pedantic: 

raise KeyError(err_msg) from e 

else: 

log.warning(header) 

log.warning(f"{err_msg}: {e}") 

 

@property 

def cards_used(self): 

"""Header cards used for the translation. 

 

Returns 

------- 

used : `frozenset` of `str` 

Set of card used. 

""" 

return self._translator.cards_used() 

 

def stripped_header(self): 

"""Return a copy of the supplied header with used keywords removed. 

 

Returns 

------- 

stripped : `dict`-like 

Same class as header supplied to constructor, but with the 

headers used to calculate the generic information removed. 

""" 

hdr = copy.copy(self._header) 

used = self._translator.cards_used() 

for c in used: 

del hdr[c] 

return hdr 

 

def __str__(self): 

# Put more interesting answers at front of list 

# and then do remainder 

priority = ("instrument", "telescope", "datetime_begin") 

properties = sorted(set(self._PROPERTIES.keys()) - set(priority)) 

 

result = "" 

for p in itertools.chain(priority, properties): 

value = getattr(self, p) 

if isinstance(value, astropy.time.Time): 

value.format = "isot" 

value = str(value.value) 

result += f"{p}: {value}\n" 

 

return result 

 

def __eq__(self, other): 

"""Compares equal if standard properties are equal 

""" 

if type(self) != type(other): 

return False 

 

for p in self._PROPERTIES: 

# Use string comparison since SkyCoord.__eq__ seems unreliable 

# otherwise. Should have per-type code so that floats and 

# quantities can be compared properly. 

v1 = f"{getattr(self, p)}" 

v2 = f"{getattr(other, p)}" 

if v1 != v2: 

return False 

 

return True 

 

def __getstate__(self): 

"""Get pickleable state 

 

Returns the properties, the name of the translator, and the 

cards that were used. Does not return the full header. 

 

Returns 

------- 

state : `dict` 

Dict containing items that can be persisted. 

""" 

state = dict() 

for p in self._PROPERTIES: 

property = f"_{p}" 

state[p] = getattr(self, property) 

 

return state 

 

def __setstate__(self, state): 

for p in self._PROPERTIES: 

property = f"_{p}" 

setattr(self, property, state[p]) 

 

 

# Method to add the standard properties 

def _make_property(property, doc, return_type): 

"""Create a getter method with associated docstring. 

 

Parameters 

---------- 

property : `str` 

Name of the property getter to be created. 

doc : `str` 

Description of this property. 

return_type : `str` 

Type of this property (used in the doc string). 

 

Returns 

------- 

p : `function` 

Getter method for this property. 

""" 

def getter(self): 

return getattr(self, f"_{property}") 

 

getter.__doc__ = f"""{doc} 

 

Returns 

------- 

{property} : `{return_type}` 

Access the property. 

""" 

return getter 

 

 

# Initialize the internal properties (underscored) and add the associated 

# getter methods. 

for name, description in ObservationInfo._PROPERTIES.items(): 

setattr(ObservationInfo, f"_{name}", None) 

setattr(ObservationInfo, name, property(_make_property(name, *description)))