Coverage for tests/test_measurements.py: 15%

159 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-08 04:06 -0700

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 unittest 

25import yaml 

26import numpy as np 

27 

28import astropy.units as u 

29from astropy.tests.helper import quantity_allclose 

30 

31from lsst.utils.tests import TestCase 

32from lsst.verify import Measurement, Metric, Name, Blob, BlobSet, Datum 

33 

34 

35class MeasurementTestCase(TestCase): 

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

37 

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) 

48 

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

52 

53 self.blob2 = Blob('Blob2') 

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

55 

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) 

61 

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

63 

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) 

69 

70 # Test blob access 

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

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

73 

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

79 

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) 

105 

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

117 

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) 

129 

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) 

133 

134 self.assertIsInstance(measurement.metric_name, Name) 

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

136 self.assertIsNone(measurement.metric) 

137 

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) 

142 

143 new_measurement = Measurement.deserialize(**json_doc) 

144 self.assertEqual(measurement, new_measurement) 

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

146 

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) 

150 

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

157 

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) 

161 

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

168 

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) 

172 

173 self.assertIsNone(measurement.metric) 

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

175 

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 

180 

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) 

185 

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

192 

193 with self.assertRaises(TypeError): 

194 # wrong units 

195 measurement.quantity = 5 * u.arcsec 

196 

197 measurement.quantity = 5 * u.mmag 

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

199 

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

205 

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

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

208 

209 json_doc = measurement.json 

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

211 

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) 

217 

218 def test_deferred_extras(self): 

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

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

221 

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

223 

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

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

226 

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) 

238 

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

244 

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

254 

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 ) 

260 

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 ) 

269 

270 def _check_yaml_round_trip(self, old_measurement): 

271 persisted = yaml.dump(old_measurement) 

272 new_measurement = yaml.safe_load(persisted) 

273 

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) 

282 

283 def test_yamlpersist_basic(self): 

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

285 self._check_yaml_round_trip(measurement) 

286 

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) 

296 

297 

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

299 unittest.main()