Coverage for python/lsst/verify/measurementset.py: 20%

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

89 statements  

1# This file is part of verify. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21__all__ = ['MeasurementSet'] 

22 

23from .measurement import Measurement 

24from .naming import Name 

25from .jsonmixin import JsonSerializationMixin 

26 

27 

28class MeasurementSet(JsonSerializationMixin): 

29 r"""A collection of `~lsst.verify.Measurement`\ s of 

30 `~lsst.verify.Metric`\ s. 

31 

32 ``MeasurementSet`` provides a dict-like interface for getting, setting, 

33 and iterating over `Measurement`\ s. 

34 

35 Parameters 

36 ---------- 

37 measurements : `list` of `lsst.verify.Measurement`\ s 

38 Measurements to include in the set. 

39 """ 

40 

41 def __init__(self, measurements=None): 

42 self._items = {} 

43 if measurements is not None: 

44 for measurement in measurements: 

45 self[measurement.metric_name] = measurement 

46 

47 @classmethod 

48 def deserialize(cls, measurements=None, blob_set=None, metric_set=None): 

49 """Create a measurement set from a parsed JSON dataset. 

50 

51 Parameters 

52 ---------- 

53 measurements : `list`, optional 

54 A list of `Measurement` JSON serializations. 

55 blob_set : `BlobSet`, optional 

56 A `BlobSet` instance that support measurement deserialization. 

57 metric_set : `MetricSet`, optional 

58 A `MetricSet` that supports measurement deserialization. If 

59 provided, measurements are validated for unit consistency 

60 with metric definitions. `Measurement` instances also gain a 

61 `Measurement.metric` attribute. 

62 

63 Returns 

64 ------- 

65 instance : `MeasurementSet` 

66 A `MeasurementSet` instance. 

67 """ 

68 instance = cls() 

69 

70 if measurements is None: 

71 measurements = [] 

72 

73 if len(metric_set) == 0: 

74 # Job.deserialize may pass an empty MetricSet, so ignore that 

75 metric_set = None 

76 

77 for meas_doc in measurements: 

78 if metric_set is not None: 

79 try: 

80 metric = metric_set[meas_doc['metric']] 

81 meas_doc['metric'] = metric 

82 except KeyError: 

83 # metric not in the MetricSet, but it's optional 

84 pass 

85 meas = Measurement.deserialize(blobs=blob_set, **meas_doc) 

86 instance.insert(meas) 

87 return instance 

88 

89 def __getitem__(self, key): 

90 if not isinstance(key, Name): 

91 key = Name(metric=key) 

92 

93 return self._items[key] 

94 

95 def __setitem__(self, key, value): 

96 if not isinstance(key, Name): 

97 key = Name(metric=key) 

98 

99 if not key.is_metric: 

100 raise KeyError('Key {0} is not a metric name'.format(key)) 

101 

102 if not isinstance(value, Measurement): 

103 message = ('Measurement {0} is not a ' 

104 'lsst.verify.Measurement-type') 

105 raise TypeError(message.format(value)) 

106 

107 if key != value.metric_name: 

108 message = ("Key {0} is inconsistent with the measurement's " 

109 "metric name, {1}") 

110 raise KeyError(message.format(key, value.metric_name)) 

111 

112 self._items[key] = value 

113 

114 def __len__(self): 

115 return len(self._items) 

116 

117 def __contains__(self, key): 

118 if not isinstance(key, Name): 

119 key = Name(metric=key) 

120 

121 return key in self._items 

122 

123 def __delitem__(self, key): 

124 if not isinstance(key, Name): 

125 key = Name(metric=key) 

126 

127 del self._items[key] 

128 

129 def __iter__(self): 

130 for key in self._items: 

131 yield key 

132 

133 def __eq__(self, other): 

134 return self._items == other._items 

135 

136 def __ne__(self, other): 

137 return not self.__eq__(other) 

138 

139 def __iadd__(self, other): 

140 """Merge another `MeasurementSet` into this one. 

141 

142 Parameters 

143 ---------- 

144 other : `MeasurementSet` 

145 Another `MeasurementSet`. Measurements in ``other`` that do 

146 exist in this set are added to this one. Measurements in 

147 ``other`` replace measurements of the same metric in this one. 

148 

149 Returns 

150 ------- 

151 self : `MeasurementSet` 

152 This `MeasurementSet`. 

153 

154 Notes 

155 ----- 

156 Equivalent to `update`. 

157 """ 

158 self.update(other) 

159 return self 

160 

161 def __str__(self): 

162 count = len(self) 

163 if count == 0: 

164 count_str = 'empty' 

165 elif count == 1: 

166 count_str = '1 Measurement' 

167 else: 

168 count_str = '{count:d} Measurements'.format(count=count) 

169 return '<MeasurementSet: {0}>'.format(count_str) 

170 

171 def keys(self): 

172 """Get a sequence of metric names contained in the measurement set. 

173 

174 Returns 

175 ------- 

176 keys : sequence of `Name` 

177 Sequence of names of metrics for measurements in the set. 

178 """ 

179 return self._items.keys() 

180 

181 def items(self): 

182 """Iterete over (`Name`, `Measurement`) pairs in the set. 

183 

184 Yields 

185 ------ 

186 item : `tuple` 

187 Tuple containing: 

188 

189 - `Name` of the measurement's `Metric`. 

190 - `Measurement` instance. 

191 """ 

192 for item in self._items.items(): 

193 yield item 

194 

195 def insert(self, measurement): 

196 """Insert a measurement into the set.""" 

197 self[measurement.metric_name] = measurement 

198 

199 def update(self, other): 

200 """Merge another `MeasurementSet` into this one. 

201 

202 Parameters 

203 ---------- 

204 other : `MeasurementSet` 

205 Another `MeasurementSet`. Measurements in ``other`` that do 

206 exist in this set are added to this one. Measurements in 

207 ``other`` replace measurements of the same metric in this one. 

208 """ 

209 for _, measurement in other.items(): 

210 self.insert(measurement) 

211 

212 def refresh_metrics(self, metric_set): 

213 r"""Refresh `Measurement.metric` attributes in `Measurement`\ s 

214 contained by this set. 

215 

216 Parameters 

217 ---------- 

218 metric_set : `MetricSet` 

219 `Metric`\ s from this set are inserted into corresponding 

220 `Measurement`\ s contained in this `MeasurementSet`. 

221 

222 Notes 

223 ----- 

224 This method is especially useful for inserting `Metric` instances into 

225 `Measurement`\ s that weren't originally created with `Metric` 

226 instances. By including a `Metric` in a `Measurement`, the serialized 

227 units of a measurment are normalized to the metric's definition. 

228 

229 See also 

230 -------- 

231 lsst.verify.Job.reload_metrics_package 

232 """ 

233 for metric_name, measurement in self.items(): 

234 if metric_name in metric_set: 

235 measurement.metric = metric_set[metric_name] 

236 

237 @property 

238 def json(self): 

239 """A `dict` that can be serialized as JSON.""" 

240 json_doc = JsonSerializationMixin._jsonify_list( 

241 [meas for name, meas in self.items()] 

242 ) 

243 return json_doc