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

# 

# A couple of useful functions to calculate PFS's SHA-1s 

# 

import hashlib 

import inspect 

import functools 

import numpy as np 

 

__all__ = ("calculatePfsVisitHash", "createHash", "astropyHeaderToDict", "astropyHeaderFromDict", 

"wraparoundNVisit", "inheritDocstrings",) 

 

 

def calculatePfsVisitHash(visits): 

"""Calculate and return a hash from a list of visits 

 

Parameters 

---------- 

visits : `list` of `int` 

List of visit numbers. 

 

Returns 

------- 

hash : `int` 

Hash of the visits. 

""" 

check = set(visits) 

if len(check) != len(visits): 

from collections import Counter 

counts = Counter(visits) 

raise ValueError(f"List of visits is not unique: {[vv for vv in counts if counts[vv] > 1]}") 

return createHash([str(vv).encode() for vv in sorted(visits)]) 

 

 

def calculate_pfsDesignId(fiberIds, ras, decs): 

"""Calculate and return the hash from a set of lists of 

fiberId, ra, and dec""" 

 

if fiberIds is None: 

if ras is None and decs is None: 

return 0x0 

 

raise RuntimeError( 

"Either all or none of fiberId, ra, and dec may be None") 

 

if (ras == 0.0).all() and (decs == 0.0).all(): # don't check fiberIds as this may be lab data 

return 0x0 

 

return createHash(["%d %.0f %.0f" % (fiberId, ra, dec) for fiberId, ra, dec in zip(fiberIds, ras, decs)]) 

 

 

def createHash(*args): 

"""Create a hash from the input strings truncated to 63 bits. 

 

Parameters 

---------- 

*args : `str` 

input string values used to generate the hash. 

 

Returns 

------- 

truncatedHash : `int` 

truncated hash value 

""" 

m = hashlib.sha1() 

for l in list(args): 

m.update(str(l).encode()) 

 

return int(m.hexdigest(), 16) & 0x7fffffffffffffff 

 

 

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 

 

 

def makeFullCovariance(covar): 

"""Given a matrix of the diagonal part of the covariance matrix return a full matrix 

 

This is mostly useful for visualising the COVAR arrays in pfsArm/pfsObject files 

 

Specifically, 

covar[0, 0:] Diagonal 

covar[1, 0:-1] +-1 off diagonal 

covar[2, 0:-2] +-2 off diagonal 

""" 

C = np.zeros(covar.shape[1]**2).reshape((covar.shape[1], covar.shape[1])) 

 

i = np.arange(C.shape[0]) 

 

C[i, i] = covar[0] # diagonal 

for j in range(1, covar.shape[0]): # bands near the diagonal 

C[i[j:], i[:-j]] = covar[j][0:-j] # above the diagonal 

C[i[:-j], i[j:]] = covar[j][0:-j] # below the diagonal 

 

return C 

 

 

def astropyHeaderToDict(header): 

"""Convert an astropy FITS header to a dict 

 

Comments are not preserved, nor are ``COMMENT`` or ``HISTORY`` cards. 

 

Parameters 

---------- 

header : `astropy.io.fits.Header` 

FITS header. 

 

Returns 

------- 

metadata : `dict` 

FITS header keywords and values. 

""" 

return {key: value for key, value in header.items() if key not in set(("HISTORY", "COMMENT"))} 

 

 

def astropyHeaderFromDict(metadata): 

"""Convert a dict to an astropy FITS header 

 

Parameters 

---------- 

metadata : `dict` 

FITS header keywords and values. 

 

Returns 

------- 

header : `astropy.io.fits.Header` 

FITS header. 

""" 

import astropy.io.fits 

header = astropy.io.fits.Header() 

for key, value in metadata.items(): 

if len(key) > 8 and not key.startswith("HIERARCH"): 

key = "HIERARCH " + key 

header.append((key, value)) 

return header 

 

 

def wraparoundNVisit(nVisit): 

"""Wraparound number of visits to acceptable range (0-999) 

 

Parameters 

---------- 

nVisit : `int` 

number of visits 

 

Returns 

------- 

nVisit_wrapped : `int` 

wraparound number of visits 

""" 

return nVisit % 1000 

 

 

def inheritDocstrings(cls): 

"""Class decorator to inherit docstrings from base classes 

 

Docstrings are copied, changing any instances of the base class name to 

the subclass name. The docstring is inserted into a new method that calls 

the parent class implementation. 

""" 

baseClasses = cls.__mro__[1:-1] # Excluding the subclass and 'object' 

for name, attr in inspect.getmembers(cls): 

if not inspect.isfunction(attr) and not inspect.ismethod(attr): 

continue 

if name in cls.__dict__: 

# Has its own implementation, and therefore own docstring. 

continue 

if name.startswith("_"): 

# Private method: user shouldn't be looking here anyway 

continue 

169 ↛ 174line 169 didn't jump to line 174, because the loop on line 169 didn't complete for base in baseClasses: 

170 ↛ 169line 170 didn't jump to line 169, because the condition on line 170 was never false if hasattr(base, name): 

impl = getattr(base, name) 

break 

else: 

raise RuntimeError(f"Unable to find implementation for {cls.__name__}.{name}") 

175 ↛ 177line 175 didn't jump to line 177, because the condition on line 175 was never true if not hasattr(impl, "__doc__"): 

# No docstring to copy 

continue 

docstring = impl.__doc__ 

if not any(base.__name__ in docstring for base in baseClasses): 

# No need to fix anything 

continue 

for base in baseClasses: 

docstring = docstring.replace(base.__name__, cls.__name__) 

 

# Modify the docstring in an override 

method = base.__dict__[impl.__name__] # No binding to the base class 

187 ↛ 192line 187 didn't jump to line 192, because the condition on line 187 was never false if isinstance(method, classmethod): 

override = functools.partial(method.__func__) 

override.__doc__ = docstring 

override = classmethod(override) # Now bind to the subclass 

else: 

override = functools.partial(method) 

override.__doc__ = docstring 

 

setattr(cls, name, override) 

 

return cls