Coverage for tests/test_trailedEdgeSources.py: 18%

193 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-11 04:01 -0700

1# 

2# This file is part of meas_extensions_trailedSources. 

3# 

4# Developed for the LSST Data Management System. 

5# This product includes software developed by the LSST Project 

6# (http://www.lsst.org). 

7# See the COPYRIGHT file at the top-level directory of this distribution 

8# for details of code ownership. 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <http://www.gnu.org/licenses/>. 

22# 

23 

24import numpy as np 

25import unittest 

26import lsst.utils.tests 

27import lsst.meas.extensions.trailedSources 

28from lsst.meas.base.tests import AlgorithmTestCase 

29from lsst.utils.tests import classParameters 

30from lsst.geom import Point2I, Point2D, Box2I, Extent2I 

31from unittest.mock import patch 

32 

33# Trailed-source length, angle, and centroid coordinates. 

34trail_lengths = np.array([5, 5, 10, 4]) 

35trail_angles = np.array([100, 0, 5, 4]) 

36trail_x_coords = np.array([100, 20, -20, 90]) 

37trail_y_coords = np.array([100, 20, -30, 100]) 

38 

39 

40class TrailedEdgeSource: 

41 """Holds a set of true trail parameters. 

42 """ 

43 

44 def __init__(self, instFlux, length, angle, xc, yc): 

45 self.instFlux = instFlux 

46 self.length = length 

47 self.angle = angle 

48 self.center = Point2D(xc, yc) 

49 self.x0 = xc - length / 2 * np.cos(angle) 

50 self.y0 = yc - length / 2 * np.sin(angle) 

51 self.x1 = xc + length / 2 * np.cos(angle) 

52 self.y1 = yc + length / 2 * np.sin(angle) 

53 

54 

55class TrailedTaskSetup: 

56 

57 def makeTrailedSourceMeasurementTask(self, plugin=None, dependencies=(), 

58 config=None, schema=None, 

59 algMetadata=None): 

60 """Set up a measurement task for a trailed source plugin. 

61 """ 

62 config = self.makeSingleFrameMeasurementConfig(plugin=plugin, 

63 dependencies=dependencies) 

64 

65 # Make sure the shape slot is base_SdssShape 

66 config.slots.shape = "base_SdssShape" 

67 return self.makeSingleFrameMeasurementTask(plugin=plugin, 

68 dependencies=dependencies, 

69 config=config, 

70 schema=schema, 

71 algMetadata=algMetadata) 

72 

73 

74# "Extend" meas.base.tests.TestDataset 

75class TrailedTestDataset(lsst.meas.base.tests.TestDataset): 

76 """A dataset for testing trailed source measurements. 

77 Given a `TrailedSource`, construct a record of the true values and an 

78 Exposure. 

79 """ 

80 

81 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds): 

82 

83 super().__init__(bbox, threshold, exposure, **kwds) 

84 

85 def addTrailedSource(self, trail, edge=True): 

86 """Add a trailed source to the simulation. 

87 

88 Re-implemented version of 

89 `lsst.meas.base.tests.TestDataset.addSource`. Numerically integrates a 

90 Gaussian PSF over a line to obtain an image of a trailed source and 

91 adds edge flags to the image. 

92 """ 

93 record = self.catalog.addNew() 

94 record.set(self.keys["centroid"], trail.center) 

95 rng = np.random.default_rng(32) 

96 covariance = rng.normal(0, 0.1, 4).reshape(2, 2) 

97 covariance[0, 1] = covariance[1, 0] 

98 record.set(self.keys["centroid_sigma"], covariance.astype(np.float32)) 

99 record.set(self.keys["shape"], self.psfShape) 

100 record.set(self.keys["isStar"], False) 

101 

102 # Sum the psf at each 

103 numIter = int(2 * trail.length) 

104 xp = np.linspace(trail.x0, trail.x1, num=numIter) 

105 yp = np.linspace(trail.y0, trail.y1, num=numIter) 

106 for (x, y) in zip(xp, yp): 

107 pt = Point2D(x, y) 

108 im = self.drawGaussian(self.exposure.getBBox(), trail.instFlux, 

109 lsst.afw.geom.Ellipse(self.psfShape, pt)) 

110 self.exposure.getMaskedImage().getImage().getArray()[:, :] += im.getArray() 

111 

112 planes = self.exposure.mask.getMaskPlaneDict() 

113 dim = self.exposure.getBBox().getDimensions() 

114 

115 # Add edge flags to the first and last 20 columns and rows. 

116 if edge: 

117 for y in range(20): 

118 self.exposure.mask.setMaskPlaneValues(planes['EDGE'], 0, dim[0] - 1, y) 

119 self.exposure.mask.setMaskPlaneValues(planes['EDGE'], 0, dim[0] - 1, y + dim[1] - 20) 

120 

121 for y in range(dim[1]): 

122 self.exposure.mask.setMaskPlaneValues(planes['EDGE'], 0, 20, y) 

123 self.exposure.mask.setMaskPlaneValues(planes['EDGE'], dim[0] - 20, dim[0] - 1, y) 

124 

125 totFlux = self.exposure.image.array.sum() 

126 self.exposure.image.array /= totFlux 

127 self.exposure.image.array *= trail.instFlux 

128 

129 record.set(self.keys["instFlux"], trail.instFlux) 

130 self._installFootprint(record, self.exposure.getImage()) 

131 

132 return record, self.exposure.getImage() 

133 

134 

135# Following from test_trailedSources 

136@classParameters(length=trail_lengths, theta=trail_angles, xc=trail_x_coords, yc=trail_y_coords) 

137class TrailedEdgeSourcesTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase): 

138 """ Test if ext_trailedSources_Naive_flag_edge is set correctly. 

139 

140 Given a `TrailedSource`, test if the edge flag is set correctly in the 

141 source catalog after the 

142 `lsst.meas.extensions.trailedSources.Naive.Plugin.makeTrailedSourceMeasurementTask` 

143 has been run on the source catalog. 

144 """ 

145 

146 def setUp(self): 

147 self.center = Point2D(50.1, 49.8) 

148 self.bbox = Box2I(lsst.geom.Point2I(-20, -30), Extent2I(140, 160)) 

149 self.dataset = TrailedTestDataset(self.bbox) 

150 

151 # Trail which extends into edge pixels 

152 self.trail = TrailedEdgeSource(100000.0, self.length, self.theta, self.xc, self.yc) 

153 self.dataset.addTrailedSource(self.trail) 

154 

155 def tearDown(self): 

156 del self.center 

157 del self.bbox 

158 del self.trail 

159 del self.dataset 

160 

161 def testEdgeFlag(self): 

162 """Test if edge flags are correctly set in NaivePlugin.py 

163 

164 Given a `TrailedTestDataset`, run the NaivePlugin measurement and 

165 check that the trailed sources have the edge flag set. [100,100] does 

166 not contain any edge pixels and should not have a flag set, [20,20] 

167 crosses into the edge region on only one side and should have the edge 

168 flag set, and [-20,-30] extends off the chip and should have the edge 

169 flag set. 

170 """ 

171 # Set up and run Naive measurement. 

172 task = TrailedTaskSetup.makeTrailedSourceMeasurementTask(self, 

173 plugin="ext_trailedSources_Naive", 

174 dependencies=("base_SdssCentroid", 

175 "base_SdssShape") 

176 ) 

177 exposure, catalog = self.dataset.realize(5.0, task.schema, randomSeed=0) 

178 task.run(catalog, exposure) 

179 record = catalog[0] 

180 

181 # Check that x0, y0 or x1, y1 is flagged as an edge pixel 

182 x1 = int(record['ext_trailedSources_Naive_x1']) 

183 y1 = int(record['ext_trailedSources_Naive_y1']) 

184 x0 = int(record['ext_trailedSources_Naive_x0']) 

185 y0 = int(record['ext_trailedSources_Naive_y0']) 

186 

187 # Test Case with no edge pixels 

188 if record['truth_x'] == 100: 

189 # These are used to ensure the mask pixels the trailed sources are 

190 # compared with have the correct flags set 

191 begin_edge_pixel_set = (exposure.mask[Point2I(x0, y0)] & exposure.mask.getPlaneBitMask( 

192 'EDGE') != 0) 

193 end_edge_pixel_set = (exposure.mask[Point2I(x1, y1)] & exposure.mask.getPlaneBitMask( 

194 'EDGE') != 0) 

195 

196 self.assertFalse(begin_edge_pixel_set) 

197 self.assertTrue(end_edge_pixel_set) 

198 

199 # Make sure measurement edge flag is set, but Naive_flag is not. 

200 # A failed trailed source measurement with the edge flag 

201 # set means the edge flag was set despite the measurement 

202 # failing. 

203 self.assertTrue(record.get("ext_trailedSources_Naive_flag_edge")) 

204 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

205 self.assertFalse(record.get("ext_trailedSources_Naive_flag_off_image")) 

206 self.assertFalse(record.get("ext_trailedSources_Naive_flag_nan")) 

207 

208 x1 = int(record['ext_trailedSources_Naive_x1']) 

209 y1 = int(record['ext_trailedSources_Naive_y1']) 

210 

211 self.assertFalse(exposure.mask[Point2I(x0, y0)] & exposure.mask.getPlaneBitMask('EDGE') != 0) 

212 self.assertTrue(exposure.mask[Point2I(x1, y1)] & exposure.mask.getPlaneBitMask('EDGE') != 0) 

213 

214 # Test case with one end of trail containing edge pixels 

215 elif record['truth_x'] == 20: 

216 begin_edge_pixel_set = (exposure.mask[Point2I(x0, y0)] & exposure.mask.getPlaneBitMask( 

217 'EDGE') != 0) 

218 end_edge_pixel_set = (exposure.mask[Point2I(x1, y1)] & exposure.mask.getPlaneBitMask( 

219 'EDGE') != 0) 

220 

221 self.assertFalse(begin_edge_pixel_set) 

222 self.assertFalse(end_edge_pixel_set) 

223 

224 # Make sure measurement Naive_flag_edge and Naive_flag not set 

225 self.assertFalse(record.get("ext_trailedSources_Naive_flag_edge")) 

226 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

227 self.assertFalse(record.get("ext_trailedSources_Naive_flag_off_image")) 

228 self.assertFalse(record.get("ext_trailedSources_Naive_flag_nan")) 

229 

230 x1 = int(record['ext_trailedSources_Naive_x1']) 

231 y1 = int(record['ext_trailedSources_Naive_y1']) 

232 

233 self.assertFalse(exposure.mask[Point2I(x0, y0)] & exposure.mask.getPlaneBitMask('EDGE') != 0) 

234 self.assertFalse(exposure.mask[Point2I(x1, y1)] & exposure.mask.getPlaneBitMask('EDGE') != 0) 

235 

236 # Test case trail fully contained 

237 elif record["truth_x"] == 90: 

238 begin_edge_pixel_set = (exposure.mask[Point2I(x0, y0)] & exposure.mask.getPlaneBitMask( 

239 'EDGE') != 0) 

240 end_edge_pixel_set = (exposure.mask[Point2I(x1, y1)] & exposure.mask.getPlaneBitMask( 

241 'EDGE') != 0) 

242 

243 self.assertFalse(begin_edge_pixel_set) 

244 self.assertFalse(end_edge_pixel_set) 

245 

246 # Make sure measurement Naive_flag_edge and Naive_flag not set 

247 self.assertFalse(record.get("ext_trailedSources_Naive_flag_edge")) 

248 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

249 self.assertFalse(record.get("ext_trailedSources_Naive_flag_off_image")) 

250 self.assertFalse(record.get("ext_trailedSources_Naive_flag_nan")) 

251 

252 x1 = int(record['ext_trailedSources_Naive_x1']) 

253 y1 = int(record['ext_trailedSources_Naive_y1']) 

254 

255 self.assertFalse(exposure.mask[Point2I(x0, y0)] & exposure.mask.getPlaneBitMask('EDGE') != 0) 

256 self.assertFalse(exposure.mask[Point2I(x1, y1)] & exposure.mask.getPlaneBitMask('EDGE') != 0) 

257 

258 # Test case with trailed source extending off chip. 

259 else: 

260 self.assertEquals(record['truth_x'], -20) 

261 self.assertTrue(record.get("ext_trailedSources_Naive_flag_edge")) 

262 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

263 self.assertTrue(record.get("ext_trailedSources_Naive_flag_off_image")) 

264 self.assertFalse(record.get("ext_trailedSources_Naive_flag_nan")) 

265 

266 def testNanFlag(self): 

267 """Test if nan flags are correctly set in NaivePlugin.py 

268 

269 Given a `TrailedTestDataset`, run the NaivePlugin measurement which 

270 has trailed sources where one of the end point values results in a 

271 nan. 

272 """ 

273 # Set up and run Naive measurement. 

274 task = TrailedTaskSetup.makeTrailedSourceMeasurementTask(self, 

275 plugin="ext_trailedSources_Naive", 

276 dependencies=("base_SdssCentroid", 

277 "base_SdssShape") 

278 ) 

279 

280 exposure, catalog = self.dataset.realize(5.0, task.schema, randomSeed=0) 

281 

282 original_check_trail_function = task.plugins['ext_trailedSources_Naive'].check_trail 

283 # Used to simulate a trailed source where one of the coordinates is a 

284 # nan. 

285 

286 def check_trail_mock(*args, **kwargs): 

287 measRecord = args[0] 

288 exposure = args[1] 

289 x0 = args[2] 

290 y0 = args[3] 

291 x1 = args[4] 

292 y1 = np.nan # overriding to test NAN flagging 

293 length = args[6] 

294 measRecord['ext_trailedSources_Naive_y1'] = np.nan 

295 return original_check_trail_function(measRecord, exposure, x0, y0, x1, y1, length) 

296 

297 # This patcher mocks check_trail so that one of the trailed sources 

298 # includes it is checking contains a nan at one of its endpoints. 

299 patcher = patch( 

300 'lsst.meas.extensions.trailedSources.NaivePlugin.SingleFrameNaiveTrailPlugin.check_trail', 

301 side_effect=check_trail_mock) 

302 patcher.start() 

303 task.run(catalog, exposure) 

304 record = catalog[0] 

305 

306 # Test Case with no edge pixels, but one is set to nan. 

307 if record['truth_x'] == 100: 

308 self.assertFalse(record.get("ext_trailedSources_Naive_flag_edge")) 

309 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

310 self.assertFalse(record.get("ext_trailedSources_Naive_flag_off_image")) 

311 self.assertTrue(record.get("ext_trailedSources_Naive_flag_nan")) 

312 

313 # Test case with one end of trail containing edge pixels, but nan is 

314 # set so edge does not end up set. 

315 elif record['truth_x'] == 20: 

316 

317 self.assertFalse(record.get("ext_trailedSources_Naive_flag_edge")) 

318 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

319 self.assertFalse(record.get("ext_trailedSources_Naive_flag_off_image")) 

320 self.assertTrue(record.get("ext_trailedSources_Naive_flag_nan")) 

321 

322 # Test case trail fully contained, but contains one nan. Only nan flag 

323 # is set. 

324 elif record["truth_x"] == 90: 

325 

326 self.assertFalse(record.get("ext_trailedSources_Naive_flag_edge")) 

327 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

328 self.assertFalse(record.get("ext_trailedSources_Naive_flag_off_image")) 

329 self.assertTrue(record.get("ext_trailedSources_Naive_flag_nan")) 

330 

331 # Test case with trailed source extending off chip. One coordinate 

332 # is off image the other is nan, so edge, off_image, and nan should 

333 # be set. 

334 else: 

335 self.assertEquals(record['truth_x'], -20) 

336 self.assertTrue(record.get("ext_trailedSources_Naive_flag_edge")) 

337 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

338 self.assertTrue(record.get("ext_trailedSources_Naive_flag_off_image")) 

339 self.assertTrue(record.get("ext_trailedSources_Naive_flag_nan")) 

340 

341 patcher.stop() 

342 

343 

344@classParameters(length=[10], theta=[5], xc=[-20], yc=[-30]) 

345class TrailedEdgeSourcesOffImageTest(AlgorithmTestCase, lsst.utils.tests.TestCase): 

346 """ Test if ext_trailedSources_Naive_flag_edge is set correctly. 

347 

348 Given a `TrailedSource`, test if the edge flag is set correctly in the 

349 source catalog after the 

350 'lsst.meas.extensions.trailedSources.Naive.Plugin.makeTrailedSourceMeasurementTask' 

351 has been run on the source catalog. 

352 """ 

353 

354 def setUp(self): 

355 self.center = Point2D(50.1, 49.8) 

356 self.bbox = Box2I(lsst.geom.Point2I(-20, -30), Extent2I(140, 160)) 

357 self.dataset = TrailedTestDataset(self.bbox) 

358 

359 # Trail which extends into edge pixels 

360 self.trail = TrailedEdgeSource(100000.0, self.length, self.theta, 

361 self.xc, self.yc) 

362 self.dataset.addTrailedSource(self.trail, edge=False) 

363 

364 def tearDown(self): 

365 del self.center 

366 del self.bbox 

367 del self.trail 

368 del self.dataset 

369 

370 def testOffImageEdgeFlag(self): 

371 """Test if edge flags are correctly set in NaivePlugin.py when source 

372 extends off the the image. 

373 

374 Given a `TrailedTestDataset`, run the NaivePlugin measurement and 

375 check that the edge flag set when a source extends off the chip. 

376 Edge pixels are not set in this test. 

377 """ 

378 # Set up and run Naive measurement. 

379 task = TrailedTaskSetup.makeTrailedSourceMeasurementTask(self, 

380 plugin="ext_trailedSources_Naive", 

381 dependencies=("base_SdssCentroid", 

382 "base_SdssShape") 

383 ) 

384 exposure, catalog = self.dataset.realize(5.0, task.schema, randomSeed=0) 

385 task.run(catalog, exposure) 

386 record = catalog[0] 

387 

388 self.assertTrue(record.get("ext_trailedSources_Naive_flag_edge")) 

389 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

390 

391 

392class TestMemory(lsst.utils.tests.MemoryTestCase): 

393 pass 

394 

395 

396def setup_module(module): 

397 lsst.utils.tests.init() 

398 

399 

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

401 lsst.utils.tests.init() 

402 unittest.main()