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

from collections import Counter 

 

__all__ = ("MaskHelper",) 

 

 

class MaskHelper: 

"""Helper for dealing with symbolic names for mask values 

 

For science, we care about the symbolic name (i.e., what the mask 

represents; e.g., ``NO_DATA``), but this needs to be translated to the 

implementation (i.e., an integer) so pixels can be selected. 

 

Parameters 

---------- 

**kwargs : `dict` mapping `str` to `int` 

The mask planes. The integers should all be positive in the range 0..63. 

""" 

maskPlanePrefix = "MP_" # Prefix for header keywords 

_maxSize = 64 # Maximum number of bits 

 

def __init__(self, **kwargs): 

self.flags = kwargs 

assert all(ii >= 0 and ii < self._maxSize and isinstance(ii, int) for ii in kwargs.values()) 

 

def __repr__(self): 

"""Representation""" 

return "%s(%s)" % (self.__class__.__name__, self.flags) 

 

def __iter__(self): 

"""Iterator""" 

return iter(self.flags) 

 

def __getitem__(self, name): 

"""Retrieve value for a single mask name""" 

return 2**self.flags[name] 

 

def __len__(self): 

"""Number of bits used""" 

return len(self.flags) 

 

def __contains__(self, name): 

"""Is mask name used?""" 

return name in self.flags 

 

def get(self, *args): 

"""Retrieve value for multiple masks""" 

return sum(self[name] for name in args) 

 

def copy(self): 

"""Return a copy""" 

return type(self)(**self.flags) 

 

def add(self, name): 

"""Add mask name""" 

if name in self.flags: 

return self[name] 

if len(self) >= self._maxSize: 

raise RuntimeError("No bits remaining") 

# Find the lowest available bit 

existing = set(self.flags.values()) 

for ii in range(self._maxSize): 

if ii not in existing: 

value = ii 

break 

else: 

raise AssertionError("Something's broken") 

self.flags[name] = value 

return self[name] 

 

@classmethod 

def fromFitsHeader(cls, header): 

"""Read from a FITS header 

 

Parameters 

---------- 

header : `dict` 

FITS header keyword-value pairs. 

 

Returns 

------- 

self : `MaskHelper` 

Constructed mask helper. 

""" 

maskPlanes = {} 

for key, value in header.items(): 

if key.startswith(cls.maskPlanePrefix) or key.startswith("HIERARCH " + cls.maskPlanePrefix): 

name = key[key.rfind(cls.maskPlanePrefix) + len(cls.maskPlanePrefix):] 

maskPlanes[name] = value 

continue 

return cls(**maskPlanes) 

 

def toFitsHeader(self): 

"""Write to a FITS header 

 

Returns 

------- 

header : `dict` 

FITS header keyword-value pairs. 

""" 

return {self.maskPlanePrefix + key: value for key, value in self.flags.items()} 

 

@classmethod 

def fromMerge(cls, helpers): 

"""Construct from multiple `MaskHelper`s 

 

There must be no discrepancies between the inputs. 

 

Parameters 

---------- 

helpers : iterable of `MaskHelper` 

`MaskHelper`s to merge. 

 

Returns 

------- 

self : `MaskHelper` 

Merged `MaskHelper`. 

""" 

maskPlanes = {} 

for hh in helpers: 

for name, value in hh.flags.items(): 

if name in maskPlanes: 

if maskPlanes[name] != value: 

raise RuntimeError("Cannot merge MaskHelpers due to mismatch: %s" % (name,)) 

else: 

maskPlanes[name] = value 

return cls(**maskPlanes) 

 

def interpret(self, value): 

"""Interpret a value from the mask 

 

Breaks down the provided value into the corresponding mask plane names. 

 

Parameters 

---------- 

value : `int` 

Value to interpret. 

 

names : `list` of `str` 

List of mask planes that are set in the provided value. 

""" 

return [nn for nn, vv in self.flags.items() if (value & 2**vv) != 0] 

 

def count(self, mask): 

"""Return counts of each mask plane 

 

Parameters 

---------- 

mask : `numpy.ndarray` 

Mask array. 

 

Returns 

------- 

counts : `dict` (`str`: `int`) 

Counts for each mask plane. An additional result indexed by an empty 

string corresponds to the number of pixels with no mask plane set. 

""" 

return {",".join(self.interpret(value)): num for value, num in Counter(mask.flatten()).items()}