Hide keyboard shortcuts

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# 

23 

24import shutil 

25import unittest 

26import tempfile 

27import yaml 

28 

29import astropy.units as u 

30from astropy.tests.helper import quantity_allclose 

31 

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 

35 

36 

37class MeasurementTestCase(TestCase): 

38 """Test lsst.verify.measurment.Measurement class.""" 

39 

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) 

50 

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') 

54 

55 self.blob2 = Blob('Blob2') 

56 self.blob2['datumN'] = Datum(11 * u.dimensionless_unscaled, 'Count') 

57 

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) 

63 

64 measurement2 = Measurement(self.pa1, 0.002 * u.mag) 

65 

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) 

71 

72 # Test blob access 

73 self.assertIn('Blob1', measurement.blobs) 

74 self.assertIn('Blob2', measurement.blobs) 

75 

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)) 

81 

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) 

107 

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']) 

119 

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) 

131 

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) 

135 

136 self.assertIsInstance(measurement.metric_name, Name) 

137 self.assertEqual(measurement.metric_name, Name('validate_drp.PA1')) 

138 self.assertIsNone(measurement.metric) 

139 

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) 

144 

145 new_measurement = Measurement.deserialize(**json_doc) 

146 self.assertEqual(measurement, new_measurement) 

147 self.assertEqual(measurement.identifier, new_measurement.identifier) 

148 

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) 

152 

153 self.assertIsNone(measurement.metric) 

154 self.assertEqual(measurement.metric_name, Name(metric='PA1')) 

155 

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 

160 

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) 

165 

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']) 

172 

173 with self.assertRaises(TypeError): 

174 # wrong units 

175 measurement.quantity = 5 * u.arcsec 

176 

177 measurement.quantity = 5 * u.mmag 

178 quantity_allclose(measurement.quantity, 5 * u.mmag) 

179 

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')}) 

185 

186 self.assertIn(str(self.pa1.name), measurement.blobs) 

187 self.assertIn('extra1', measurement.extras) 

188 

189 json_doc = measurement.json 

190 self.assertIn(measurement.extras.identifier, json_doc['blob_refs']) 

191 

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) 

197 

198 def test_deferred_extras(self): 

199 """Test adding extras to an existing measurement.""" 

200 measurement = Measurement(self.pa1, 5. * u.mmag) 

201 

202 self.assertIn(str(self.pa1.name), measurement.blobs) 

203 

204 measurement.extras['extra1'] = Datum(10. * u.arcmin, 'Extra 1') 

205 self.assertIn('extra1', measurement.extras) 

206 

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) 

218 

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") 

224 

225 def _check_yaml_round_trip(self, old_measurement): 

226 persisted = yaml.dump(old_measurement) 

227 new_measurement = yaml.safe_load(persisted) 

228 

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) 

237 

238 def test_yamlpersist_basic(self): 

239 measurement = Measurement('validate_drp.PA1', 0.002 * u.mag) 

240 self._check_yaml_round_trip(measurement) 

241 

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) 

251 

252 def test_butler(self): 

253 CAMERA_ID = "NotACam" 

254 MAP_ID = "map" 

255 TRACT_ID = 5 

256 PATCH_IDS = [42, 43] 

257 

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") 

284 

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) 

296 

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) 

307 

308 @staticmethod 

309 def _get_dataset_name(metric_name): 

310 """Get the dataset type corresponding to a metric. 

311 

312 Parameters 

313 ---------- 

314 metric_name : `lsst.verify.Name` or `str` 

315 The name of the metric to store or retrieve. 

316 

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(".", "_") 

325 

326 

327if __name__ == "__main__": 327 ↛ 328line 327 didn't jump to line 328, because the condition on line 327 was never true

328 unittest.main()