Coverage for python/lsst/verify/measurementset.py: 20%
89 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-11 06:50 +0000
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-11 06:50 +0000
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']
23from .measurement import Measurement
24from .naming import Name
25from .jsonmixin import JsonSerializationMixin
28class MeasurementSet(JsonSerializationMixin):
29 r"""A collection of `~lsst.verify.Measurement`\ s of
30 `~lsst.verify.Metric`\ s.
32 ``MeasurementSet`` provides a dict-like interface for getting, setting,
33 and iterating over `Measurement`\ s.
35 Parameters
36 ----------
37 measurements : `list` of `lsst.verify.Measurement`\ s
38 Measurements to include in the set.
39 """
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
47 @classmethod
48 def deserialize(cls, measurements=None, blob_set=None, metric_set=None):
49 """Create a measurement set from a parsed JSON dataset.
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.
63 Returns
64 -------
65 instance : `MeasurementSet`
66 A `MeasurementSet` instance.
67 """
68 instance = cls()
70 if measurements is None:
71 measurements = []
73 if len(metric_set) == 0:
74 # Job.deserialize may pass an empty MetricSet, so ignore that
75 metric_set = None
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
89 def __getitem__(self, key):
90 if not isinstance(key, Name):
91 key = Name(metric=key)
93 return self._items[key]
95 def __setitem__(self, key, value):
96 if not isinstance(key, Name):
97 key = Name(metric=key)
99 if not key.is_metric:
100 raise KeyError('Key {0} is not a metric name'.format(key))
102 if not isinstance(value, Measurement):
103 message = ('Measurement {0} is not a '
104 'lsst.verify.Measurement-type')
105 raise TypeError(message.format(value))
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))
112 self._items[key] = value
114 def __len__(self):
115 return len(self._items)
117 def __contains__(self, key):
118 if not isinstance(key, Name):
119 key = Name(metric=key)
121 return key in self._items
123 def __delitem__(self, key):
124 if not isinstance(key, Name):
125 key = Name(metric=key)
127 del self._items[key]
129 def __iter__(self):
130 for key in self._items:
131 yield key
133 def __eq__(self, other):
134 return self._items == other._items
136 def __ne__(self, other):
137 return not self.__eq__(other)
139 def __iadd__(self, other):
140 """Merge another `MeasurementSet` into this one.
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.
149 Returns
150 -------
151 self : `MeasurementSet`
152 This `MeasurementSet`.
154 Notes
155 -----
156 Equivalent to `update`.
157 """
158 self.update(other)
159 return self
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)
171 def keys(self):
172 """Get a sequence of metric names contained in the measurement set.
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()
181 def items(self):
182 """Iterete over (`Name`, `Measurement`) pairs in the set.
184 Yields
185 ------
186 item : `tuple`
187 Tuple containing:
189 - `Name` of the measurement's `Metric`.
190 - `Measurement` instance.
191 """
192 for item in self._items.items():
193 yield item
195 def insert(self, measurement):
196 """Insert a measurement into the set."""
197 self[measurement.metric_name] = measurement
199 def update(self, other):
200 """Merge another `MeasurementSet` into this one.
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)
212 def refresh_metrics(self, metric_set):
213 r"""Refresh `Measurement.metric` attributes in `Measurement`\ s
214 contained by this set.
216 Parameters
217 ----------
218 metric_set : `MetricSet`
219 `Metric`\ s from this set are inserted into corresponding
220 `Measurement`\ s contained in this `MeasurementSet`.
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.
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]
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