Coverage for tests/test_measurements.py : 14%

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# 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 shutil
25import unittest
26import tempfile
27import yaml
29import astropy.units as u
30from astropy.tests.helper import quantity_allclose
32from lsst.utils.tests import TestCase
33from lsst.verify import Measurement, Metric, Name, Blob, BlobSet, Datum
34from butler_utils import make_test_butler, make_dataset_type
37class MeasurementTestCase(TestCase):
38 """Test lsst.verify.measurment.Measurement class."""
40 def setUp(self):
41 self.pa1 = Metric(
42 'validate_drp.PA1',
43 "The maximum rms of the unresolved source magnitude distribution "
44 "around the mean value (repeatability).",
45 'mmag',
46 tags=['photometric', 'LPM-17'],
47 reference_doc='LPM-17',
48 reference_url='http://ls.st/lpm-17',
49 reference_page=21)
51 self.blob1 = Blob('Blob1')
52 self.blob1['datum1'] = Datum(5 * u.arcsec, 'Datum 1')
53 self.blob1['datum2'] = Datum(28. * u.mag, 'Datum 2')
55 self.blob2 = Blob('Blob2')
56 self.blob2['datumN'] = Datum(11 * u.dimensionless_unscaled, 'Count')
58 def test_PA1_measurement_with_metric(self):
59 """Standard metric with a given Metric instance."""
60 measurement = Measurement(self.pa1, 0.002 * u.mag, blobs=[self.blob1],
61 notes={'filter_name': 'r'})
62 measurement.link_blob(self.blob2)
64 measurement2 = Measurement(self.pa1, 0.002 * u.mag)
66 self.assertTrue(quantity_allclose(measurement.quantity, 0.002 * u.mag))
67 self.assertIsInstance(measurement.metric_name, Name)
68 self.assertEqual(measurement.metric_name, Name('validate_drp.PA1'))
69 self.assertEqual(measurement.metric, self.pa1)
70 self.assertNotEqual(measurement.identifier, measurement2.identifier)
72 # Test blob access
73 self.assertIn('Blob1', measurement.blobs)
74 self.assertIn('Blob2', measurement.blobs)
76 # Test Datum representation
77 datum = measurement.datum
78 self.assertTrue(quantity_allclose(datum.quantity, 0.002 * u.mag))
79 self.assertEqual(datum.label, str(self.pa1.name))
80 self.assertEqual(datum.description, str(self.pa1.description))
82 # Test notes (MeasurementNotes)
83 self.assertEqual(measurement.notes['filter_name'], 'r')
84 # Add a note
85 measurement.notes['camera'] = 'MegaCam'
86 self.assertEqual(measurement.notes['camera'], 'MegaCam')
87 self.assertEqual(len(measurement.notes), 2)
88 self.assertIn('camera', measurement.notes)
89 self.assertIn('filter_name', measurement.notes)
90 # Prefixed keys
91 self.assertIn('validate_drp.PA1.camera', measurement.notes)
92 # test iteration
93 iterkeys = set([key for key in measurement.notes])
94 self.assertEqual(len(iterkeys), 2)
95 self.assertEqual(set(iterkeys), set(measurement.notes.keys()))
96 itemkeys = set()
97 for key, value in measurement.notes.items():
98 self.assertEqual(measurement.notes[key], value)
99 itemkeys.add(key)
100 self.assertEqual(itemkeys, iterkeys)
101 # Test update
102 measurement.notes.update({'photometric': True, 'facility': 'CFHT'})
103 self.assertIn('photometric', measurement.notes)
104 # Test delete
105 del measurement.notes['photometric']
106 self.assertNotIn('photometric', measurement.notes)
108 # Test serialization
109 json_doc = measurement.json
110 # Units should be cast to those of the metric
111 self.assertEqual(json_doc['unit'], 'mmag')
112 self.assertFloatsAlmostEqual(json_doc['value'], 2.0)
113 self.assertEqual(json_doc['identifier'], measurement.identifier)
114 self.assertIsInstance(json_doc['blob_refs'], list)
115 self.assertIn(self.blob1.identifier, json_doc['blob_refs'])
116 self.assertIn(self.blob2.identifier, json_doc['blob_refs'])
117 # No extras, so should not be serialized
118 self.assertNotIn(measurement.extras.identifier, json_doc['blob_refs'])
120 # Test deserialization
121 new_measurement = Measurement.deserialize(
122 blobs=BlobSet([self.blob1, self.blob2]),
123 **json_doc)
124 # shim in original notes; normally these are deserialized via the
125 # Job object.
126 new_measurement.notes.update(measurement.notes)
127 self.assertEqual(measurement, new_measurement)
128 self.assertEqual(measurement.identifier, new_measurement.identifier)
129 self.assertIn('Blob1', measurement.blobs)
130 self.assertIn('Blob2', measurement.blobs)
132 def test_PA1_measurement_without_metric(self):
133 """Test a measurement without a Metric instance."""
134 measurement = Measurement('validate_drp.PA1', 0.002 * u.mag)
136 self.assertIsInstance(measurement.metric_name, Name)
137 self.assertEqual(measurement.metric_name, Name('validate_drp.PA1'))
138 self.assertIsNone(measurement.metric)
140 json_doc = measurement.json
141 # Units are not converted
142 self.assertEqual(json_doc['unit'], 'mag')
143 self.assertFloatsAlmostEqual(json_doc['value'], 0.002)
145 new_measurement = Measurement.deserialize(**json_doc)
146 self.assertEqual(measurement, new_measurement)
147 self.assertEqual(measurement.identifier, new_measurement.identifier)
149 def test_PA1_deferred_metric(self):
150 """Test a measurement when the Metric instance is added later."""
151 measurement = Measurement('PA1', 0.002 * u.mag)
153 self.assertIsNone(measurement.metric)
154 self.assertEqual(measurement.metric_name, Name(metric='PA1'))
156 # Try adding in a metric with the wrong units to existing quantity
157 other_metric = Metric('testing.other', 'Incompatible units', 'arcsec')
158 with self.assertRaises(TypeError):
159 measurement.metric = other_metric
161 # Add metric in; the name should also update
162 measurement.metric = self.pa1
163 self.assertEqual(measurement.metric, self.pa1)
164 self.assertEqual(measurement.metric_name, self.pa1.name)
166 def test_PA1_deferred_quantity(self):
167 """Test a measurement where the quantity is added later."""
168 measurement = Measurement(self.pa1)
169 json_doc = measurement.json
170 self.assertIsNone(json_doc['unit'])
171 self.assertIsNone(json_doc['value'])
173 with self.assertRaises(TypeError):
174 # wrong units
175 measurement.quantity = 5 * u.arcsec
177 measurement.quantity = 5 * u.mmag
178 quantity_allclose(measurement.quantity, 5 * u.mmag)
180 def test_creation_with_extras(self):
181 """Test creating a measurement with an extra."""
182 measurement = Measurement(
183 self.pa1, 5. * u.mmag,
184 extras={'extra1': Datum(10. * u.arcmin, 'Extra 1')})
186 self.assertIn(str(self.pa1.name), measurement.blobs)
187 self.assertIn('extra1', measurement.extras)
189 json_doc = measurement.json
190 self.assertIn(measurement.extras.identifier, json_doc['blob_refs'])
192 blobs = BlobSet([b for k, b in measurement.blobs.items()])
193 new_measurement = Measurement.deserialize(blobs=blobs, **json_doc)
194 self.assertIn('extra1', new_measurement.extras)
195 self.assertEqual(measurement, new_measurement)
196 self.assertEqual(measurement.identifier, new_measurement.identifier)
198 def test_deferred_extras(self):
199 """Test adding extras to an existing measurement."""
200 measurement = Measurement(self.pa1, 5. * u.mmag)
202 self.assertIn(str(self.pa1.name), measurement.blobs)
204 measurement.extras['extra1'] = Datum(10. * u.arcmin, 'Extra 1')
205 self.assertIn('extra1', measurement.extras)
207 def test_quantity_coercion(self):
208 # strings can't be changed into a Quantity
209 with self.assertRaises(TypeError):
210 Measurement('test_metric', quantity='hello')
211 # objects can't be a Quantity
212 with self.assertRaises(TypeError):
213 Measurement('test_metric', quantity=int)
214 m = Measurement('test_metric', quantity=5)
215 self.assertEqual(m.quantity, 5)
216 m = Measurement('test_metric', quantity=5.1)
217 self.assertEqual(m.quantity, 5.1)
219 def test_str(self):
220 metric = 'test.cmodel_mag'
221 value = 1235 * u.mag
222 m = Measurement(metric, value)
223 self.assertEqual(str(m), "test.cmodel_mag: 1235.0 mag")
225 def _check_yaml_round_trip(self, old_measurement):
226 persisted = yaml.dump(old_measurement)
227 new_measurement = yaml.safe_load(persisted)
229 self.assertEqual(old_measurement, new_measurement)
230 # These fields don't participate in Measurement equality
231 self.assertEqual(old_measurement.identifier,
232 new_measurement.identifier)
233 self.assertEqual(old_measurement.blobs,
234 new_measurement.blobs)
235 self.assertEqual(old_measurement.extras,
236 new_measurement.extras)
238 def test_yamlpersist_basic(self):
239 measurement = Measurement('validate_drp.PA1', 0.002 * u.mag)
240 self._check_yaml_round_trip(measurement)
242 def test_yamlpersist_complex(self):
243 measurement = Measurement(
244 self.pa1,
245 5. * u.mmag,
246 notes={'filter_name': 'r'},
247 blobs=[self.blob1],
248 extras={'extra1': Datum(10. * u.arcmin, 'Extra 1')}
249 )
250 self._check_yaml_round_trip(measurement)
252 def test_butler(self):
253 CAMERA_ID = "NotACam"
254 MAP_ID = "map"
255 TRACT_ID = 5
256 PATCH_IDS = [42, 43]
258 root = tempfile.mkdtemp()
259 self.addCleanup(shutil.rmtree, root, ignore_errors=True)
260 butler = make_test_butler(
261 root,
262 {
263 "instrument": [{"name": CAMERA_ID}],
264 "skymap": [{"name": MAP_ID, "hash": bytes()}],
265 "tract": [{"id": TRACT_ID, "skymap": MAP_ID}],
266 "patch": [{"id": num,
267 "tract": TRACT_ID,
268 "skymap": MAP_ID,
269 "cell_x": 0, "cell_y": i}
270 for i, num in enumerate(PATCH_IDS)],
271 })
272 metric_coarse = "verify.dummyMetric"
273 make_dataset_type(
274 butler,
275 self._get_dataset_name(metric_coarse),
276 set(),
277 "MetricValue")
278 metric_fine = "verify.tractableMetric"
279 make_dataset_type(
280 butler,
281 self._get_dataset_name(metric_fine),
282 {"instrument", "skymap", "patch", "tract"},
283 "MetricValue")
285 initial_data = {
286 None: Measurement(metric_coarse, 0.052 * u.mag),
287 PATCH_IDS[0]: Measurement(metric_fine, 0.2 * u.arcsec),
288 PATCH_IDS[1]: Measurement(metric_fine, 0.5 * u.arcsec),
289 }
290 for patch, value in initial_data.items():
291 # Expect data ID keywords to be ignored for coarse metric
292 butler.put(
293 value, self._get_dataset_name(value.metric_name),
294 instrument=CAMERA_ID, skymap=MAP_ID, tract=TRACT_ID,
295 patch=patch)
297 for patch, value in initial_data.items():
298 dataset_name = self._get_dataset_name(value.metric_name)
299 if patch is None:
300 unpersisted = butler.get(dataset_name)
301 else:
302 unpersisted = butler.get(
303 dataset_name,
304 instrument=CAMERA_ID, skymap=MAP_ID, tract=TRACT_ID,
305 patch=patch)
306 self.assertEqual(unpersisted, value)
308 @staticmethod
309 def _get_dataset_name(metric_name):
310 """Get the dataset type corresponding to a metric.
312 Parameters
313 ----------
314 metric_name : `lsst.verify.Name` or `str`
315 The name of the metric to store or retrieve.
317 Returns
318 -------
319 dataset_type : `str`
320 The name of the corresponding dataset type.
321 """
322 raw_name = "metricvalue_%s" % metric_name
323 # avoid confusion with compound datasets
324 return raw_name.replace(".", "_")
327if __name__ == "__main__": 327 ↛ 328line 327 didn't jump to line 328, because the condition on line 327 was never true
328 unittest.main()