Coverage for tests/test_detectAndMeasure.py: 8%

314 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-21 08:36 +0000

1# This file is part of ip_diffim. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

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

7# for details of code ownership. 

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 GNU General Public License 

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

21 

22import numpy as np 

23import unittest 

24 

25import lsst.geom 

26from lsst.ip.diffim import detectAndMeasure, subtractImages 

27from lsst.ip.diffim.utils import makeTestImage 

28import lsst.utils.tests 

29 

30 

31class DetectAndMeasureTestBase(lsst.utils.tests.TestCase): 

32 

33 def _check_diaSource(self, refSources, diaSource, refIds=None, 

34 matchDistance=1., scale=1., usePsfFlux=True, 

35 rtol=0.02, atol=None): 

36 """Match a diaSource with a source in a reference catalog 

37 and compare properties. 

38 

39 Parameters 

40 ---------- 

41 refSources : `lsst.afw.table.SourceCatalog` 

42 The reference catalog. 

43 diaSource : `lsst.afw.table.SourceRecord` 

44 The new diaSource to match to the reference catalog. 

45 refIds : `list` of `int`, optional 

46 Source IDs of previously associated diaSources. 

47 matchDistance : `float`, optional 

48 Maximum distance allowed between the detected and reference source 

49 locations, in pixels. 

50 scale : `float`, optional 

51 Optional factor to scale the flux by before performing the test. 

52 usePsfFlux : `bool`, optional 

53 If set, test the PsfInstFlux field, otherwise use ApInstFlux. 

54 rtol : `float`, optional 

55 Relative tolerance of the flux value test. 

56 atol : `float`, optional 

57 Absolute tolerance of the flux value test. 

58 """ 

59 distance = np.sqrt((diaSource.getX() - refSources.getX())**2 

60 + (diaSource.getY() - refSources.getY())**2) 

61 self.assertLess(min(distance), matchDistance) 

62 src = refSources[np.argmin(distance)] 

63 if refIds is not None: 

64 # Check that the same source was not previously associated 

65 self.assertNotIn(src.getId(), refIds) 

66 refIds.append(src.getId()) 

67 if atol is None: 

68 atol = rtol*src.getPsfInstFlux() if usePsfFlux else rtol*src.getApInstFlux() 

69 if usePsfFlux: 

70 self.assertFloatsAlmostEqual(src.getPsfInstFlux()*scale, diaSource.getPsfInstFlux(), 

71 rtol=rtol, atol=atol) 

72 else: 

73 self.assertFloatsAlmostEqual(src.getApInstFlux()*scale, diaSource.getApInstFlux(), 

74 rtol=rtol, atol=atol) 

75 

76 def _check_values(self, values, minValue=None, maxValue=None): 

77 """Verify that an array has finite values, and optionally that they are 

78 within specified minimum and maximum bounds. 

79 

80 Parameters 

81 ---------- 

82 values : `numpy.ndarray` 

83 Array of values to check. 

84 minValue : `float`, optional 

85 Minimum allowable value. 

86 maxValue : `float`, optional 

87 Maximum allowable value. 

88 """ 

89 self.assertTrue(np.all(np.isfinite(values))) 

90 if minValue is not None: 

91 self.assertTrue(np.all(values >= minValue)) 

92 if maxValue is not None: 

93 self.assertTrue(np.all(values <= maxValue)) 

94 

95 def _setup_detection(self, doApCorr=False, doMerge=False, 

96 doSkySources=False, doForcedMeasurement=False): 

97 """Setup and configure the detection and measurement PipelineTask. 

98 

99 Parameters 

100 ---------- 

101 doApCorr : `bool`, optional 

102 Run subtask to apply aperture corrections. 

103 doMerge : `bool`, optional 

104 Merge positive and negative diaSources. 

105 doSkySources : `bool`, optional 

106 Generate sky sources. 

107 doForcedMeasurement : `bool`, optional 

108 Force photometer diaSource locations on PVI. 

109 

110 Returns 

111 ------- 

112 `lsst.pipe.base.PipelineTask` 

113 The configured Task to use for detection and measurement. 

114 """ 

115 config = self.detectionTask.ConfigClass() 

116 config.doApCorr = doApCorr 

117 config.doMerge = doMerge 

118 config.doSkySources = doSkySources 

119 config.doForcedMeasurement = doForcedMeasurement 

120 if doSkySources: 

121 config.skySources.nSources = 5 

122 return self.detectionTask(config=config) 

123 

124 

125class DetectAndMeasureTest(DetectAndMeasureTestBase): 

126 detectionTask = detectAndMeasure.DetectAndMeasureTask 

127 

128 def test_detection_xy0(self): 

129 """Basic functionality test with non-zero x0 and y0. 

130 """ 

131 # Set up the simulated images 

132 noiseLevel = 1. 

133 staticSeed = 1 

134 fluxLevel = 500 

135 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel, "x0": 12345, "y0": 67890} 

136 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

137 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

138 difference = science.clone() 

139 

140 # Configure the detection Task 

141 detectionTask = self._setup_detection() 

142 

143 # Run detection and check the results 

144 output = detectionTask.run(science, matchedTemplate, difference) 

145 subtractedMeasuredExposure = output.subtractedMeasuredExposure 

146 

147 self.assertImagesEqual(subtractedMeasuredExposure.image, difference.image) 

148 

149 def test_measurements_finite(self): 

150 """Measured fluxes and centroids should always be finite. 

151 """ 

152 columnNames = ["coord_ra", "coord_dec", "ip_diffim_forced_PsfFlux_instFlux"] 

153 

154 # Set up the simulated images 

155 noiseLevel = 1. 

156 staticSeed = 1 

157 transientSeed = 6 

158 xSize = 256 

159 ySize = 256 

160 kwargs = {"psfSize": 2.4, "x0": 0, "y0": 0, 

161 "xSize": xSize, "ySize": ySize} 

162 science, sources = makeTestImage(seed=staticSeed, noiseLevel=noiseLevel, noiseSeed=6, 

163 nSrc=1, **kwargs) 

164 matchedTemplate, _ = makeTestImage(seed=staticSeed, noiseLevel=noiseLevel/4, noiseSeed=7, 

165 nSrc=1, **kwargs) 

166 rng = np.random.RandomState(3) 

167 xLoc = np.arange(-5, xSize+5, 10) 

168 rng.shuffle(xLoc) 

169 yLoc = np.arange(-5, ySize+5, 10) 

170 rng.shuffle(yLoc) 

171 transients, transientSources = makeTestImage(seed=transientSeed, 

172 nSrc=len(xLoc), fluxLevel=1000., 

173 noiseLevel=noiseLevel, noiseSeed=8, 

174 xLoc=xLoc, yLoc=yLoc, 

175 **kwargs) 

176 difference = science.clone() 

177 difference.maskedImage -= matchedTemplate.maskedImage 

178 difference.maskedImage += transients.maskedImage 

179 

180 # Configure the detection Task 

181 detectionTask = self._setup_detection(doForcedMeasurement=True) 

182 

183 # Run detection and check the results 

184 output = detectionTask.run(science, matchedTemplate, difference) 

185 

186 for column in columnNames: 

187 self._check_values(output.diaSources[column]) 

188 self._check_values(output.diaSources.getX(), minValue=0, maxValue=xSize) 

189 self._check_values(output.diaSources.getY(), minValue=0, maxValue=ySize) 

190 self._check_values(output.diaSources.getPsfInstFlux()) 

191 

192 def test_detect_transients(self): 

193 """Run detection on a difference image containing transients. 

194 """ 

195 # Set up the simulated images 

196 noiseLevel = 1. 

197 staticSeed = 1 

198 transientSeed = 6 

199 fluxLevel = 500 

200 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel} 

201 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

202 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

203 

204 # Configure the detection Task 

205 detectionTask = self._setup_detection() 

206 

207 # Run detection and check the results 

208 def _detection_wrapper(positive=True): 

209 transients, transientSources = makeTestImage(seed=transientSeed, psfSize=2.4, 

210 nSrc=10, fluxLevel=1000., 

211 noiseLevel=noiseLevel, noiseSeed=8) 

212 difference = science.clone() 

213 difference.maskedImage -= matchedTemplate.maskedImage 

214 if positive: 

215 difference.maskedImage += transients.maskedImage 

216 else: 

217 difference.maskedImage -= transients.maskedImage 

218 output = detectionTask.run(science, matchedTemplate, difference) 

219 refIds = [] 

220 scale = 1. if positive else -1. 

221 for diaSource in output.diaSources: 

222 self._check_diaSource(transientSources, diaSource, refIds=refIds, scale=scale) 

223 _detection_wrapper(positive=True) 

224 _detection_wrapper(positive=False) 

225 

226 def test_detect_dipoles(self): 

227 """Run detection on a difference image containing dipoles. 

228 """ 

229 # Set up the simulated images 

230 noiseLevel = 1. 

231 staticSeed = 1 

232 fluxLevel = 1000 

233 fluxRange = 1.5 

234 nSources = 10 

235 offset = 1 

236 dipoleFlag = "ip_diffim_DipoleFit_flag_classification" 

237 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel, "fluxRange": fluxRange, 

238 "nSrc": nSources} 

239 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

240 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

241 difference = science.clone() 

242 matchedTemplate.image.array[...] = np.roll(matchedTemplate.image.array[...], offset, axis=0) 

243 matchedTemplate.variance.array[...] = np.roll(matchedTemplate.variance.array[...], offset, axis=0) 

244 matchedTemplate.mask.array[...] = np.roll(matchedTemplate.mask.array[...], offset, axis=0) 

245 difference.maskedImage -= matchedTemplate.maskedImage[science.getBBox()] 

246 

247 # Configure the detection Task 

248 detectionTask = self._setup_detection() 

249 

250 # Run detection and check the results 

251 output = detectionTask.run(science, matchedTemplate, difference) 

252 self.assertIn(dipoleFlag, output.diaSources.schema.getNames()) 

253 nSourcesDet = len(sources) 

254 self.assertEqual(len(output.diaSources), 2*nSourcesDet) 

255 refIds = [] 

256 # The diaSource check should fail if we don't merge positive and negative footprints 

257 for diaSource in output.diaSources: 

258 with self.assertRaises(AssertionError): 

259 self._check_diaSource(sources, diaSource, refIds=refIds, scale=0, 

260 atol=np.sqrt(fluxRange*fluxLevel)) 

261 

262 detectionTask2 = self._setup_detection(doMerge=True) 

263 output2 = detectionTask2.run(science, matchedTemplate, difference) 

264 self.assertEqual(len(output2.diaSources), nSourcesDet) 

265 refIds = [] 

266 for diaSource in output2.diaSources: 

267 if diaSource[dipoleFlag]: 

268 self._check_diaSource(sources, diaSource, refIds=refIds, scale=0, 

269 rtol=0.05, atol=None, usePsfFlux=False) 

270 self.assertFloatsAlmostEqual(diaSource["ip_diffim_DipoleFit_orientation"], -90., atol=2.) 

271 self.assertFloatsAlmostEqual(diaSource["ip_diffim_DipoleFit_separation"], offset, rtol=0.1) 

272 else: 

273 raise ValueError("DiaSource with ID %s is not a dipole!", diaSource.getId()) 

274 

275 def test_sky_sources(self): 

276 """Add sky sources and check that they are sufficiently far from other 

277 sources and have negligible flux. 

278 """ 

279 # Set up the simulated images 

280 noiseLevel = 1. 

281 staticSeed = 1 

282 transientSeed = 6 

283 transientFluxLevel = 1000. 

284 transientFluxRange = 1.5 

285 fluxLevel = 500 

286 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel} 

287 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

288 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

289 transients, transientSources = makeTestImage(seed=transientSeed, psfSize=2.4, 

290 nSrc=10, fluxLevel=transientFluxLevel, 

291 fluxRange=transientFluxRange, 

292 noiseLevel=noiseLevel, noiseSeed=8) 

293 difference = science.clone() 

294 difference.maskedImage -= matchedTemplate.maskedImage 

295 difference.maskedImage += transients.maskedImage 

296 kernelWidth = np.max(science.psf.getKernel().getDimensions())//2 

297 

298 # Configure the detection Task 

299 detectionTask = self._setup_detection(doSkySources=True) 

300 

301 # Run detection and check the results 

302 output = detectionTask.run(science, matchedTemplate, difference) 

303 skySources = output.diaSources[output.diaSources["sky_source"]] 

304 self.assertEqual(len(skySources), detectionTask.config.skySources.nSources) 

305 for skySource in skySources: 

306 # The sky sources should not be close to any other source 

307 with self.assertRaises(AssertionError): 

308 self._check_diaSource(transientSources, skySource, matchDistance=kernelWidth) 

309 with self.assertRaises(AssertionError): 

310 self._check_diaSource(sources, skySource, matchDistance=kernelWidth) 

311 # The sky sources should have low flux levels. 

312 self._check_diaSource(transientSources, skySource, matchDistance=1000, scale=0., 

313 atol=np.sqrt(transientFluxRange*transientFluxLevel)) 

314 

315 def _check_diaSource(self, refSources, diaSource, refIds=None, 

316 matchDistance=1., scale=1., usePsfFlux=True, 

317 rtol=0.02, atol=None): 

318 """Match a diaSource with a source in a reference catalog 

319 and compare properties. 

320 """ 

321 distance = np.sqrt((diaSource.getX() - refSources.getX())**2 

322 + (diaSource.getY() - refSources.getY())**2) 

323 self.assertLess(min(distance), matchDistance) 

324 src = refSources[np.argmin(distance)] 

325 if refIds is not None: 

326 # Check that the same source was not previously associated 

327 self.assertNotIn(src.getId(), refIds) 

328 refIds.append(src.getId()) 

329 if atol is None: 

330 atol = rtol*src.getPsfInstFlux() if usePsfFlux else rtol*src.getApInstFlux() 

331 if usePsfFlux: 

332 self.assertFloatsAlmostEqual(src.getPsfInstFlux()*scale, diaSource.getPsfInstFlux(), 

333 rtol=rtol, atol=atol) 

334 else: 

335 self.assertFloatsAlmostEqual(src.getApInstFlux()*scale, diaSource.getApInstFlux(), 

336 rtol=rtol, atol=atol) 

337 

338 

339class DetectAndMeasureScoreTest(DetectAndMeasureTestBase): 

340 detectionTask = detectAndMeasure.DetectAndMeasureScoreTask 

341 

342 def test_detection_xy0(self): 

343 """Basic functionality test with non-zero x0 and y0. 

344 """ 

345 # Set up the simulated images 

346 noiseLevel = 1. 

347 staticSeed = 1 

348 fluxLevel = 500 

349 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel, "x0": 12345, "y0": 67890} 

350 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

351 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

352 difference = science.clone() 

353 subtractTask = subtractImages.AlardLuptonPreconvolveSubtractTask() 

354 scienceKernel = science.psf.getKernel() 

355 score = subtractTask._convolveExposure(difference, scienceKernel, subtractTask.convolutionControl) 

356 

357 # Configure the detection Task 

358 detectionTask = self._setup_detection() 

359 

360 # Run detection and check the results 

361 output = detectionTask.run(science, matchedTemplate, difference, score) 

362 subtractedMeasuredExposure = output.subtractedMeasuredExposure 

363 

364 self.assertImagesEqual(subtractedMeasuredExposure.image, difference.image) 

365 

366 def test_measurements_finite(self): 

367 """Measured fluxes and centroids should always be finite. 

368 """ 

369 columnNames = ["coord_ra", "coord_dec", "ip_diffim_forced_PsfFlux_instFlux"] 

370 

371 # Set up the simulated images 

372 noiseLevel = 1. 

373 staticSeed = 1 

374 transientSeed = 6 

375 xSize = 256 

376 ySize = 256 

377 kwargs = {"psfSize": 2.4, "x0": 0, "y0": 0, 

378 "xSize": xSize, "ySize": ySize} 

379 science, sources = makeTestImage(seed=staticSeed, noiseLevel=noiseLevel, noiseSeed=6, 

380 nSrc=1, **kwargs) 

381 matchedTemplate, _ = makeTestImage(seed=staticSeed, noiseLevel=noiseLevel/4, noiseSeed=7, 

382 nSrc=1, **kwargs) 

383 rng = np.random.RandomState(3) 

384 xLoc = np.arange(-5, xSize+5, 10) 

385 rng.shuffle(xLoc) 

386 yLoc = np.arange(-5, ySize+5, 10) 

387 rng.shuffle(yLoc) 

388 transients, transientSources = makeTestImage(seed=transientSeed, 

389 nSrc=len(xLoc), fluxLevel=1000., 

390 noiseLevel=noiseLevel, noiseSeed=8, 

391 xLoc=xLoc, yLoc=yLoc, 

392 **kwargs) 

393 difference = science.clone() 

394 difference.maskedImage -= matchedTemplate.maskedImage 

395 difference.maskedImage += transients.maskedImage 

396 subtractTask = subtractImages.AlardLuptonPreconvolveSubtractTask() 

397 scienceKernel = science.psf.getKernel() 

398 score = subtractTask._convolveExposure(difference, scienceKernel, subtractTask.convolutionControl) 

399 

400 # Configure the detection Task 

401 detectionTask = self._setup_detection(doForcedMeasurement=True) 

402 

403 # Run detection and check the results 

404 output = detectionTask.run(science, matchedTemplate, difference, score) 

405 

406 for column in columnNames: 

407 self._check_values(output.diaSources[column]) 

408 self._check_values(output.diaSources.getX(), minValue=0, maxValue=xSize) 

409 self._check_values(output.diaSources.getY(), minValue=0, maxValue=ySize) 

410 self._check_values(output.diaSources.getPsfInstFlux()) 

411 

412 def test_detect_transients(self): 

413 """Run detection on a difference image containing transients. 

414 """ 

415 # Set up the simulated images 

416 noiseLevel = 1. 

417 staticSeed = 1 

418 transientSeed = 6 

419 fluxLevel = 500 

420 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel} 

421 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

422 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

423 scienceKernel = science.psf.getKernel() 

424 subtractTask = subtractImages.AlardLuptonPreconvolveSubtractTask() 

425 

426 # Configure the detection Task 

427 detectionTask = self._setup_detection() 

428 

429 # Run detection and check the results 

430 def _detection_wrapper(positive=True): 

431 """Simulate positive or negative transients and run detection. 

432 

433 Parameters 

434 ---------- 

435 positive : `bool`, optional 

436 If set, use positive transient sources. 

437 """ 

438 transients, transientSources = makeTestImage(seed=transientSeed, psfSize=2.4, 

439 nSrc=10, fluxLevel=1000., 

440 noiseLevel=noiseLevel, noiseSeed=8) 

441 difference = science.clone() 

442 difference.maskedImage -= matchedTemplate.maskedImage 

443 if positive: 

444 difference.maskedImage += transients.maskedImage 

445 else: 

446 difference.maskedImage -= transients.maskedImage 

447 score = subtractTask._convolveExposure(difference, scienceKernel, subtractTask.convolutionControl) 

448 output = detectionTask.run(science, matchedTemplate, difference, score) 

449 refIds = [] 

450 scale = 1. if positive else -1. 

451 for diaSource in output.diaSources: 

452 self._check_diaSource(transientSources, diaSource, refIds=refIds, scale=scale) 

453 _detection_wrapper(positive=True) 

454 _detection_wrapper(positive=False) 

455 

456 def test_detect_dipoles(self): 

457 """Run detection on a difference image containing dipoles. 

458 """ 

459 # Set up the simulated images 

460 noiseLevel = 1. 

461 staticSeed = 1 

462 fluxLevel = 1000 

463 fluxRange = 1.5 

464 nSources = 10 

465 offset = 1 

466 dipoleFlag = "ip_diffim_DipoleFit_flag_classification" 

467 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel, "fluxRange": fluxRange, 

468 "nSrc": nSources} 

469 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

470 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

471 difference = science.clone() 

472 # Shift the template by a pixel in order to make dipoles in the difference image. 

473 matchedTemplate.image.array[...] = np.roll(matchedTemplate.image.array[...], offset, axis=0) 

474 matchedTemplate.variance.array[...] = np.roll(matchedTemplate.variance.array[...], offset, axis=0) 

475 matchedTemplate.mask.array[...] = np.roll(matchedTemplate.mask.array[...], offset, axis=0) 

476 difference.maskedImage -= matchedTemplate.maskedImage[science.getBBox()] 

477 subtractTask = subtractImages.AlardLuptonPreconvolveSubtractTask() 

478 scienceKernel = science.psf.getKernel() 

479 score = subtractTask._convolveExposure(difference, scienceKernel, subtractTask.convolutionControl) 

480 

481 # Configure the detection Task 

482 detectionTask = self._setup_detection() 

483 

484 # Run detection and check the results 

485 output = detectionTask.run(science, matchedTemplate, difference, score) 

486 self.assertIn(dipoleFlag, output.diaSources.schema.getNames()) 

487 nSourcesDet = len(sources) 

488 # Since we did not merge the dipoles, each source should result in 

489 # both a positive and a negative diaSource 

490 self.assertEqual(len(output.diaSources), 2*nSourcesDet) 

491 refIds = [] 

492 # The diaSource check should fail if we don't merge positive and negative footprints 

493 for diaSource in output.diaSources: 

494 with self.assertRaises(AssertionError): 

495 self._check_diaSource(sources, diaSource, refIds=refIds, scale=0, 

496 atol=np.sqrt(fluxRange*fluxLevel)) 

497 

498 detectionTask2 = self._setup_detection(doMerge=True) 

499 output2 = detectionTask2.run(science, matchedTemplate, difference, score) 

500 self.assertEqual(len(output2.diaSources), nSourcesDet) 

501 refIds = [] 

502 for diaSource in output2.diaSources: 

503 if diaSource[dipoleFlag]: 

504 self._check_diaSource(sources, diaSource, refIds=refIds, scale=0, 

505 rtol=0.05, atol=None, usePsfFlux=False) 

506 self.assertFloatsAlmostEqual(diaSource["ip_diffim_DipoleFit_orientation"], -90., atol=2.) 

507 self.assertFloatsAlmostEqual(diaSource["ip_diffim_DipoleFit_separation"], offset, rtol=0.1) 

508 else: 

509 raise ValueError("DiaSource with ID %s is not a dipole!", diaSource.getId()) 

510 

511 def test_sky_sources(self): 

512 """Add sky sources and check that they are sufficiently far from other 

513 sources and have negligible flux. 

514 """ 

515 # Set up the simulated images 

516 noiseLevel = 1. 

517 staticSeed = 1 

518 transientSeed = 6 

519 transientFluxLevel = 1000. 

520 transientFluxRange = 1.5 

521 fluxLevel = 500 

522 kwargs = {"seed": staticSeed, "psfSize": 2.4, "fluxLevel": fluxLevel} 

523 science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) 

524 matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) 

525 transients, transientSources = makeTestImage(seed=transientSeed, psfSize=2.4, 

526 nSrc=10, fluxLevel=transientFluxLevel, 

527 fluxRange=transientFluxRange, 

528 noiseLevel=noiseLevel, noiseSeed=8) 

529 difference = science.clone() 

530 difference.maskedImage -= matchedTemplate.maskedImage 

531 difference.maskedImage += transients.maskedImage 

532 subtractTask = subtractImages.AlardLuptonPreconvolveSubtractTask() 

533 scienceKernel = science.psf.getKernel() 

534 kernelWidth = np.max(scienceKernel.getDimensions())//2 

535 score = subtractTask._convolveExposure(difference, scienceKernel, subtractTask.convolutionControl) 

536 

537 # Configure the detection Task 

538 detectionTask = self._setup_detection(doSkySources=True) 

539 

540 # Run detection and check the results 

541 output = detectionTask.run(science, matchedTemplate, difference, score) 

542 skySources = output.diaSources[output.diaSources["sky_source"]] 

543 self.assertEqual(len(skySources), detectionTask.config.skySources.nSources) 

544 for skySource in skySources: 

545 # The sky sources should not be close to any other source 

546 with self.assertRaises(AssertionError): 

547 self._check_diaSource(transientSources, skySource, matchDistance=kernelWidth) 

548 with self.assertRaises(AssertionError): 

549 self._check_diaSource(sources, skySource, matchDistance=kernelWidth) 

550 # The sky sources should have low flux levels. 

551 self._check_diaSource(transientSources, skySource, matchDistance=1000, scale=0., 

552 atol=np.sqrt(transientFluxRange*transientFluxLevel)) 

553 

554 

555def setup_module(module): 

556 lsst.utils.tests.init() 

557 

558 

559class MemoryTestCase(lsst.utils.tests.MemoryTestCase): 

560 pass 

561 

562 

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

564 lsst.utils.tests.init() 

565 unittest.main()