Coverage for tests/test_measurements.py: 14%
159 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-18 02:04 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-18 02:04 -0800
1#
2# LSST Data Management System
3#
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6#
7# See COPYRIGHT file at the top of the source tree.
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 LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
24import unittest
25import yaml
26import numpy as np
28import astropy.units as u
29from astropy.tests.helper import quantity_allclose
31from lsst.utils.tests import TestCase
32from lsst.verify import Measurement, Metric, Name, Blob, BlobSet, Datum
35class MeasurementTestCase(TestCase):
36 """Test lsst.verify.measurment.Measurement class."""
38 def setUp(self):
39 self.pa1 = Metric(
40 'validate_drp.PA1',
41 "The maximum rms of the unresolved source magnitude distribution "
42 "around the mean value (repeatability).",
43 'mmag',
44 tags=['photometric', 'LPM-17'],
45 reference_doc='LPM-17',
46 reference_url='http://ls.st/lpm-17',
47 reference_page=21)
49 self.blob1 = Blob('Blob1')
50 self.blob1['datum1'] = Datum(5 * u.arcsec, 'Datum 1')
51 self.blob1['datum2'] = Datum(28. * u.mag, 'Datum 2')
53 self.blob2 = Blob('Blob2')
54 self.blob2['datumN'] = Datum(11 * u.dimensionless_unscaled, 'Count')
56 def test_PA1_measurement_with_metric(self):
57 """Standard metric with a given Metric instance."""
58 measurement = Measurement(self.pa1, 0.002 * u.mag, blobs=[self.blob1],
59 notes={'filter_name': 'r'})
60 measurement.link_blob(self.blob2)
62 measurement2 = Measurement(self.pa1, 0.002 * u.mag)
64 self.assertTrue(quantity_allclose(measurement.quantity, 0.002 * u.mag))
65 self.assertIsInstance(measurement.metric_name, Name)
66 self.assertEqual(measurement.metric_name, Name('validate_drp.PA1'))
67 self.assertEqual(measurement.metric, self.pa1)
68 self.assertNotEqual(measurement.identifier, measurement2.identifier)
70 # Test blob access
71 self.assertIn('Blob1', measurement.blobs)
72 self.assertIn('Blob2', measurement.blobs)
74 # Test Datum representation
75 datum = measurement.datum
76 self.assertTrue(quantity_allclose(datum.quantity, 0.002 * u.mag))
77 self.assertEqual(datum.label, str(self.pa1.name))
78 self.assertEqual(datum.description, str(self.pa1.description))
80 # Test notes (MeasurementNotes)
81 self.assertEqual(measurement.notes['filter_name'], 'r')
82 # Add a note
83 measurement.notes['camera'] = 'MegaCam'
84 self.assertEqual(measurement.notes['camera'], 'MegaCam')
85 self.assertEqual(len(measurement.notes), 2)
86 self.assertIn('camera', measurement.notes)
87 self.assertIn('filter_name', measurement.notes)
88 # Prefixed keys
89 self.assertIn('validate_drp.PA1.camera', measurement.notes)
90 # test iteration
91 iterkeys = set([key for key in measurement.notes])
92 self.assertEqual(len(iterkeys), 2)
93 self.assertEqual(set(iterkeys), set(measurement.notes.keys()))
94 itemkeys = set()
95 for key, value in measurement.notes.items():
96 self.assertEqual(measurement.notes[key], value)
97 itemkeys.add(key)
98 self.assertEqual(itemkeys, iterkeys)
99 # Test update
100 measurement.notes.update({'photometric': True, 'facility': 'CFHT'})
101 self.assertIn('photometric', measurement.notes)
102 # Test delete
103 del measurement.notes['photometric']
104 self.assertNotIn('photometric', measurement.notes)
106 # Test serialization
107 json_doc = measurement.json
108 # Units should be cast to those of the metric
109 self.assertEqual(json_doc['unit'], 'mmag')
110 self.assertFloatsAlmostEqual(json_doc['value'], 2.0)
111 self.assertEqual(json_doc['identifier'], measurement.identifier)
112 self.assertIsInstance(json_doc['blob_refs'], list)
113 self.assertIn(self.blob1.identifier, json_doc['blob_refs'])
114 self.assertIn(self.blob2.identifier, json_doc['blob_refs'])
115 # No extras, so should not be serialized
116 self.assertNotIn(measurement.extras.identifier, json_doc['blob_refs'])
118 # Test deserialization
119 new_measurement = Measurement.deserialize(
120 blobs=BlobSet([self.blob1, self.blob2]),
121 **json_doc)
122 # shim in original notes; normally these are deserialized via the
123 # Job object.
124 new_measurement.notes.update(measurement.notes)
125 self.assertEqual(measurement, new_measurement)
126 self.assertEqual(measurement.identifier, new_measurement.identifier)
127 self.assertIn('Blob1', measurement.blobs)
128 self.assertIn('Blob2', measurement.blobs)
130 def test_PA1_measurement_without_metric(self):
131 """Test a measurement without a Metric instance."""
132 measurement = Measurement('validate_drp.PA1', 0.002 * u.mag)
134 self.assertIsInstance(measurement.metric_name, Name)
135 self.assertEqual(measurement.metric_name, Name('validate_drp.PA1'))
136 self.assertIsNone(measurement.metric)
138 json_doc = measurement.json
139 # Units are not converted
140 self.assertEqual(json_doc['unit'], 'mag')
141 self.assertFloatsAlmostEqual(json_doc['value'], 0.002)
143 new_measurement = Measurement.deserialize(**json_doc)
144 self.assertEqual(measurement, new_measurement)
145 self.assertEqual(measurement.identifier, new_measurement.identifier)
147 def test_PA1_measurement_with_nan(self):
148 """Test (de)serialization of a measurement with value np.nan."""
149 measurement = Measurement('PA1', np.nan * u.mag)
151 json_doc = measurement.json
152 # a np.nan value is serialized to None
153 self.assertEqual(json_doc['value'], None)
154 # a None value is deserialized to np.nan
155 new_measurement = Measurement.deserialize(**json_doc)
156 self.assertEqual(str(new_measurement), 'PA1: nan mag')
158 def test_PA1_measurement_with_inf(self):
159 """Test (de)serialization of a measurement with value np.inf."""
160 measurement = Measurement('PA1', np.inf * u.mag)
162 json_doc = measurement.json
163 # a np.inf value is also serialized to None
164 self.assertEqual(json_doc['value'], None)
165 # but once it is None, we can only deserialized it to np.nan
166 new_measurement = Measurement.deserialize(**json_doc)
167 self.assertEqual(str(new_measurement), 'PA1: nan mag')
169 def test_PA1_deferred_metric(self):
170 """Test a measurement when the Metric instance is added later."""
171 measurement = Measurement('PA1', 0.002 * u.mag)
173 self.assertIsNone(measurement.metric)
174 self.assertEqual(measurement.metric_name, Name(metric='PA1'))
176 # Try adding in a metric with the wrong units to existing quantity
177 other_metric = Metric('testing.other', 'Incompatible units', 'arcsec')
178 with self.assertRaises(TypeError):
179 measurement.metric = other_metric
181 # Add metric in; the name should also update
182 measurement.metric = self.pa1
183 self.assertEqual(measurement.metric, self.pa1)
184 self.assertEqual(measurement.metric_name, self.pa1.name)
186 def test_PA1_deferred_quantity(self):
187 """Test a measurement where the quantity is added later."""
188 measurement = Measurement(self.pa1)
189 json_doc = measurement.json
190 self.assertIsNone(json_doc['unit'])
191 self.assertIsNone(json_doc['value'])
193 with self.assertRaises(TypeError):
194 # wrong units
195 measurement.quantity = 5 * u.arcsec
197 measurement.quantity = 5 * u.mmag
198 quantity_allclose(measurement.quantity, 5 * u.mmag)
200 def test_creation_with_extras(self):
201 """Test creating a measurement with an extra."""
202 measurement = Measurement(
203 self.pa1, 5. * u.mmag,
204 extras={'extra1': Datum(10. * u.arcmin, 'Extra 1')})
206 self.assertIn(str(self.pa1.name), measurement.blobs)
207 self.assertIn('extra1', measurement.extras)
209 json_doc = measurement.json
210 self.assertIn(measurement.extras.identifier, json_doc['blob_refs'])
212 blobs = BlobSet([b for k, b in measurement.blobs.items()])
213 new_measurement = Measurement.deserialize(blobs=blobs, **json_doc)
214 self.assertIn('extra1', new_measurement.extras)
215 self.assertEqual(measurement, new_measurement)
216 self.assertEqual(measurement.identifier, new_measurement.identifier)
218 def test_deferred_extras(self):
219 """Test adding extras to an existing measurement."""
220 measurement = Measurement(self.pa1, 5. * u.mmag)
222 self.assertIn(str(self.pa1.name), measurement.blobs)
224 measurement.extras['extra1'] = Datum(10. * u.arcmin, 'Extra 1')
225 self.assertIn('extra1', measurement.extras)
227 def test_quantity_coercion(self):
228 # strings can't be changed into a Quantity
229 with self.assertRaises(TypeError):
230 Measurement('test_metric', quantity='hello')
231 # objects can't be a Quantity
232 with self.assertRaises(TypeError):
233 Measurement('test_metric', quantity=int)
234 m = Measurement('test_metric', quantity=5)
235 self.assertEqual(m.quantity, 5)
236 m = Measurement('test_metric', quantity=5.1)
237 self.assertEqual(m.quantity, 5.1)
239 def test_str(self):
240 metric = 'test.cmodel_mag'
241 value = 1235 * u.mag
242 m = Measurement(metric, value)
243 self.assertEqual(str(m), "test.cmodel_mag: 1235.0 mag")
245 def test_repr(self):
246 metric = 'test.cmodel_mag'
247 self.assertEqual(
248 repr(Measurement(metric)),
249 "Measurement('test.cmodel_mag', None)")
250 value = 1235 * u.mag
251 self.assertEqual(
252 repr(Measurement(metric, value)),
253 "Measurement('test.cmodel_mag', <Quantity 1235. mag>)")
255 self.assertEqual(
256 repr(Measurement(metric, value, [self.blob1])),
257 "Measurement('test.cmodel_mag', <Quantity 1235. mag>, "
258 f"blobs=[{self.blob1!r}])"
259 )
261 notes = {metric + '.filter_name': 'r'}
262 extras = {'extra1': Datum(10. * u.arcmin, 'Extra 1')}
263 self.assertEqual(
264 repr(Measurement(metric, value, notes=notes, blobs=[self.blob1],
265 extras=extras)),
266 "Measurement('test.cmodel_mag', <Quantity 1235. mag>, "
267 f"blobs=[{self.blob1!r}], extras={extras!r}, notes={notes!r})"
268 )
270 def _check_yaml_round_trip(self, old_measurement):
271 persisted = yaml.dump(old_measurement)
272 new_measurement = yaml.safe_load(persisted)
274 self.assertEqual(old_measurement, new_measurement)
275 # These fields don't participate in Measurement equality
276 self.assertEqual(old_measurement.identifier,
277 new_measurement.identifier)
278 self.assertEqual(old_measurement.blobs,
279 new_measurement.blobs)
280 self.assertEqual(old_measurement.extras,
281 new_measurement.extras)
283 def test_yamlpersist_basic(self):
284 measurement = Measurement('validate_drp.PA1', 0.002 * u.mag)
285 self._check_yaml_round_trip(measurement)
287 def test_yamlpersist_complex(self):
288 measurement = Measurement(
289 self.pa1,
290 5. * u.mmag,
291 notes={'filter_name': 'r'},
292 blobs=[self.blob1],
293 extras={'extra1': Datum(10. * u.arcmin, 'Extra 1')}
294 )
295 self._check_yaml_round_trip(measurement)
298if __name__ == "__main__": 298 ↛ 299line 298 didn't jump to line 299, because the condition on line 298 was never true
299 unittest.main()