Coverage for python/astro_metadata_translator/observationGroup.py: 27%

Shortcuts 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

72 statements  

1# This file is part of astro_metadata_translator. 

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 LICENSE file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12"""Represent a collection of translated headers""" 

13 

14__all__ = ("ObservationGroup",) 

15 

16import logging 

17from collections.abc import MutableSequence 

18 

19from .observationInfo import ObservationInfo 

20 

21log = logging.getLogger(__name__) 

22 

23 

24class ObservationGroup(MutableSequence): 

25 """A collection of `ObservationInfo` headers. 

26 

27 Parameters 

28 ---------- 

29 members : iterable of `ObservationInfo` or `dict`-like 

30 `ObservationInfo` to seed the group membership. If `dict`-like 

31 values are used they will be passed to the `ObservationInfo` 

32 constructor. 

33 translator_class : `MetadataTranslator`-class, optional 

34 If any of the members is not an `ObservationInfo`, translator class 

35 to pass to the `ObservationInfo` constructor. If `None` the 

36 translation class will be determined automatically. 

37 pedantic : `bool`, optional 

38 If any of the members is not an `ObservationInfo`, passed to the 

39 `ObservationInfo` constructor to control whether 

40 a failed translation is fatal or not. `None` indicates that the 

41 `ObservationInfo` constructor default should be used. 

42 """ 

43 

44 def __init__(self, members, translator_class=None, pedantic=None): 

45 self._members = [self._coerce_value(m, translator_class=translator_class, pedantic=pedantic) 

46 for m in members] 

47 

48 # Cache of members in time order 

49 self._sorted = None 

50 

51 def __len__(self): 

52 return len(self._members) 

53 

54 def __delitem__(self, index): 

55 del self._members[index] 

56 self._sorted = None 

57 

58 def __getitem__(self, index): 

59 return self._members[index] 

60 

61 def __str__(self): 

62 results = [] 

63 for obs_info in self._members: 

64 results.append(f"({obs_info.instrument}, {obs_info.datetime_begin})") 

65 return "[" + ", ".join(results) + "]" 

66 

67 def _coerce_value(self, value, translator_class=None, pedantic=None): 

68 """Given a value, ensure it is an `ObservationInfo`. 

69 

70 Parameters 

71 ---------- 

72 value : `ObservationInfo` or `dict`-like 

73 Either an `ObservationInfo` or something that can be passed to 

74 an `ObservationInfo` constructor. 

75 translator_class : `MetadataTranslator`-class, optional 

76 If value is not an `ObservationInfo`, translator class to pass to 

77 the `ObservationInfo` constructor. If `None` the 

78 translation class will be determined automatically. 

79 pedantic : `bool`, optional 

80 If value is not an `ObservationInfo`, passed to the 

81 `ObservationInfo` constructor to control whether 

82 a failed translation is fatal or not. `None` indicates that the 

83 `ObservationInfo` constructor default should be used. 

84 

85 Raises 

86 ------ 

87 ValueError 

88 Raised if supplied value is not an `ObservationInfo` and can 

89 not be turned into one. 

90 """ 

91 if value is None: 

92 raise ValueError("An ObservationGroup cannot contain 'None'") 

93 

94 if not isinstance(value, ObservationInfo): 

95 try: 

96 kwargs = {"translator_class": translator_class} 

97 if pedantic is not None: 

98 kwargs["pedantic"] = pedantic 

99 value = ObservationInfo(value, **kwargs) 

100 except Exception as e: 

101 raise ValueError("Could not convert value to ObservationInfo") from e 

102 

103 return value 

104 

105 def __iter__(self): 

106 return iter(self._members) 

107 

108 def __eq__(self, other): 

109 """Compares equal if all the members are equal in the same order. 

110 """ 

111 if not isinstance(other, ObservationGroup): 

112 return NotImplemented 

113 

114 for info1, info2 in zip(self, other): 

115 if info1 != info2: 

116 return False 

117 return True 

118 

119 def __setitem__(self, index, value): 

120 """Store item in group. 

121 

122 Item must be an `ObservationInfo` or something that can be passed 

123 to an `ObservationInfo` constructor. 

124 """ 

125 value = self._coerce_value(value) 

126 self._members[index] = value 

127 self._sorted = None 

128 

129 def insert(self, index, value): 

130 value = self._coerce_value(value) 

131 self._members.insert(index, value) 

132 self._sorted = None 

133 

134 def reverse(self): 

135 self._members.reverse() 

136 

137 def sort(self, key=None, reverse=False): 

138 self._members.sort(key=key, reverse=reverse) 

139 if key is None and not reverse and self._sorted is None: 

140 # Store sorted order in cache 

141 self._sorted = self._members.copy() 

142 

143 def extremes(self): 

144 """Return the oldest observation in the group and the newest. 

145 

146 If there is only one member of the group, the newest and oldest 

147 can be the same observation. 

148 

149 Returns 

150 ------- 

151 oldest : `ObservationInfo` 

152 Oldest observation. 

153 newest : `ObservationInfo` 

154 Newest observation. 

155 """ 

156 if self._sorted is None: 

157 self._sorted = sorted(self._members) 

158 return self._sorted[0], self._sorted[-1] 

159 

160 def newest(self): 

161 """Return the newest observation in the group. 

162 

163 Returns 

164 ------- 

165 newest : `ObservationInfo` 

166 The newest `ObservationInfo` in the `ObservationGroup`. 

167 """ 

168 return self.extremes()[1] 

169 

170 def oldest(self): 

171 """Return the oldest observation in the group. 

172 

173 Returns 

174 ------- 

175 oldest : `ObservationInfo` 

176 The oldest `ObservationInfo` in the `ObservationGroup`. 

177 """ 

178 return self.extremes()[0] 

179 

180 def property_values(self, property): 

181 """Return a set of values associated with the specified property. 

182 

183 Parameters 

184 ---------- 

185 property : `str` 

186 Property of an `ObservationInfo` 

187 

188 Returns 

189 ------- 

190 values : `set` 

191 All the distinct values for that property within this group. 

192 """ 

193 return {getattr(obs_info, property) for obs_info in self} 

194 

195 def to_simple(self): 

196 """Convert the group to simplified form. 

197 

198 Returns 

199 ------- 

200 simple : `list` of `dict` 

201 Simple form is a list containing the simplified dict form of 

202 each `ObservationInfo`. 

203 """ 

204 return [obsinfo.to_simple() for obsinfo in self] 

205 

206 @classmethod 

207 def from_simple(cls, simple): 

208 """Convert simplified form back to `ObservationGroup` 

209 

210 Parameters 

211 ---------- 

212 simple : `list` of `dict` 

213 Object returned by `to_simple`. 

214 

215 Returns 

216 ------- 

217 group : `ObservationGroup` 

218 Reconstructed group. 

219 """ 

220 return cls((ObservationInfo.from_simple(o) for o in simple))