Coverage for tests/test_defects.py: 12%

264 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-04 10:38 +0000

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2017 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

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"""Test cases for lsst.cp.pipe.FindDefectsTask.""" 

24 

25import unittest 

26import numpy as np 

27import copy 

28 

29import lsst.utils 

30import lsst.utils.tests 

31 

32import lsst.ip.isr as ipIsr 

33import lsst.cp.pipe as cpPipe 

34from lsst.ip.isr import isrMock, countMaskedPixels 

35from lsst.geom import Box2I, Point2I, Extent2I 

36 

37 

38class MeasureDefectsTaskTestCase(lsst.utils.tests.TestCase): 

39 """A test case for the defect finding task.""" 

40 

41 def setUp(self): 

42 self.defaultConfig = cpPipe.defects.MeasureDefectsTask.ConfigClass() 

43 

44 self.flatMean = 2000 

45 self.darkMean = 1 

46 self.readNoiseAdu = 10 

47 self.nSigmaBright = 8 

48 self.nSigmaDark = 8 

49 

50 mockImageConfig = isrMock.IsrMock.ConfigClass() 

51 

52 # flatDrop is not really relevant as we replace the data 

53 # but good to note it in case we change how this image is made 

54 mockImageConfig.flatDrop = 0.99999 

55 mockImageConfig.isTrimmed = True 

56 

57 self.flatExp = isrMock.FlatMock(config=mockImageConfig).run() 

58 (shapeY, shapeX) = self.flatExp.getDimensions() 

59 

60 # x, y, size tuples 

61 # always put edge defects at the start and change the value of nEdge 

62 

63 self.brightDefects = [(0, 15, 3, 3), (100, 123, 1, 1)] 

64 

65 self.darkDefects = [(5, 0, 1, 1), (7, 62, 2, 2)] 

66 

67 nEdge = 1 # NOTE: update if more edge defects are included 

68 self.noEdges = slice(nEdge, None) 

69 self.onlyEdges = slice(0, nEdge) 

70 

71 self.darkBBoxes = [Box2I(Point2I(x, y), Extent2I(sx, sy)) for (x, y, sx, sy) in self.darkDefects] 

72 self.brightBBoxes = [Box2I(Point2I(x, y), Extent2I(sx, sy)) for (x, y, sx, sy) in self.brightDefects] 

73 

74 flatWidth = np.sqrt(self.flatMean) + self.readNoiseAdu 

75 darkWidth = self.readNoiseAdu 

76 self.rng = np.random.RandomState(0) 

77 flatData = self.rng.normal(self.flatMean, flatWidth, (shapeX, shapeY)) 

78 darkData = self.rng.normal(self.darkMean, darkWidth, (shapeX, shapeY)) 

79 

80 # NOTE: darks and flats have same defects applied deliberately to both 

81 for defect in self.brightDefects: 

82 y, x, sy, sx = defect 

83 # are these actually the numbers we want? 

84 flatData[x:x+sx, y:y+sy] += self.nSigmaBright * flatWidth 

85 darkData[x:x+sx, y:y+sy] += self.nSigmaBright * darkWidth 

86 

87 for defect in self.darkDefects: 

88 y, x, sy, sx = defect 

89 # are these actually the numbers we want? 

90 flatData[x:x+sx, y:y+sy] -= self.nSigmaDark * flatWidth 

91 darkData[x:x+sx, y:y+sy] -= self.nSigmaDark * darkWidth 

92 

93 self.darkExp = self.flatExp.clone() 

94 self.spareImage = self.flatExp.clone() # for testing edge bits and misc 

95 

96 self.flatExp.image.array[:] = flatData 

97 self.darkExp.image.array[:] = darkData 

98 

99 self.defaultTask = cpPipe.defects.MeasureDefectsTask() 

100 

101 self.allDefectsList = ipIsr.Defects() 

102 

103 self.brightDefectsList = ipIsr.Defects() 

104 for d in self.brightBBoxes: 

105 self.brightDefectsList.append(d) 

106 self.allDefectsList.append(d) 

107 

108 self.darkDefectsList = ipIsr.Defects() 

109 for d in self.darkBBoxes: 

110 self.darkDefectsList.append(d) 

111 self.allDefectsList.append(d) 

112 

113 def check_maskBlocks(self, inputDefects, expectedDefects): 

114 """A helper function for the tests of 

115 maskBlocksIfIntermitentBadPixelsInColumn. 

116 

117 """ 

118 config = copy.copy(self.defaultConfig) 

119 config.badOnAndOffPixelColumnThreshold = 10 

120 config.goodPixelColumnGapThreshold = 5 

121 config.nPixBorderUpDown = 0 

122 config.nPixBorderLeftRight = 0 

123 

124 task = cpPipe.defects.MeasureDefectsTask(config=config) 

125 

126 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects) 

127 boxesMeasured = [] 

128 for defect in defectsWithColumns: 

129 boxesMeasured.append(defect.getBBox()) 

130 

131 for boxInput in expectedDefects: 

132 self.assertIn(boxInput, boxesMeasured) 

133 

134 # Check that the code did not mask anything extra by 

135 # looking in both the input list and "expanded-column" list. 

136 unionInputExpectedBoxes = [] 

137 for defect in inputDefects: 

138 unionInputExpectedBoxes.append(defect.getBBox()) 

139 for defect in expectedDefects: 

140 unionInputExpectedBoxes.append(defect) 

141 

142 # Check that code doesn't mask more than it is supposed to. 

143 for boxMeas in boxesMeasured: 

144 self.assertIn(boxMeas, unionInputExpectedBoxes) 

145 

146 def test_maskBlocks_full_column(self): 

147 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

148 

149 Tests that a contigous bad column does not get split by the 

150 code. 

151 

152 The mock flat has a size of 200X204 pixels. This column has a 

153 maximum length of 50 pixels, otherwise there would be a split 

154 along the mock amp boundary. 

155 

156 Plots can be found in DM-19903 on Jira. 

157 

158 """ 

159 

160 defects = self.allDefectsList 

161 defects.append(Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50))) 

162 expectedDefects = [Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50))] 

163 

164 self.check_maskBlocks(defects, expectedDefects) 

165 

166 def test_maskBlocks_long_column(self): 

167 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

168 

169 Tests that a contigous bad column with Npix >= 

170 badOnAndOffPixelColumnThreshold (10) does not get split by the 

171 code. 

172 

173 Plots can be found in DM-19903 on Jira. 

174 

175 """ 

176 

177 expectedDefects = [Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))] 

178 defects = self.allDefectsList 

179 defects.append(Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))) 

180 

181 self.check_maskBlocks(defects, expectedDefects) 

182 

183 def test_maskBlocks_short_column(self): 

184 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

185 

186 Tests that a contigous bad column Npix < 

187 badOnAndOffPixelColumnThreshold (10) does not get split by the 

188 code. 

189 

190 Plots can be found in DM-19903 on Jira. 

191 

192 """ 

193 

194 expectedDefects = [Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))] 

195 defects = self.allDefectsList 

196 defects.append(Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))) 

197 

198 self.check_maskBlocks(defects, expectedDefects) 

199 

200 def test_maskBlocks_discontigous_to_single_block(self): 

201 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

202 

203 Npix discontiguous bad pixels in a column where Npix >= 

204 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

205 goodPixelColumnGapThreshold (5). Under these conditions, the 

206 whole block of bad pixels (including good gaps) should be 

207 masked. 

208 

209 Plots can be found in DM-19903 on Jira. 

210 

211 """ 

212 

213 expectedDefects = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 48))] 

214 defects = self.allDefectsList 

215 badPixels = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 2)), 

216 Box2I(corner=Point2I(30, 5), dimensions=Extent2I(1, 3)), 

217 Box2I(corner=Point2I(30, 11), dimensions=Extent2I(1, 5)), 

218 Box2I(corner=Point2I(30, 19), dimensions=Extent2I(1, 5)), 

219 Box2I(corner=Point2I(30, 27), dimensions=Extent2I(1, 4)), 

220 Box2I(corner=Point2I(30, 34), dimensions=Extent2I(1, 15))] 

221 for badBox in badPixels: 

222 defects.append(badBox) 

223 

224 self.check_maskBlocks(defects, expectedDefects) 

225 

226 def test_maskBlocks_discontigous_less_than_thresholds(self): 

227 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

228 

229 Npix discontiguous bad pixels in a column where Npix < 

230 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

231 goodPixelColumnGapThreshold (5). Under these conditions, the 

232 expected defect boxes should be the same as the input boxes. 

233 

234 Plots can be found in DM-19903 on Jira. 

235 

236 """ 

237 

238 expectedDefects = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)), 

239 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)), 

240 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))] 

241 defects = self.allDefectsList 

242 badPixels = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)), 

243 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)), 

244 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))] 

245 for badBox in badPixels: 

246 defects.append(badBox) 

247 

248 self.check_maskBlocks(defects, expectedDefects) 

249 

250 def test_maskBlocks_more_than_thresholds(self): 

251 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

252 

253 Npix discontiguous bad pixels in a column where Npix < 

254 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

255 goodPixelColumnGapThreshold (5). Npix=34 (> 10) bad pixels 

256 total, 1 "good" gap with 13 pixels big enough (13 >= 5 good 

257 pixels, from y=6 (1+5) to y=19). 

258 

259 Plots can be found in DM-19903 on Jira. 

260 

261 """ 

262 

263 expectedDefects = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 7)), 

264 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 30))] 

265 defects = self.allDefectsList 

266 badPixels = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 2)), 

267 Box2I(corner=Point2I(40, 5), dimensions=Extent2I(1, 3)), 

268 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 5)), 

269 Box2I(corner=Point2I(40, 27), dimensions=Extent2I(1, 4)), 

270 Box2I(corner=Point2I(40, 34), dimensions=Extent2I(1, 15))] 

271 for badBox in badPixels: 

272 defects.append(badBox) 

273 

274 self.check_maskBlocks(defects, expectedDefects) 

275 

276 def test_maskBlocks_not_enough_bad_pixels_in_column(self): 

277 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

278 

279 Npix discontiguous bad pixels in a column where Npix < 

280 badOnAndOffPixelColumnThreshold (10) and and gaps of good 

281 pixels > goodPixelColumnGapThreshold (5). Since Npix < 

282 badOnAndOffPixelColumnThreshold, then it doesn't matter that 

283 the number of good pixels in gap > 

284 goodPixelColumnGapThreshold. 5<10 bad pixels total, 1 "good" 

285 gap big enough (29>=5 good pixels, from y =12 (10+2) to y=30) 

286 

287 Plots can be found in DM-19903 on Jira. 

288 

289 """ 

290 

291 expectedDefects = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)), 

292 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))] 

293 defects = self.allDefectsList 

294 badPixels = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)), 

295 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))] 

296 for badBox in badPixels: 

297 defects.append(badBox) 

298 

299 self.check_maskBlocks(defects, expectedDefects) 

300 

301 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self): 

302 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

303 

304 Npix discontiguous bad pixels in a column where Npix > 

305 badOnAndOffPixelColumnThreshold (10) and every other pixel is 

306 bad. 

307 

308 Plots can be found in DM-19903 on Jira. 

309 

310 """ 

311 

312 expectedDefects = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 31))] 

313 defects = self.allDefectsList 

314 badPixels = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 1)), 

315 Box2I(corner=Point2I(50, 12), dimensions=Extent2I(1, 1)), 

316 Box2I(corner=Point2I(50, 14), dimensions=Extent2I(1, 1)), 

317 Box2I(corner=Point2I(50, 16), dimensions=Extent2I(1, 1)), 

318 Box2I(corner=Point2I(50, 18), dimensions=Extent2I(1, 1)), 

319 Box2I(corner=Point2I(50, 20), dimensions=Extent2I(1, 1)), 

320 Box2I(corner=Point2I(50, 22), dimensions=Extent2I(1, 1)), 

321 Box2I(corner=Point2I(50, 24), dimensions=Extent2I(1, 1)), 

322 Box2I(corner=Point2I(50, 26), dimensions=Extent2I(1, 1)), 

323 Box2I(corner=Point2I(50, 28), dimensions=Extent2I(1, 1)), 

324 Box2I(corner=Point2I(50, 30), dimensions=Extent2I(1, 1)), 

325 Box2I(corner=Point2I(50, 32), dimensions=Extent2I(1, 1)), 

326 Box2I(corner=Point2I(50, 34), dimensions=Extent2I(1, 1)), 

327 Box2I(corner=Point2I(50, 36), dimensions=Extent2I(1, 1)), 

328 Box2I(corner=Point2I(50, 38), dimensions=Extent2I(1, 1)), 

329 Box2I(corner=Point2I(50, 40), dimensions=Extent2I(1, 1))] 

330 for badBox in badPixels: 

331 defects.append(badBox) 

332 

333 self.check_maskBlocks(defects, expectedDefects) 

334 

335 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self): 

336 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

337 

338 Npix discontiguous bad pixels in a column where Npix > 

339 badOnAndOffPixelColumnThreshold (10) and every other pixel is 

340 bad. 

341 

342 Plots can be found in DM-19903 on Jira. 

343 

344 """ 

345 

346 expectedDefects = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)), 

347 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)), 

348 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)), 

349 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)), 

350 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)), 

351 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))] 

352 defects = self.allDefectsList 

353 badPixels = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)), 

354 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)), 

355 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)), 

356 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)), 

357 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)), 

358 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))] 

359 for badBox in badPixels: 

360 defects.append(badBox) 

361 

362 self.check_maskBlocks(defects, expectedDefects) 

363 

364 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self): 

365 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

366 

367 Npix discontiguous bad pixels in column with "blobs" of "m" 

368 bad pixels to one side, m > badOnAndOffPixelColumnThreshold 

369 (10), number of good pixel in gaps between blobs < 

370 goodPixelColumnGapThreshold (5). 

371 

372 Plots can be found in DM-19903 on Jira. 

373 

374 """ 

375 

376 expectedDefects = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 29)), 

377 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 12))] 

378 defects = self.allDefectsList 

379 badPixels = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 18)), 

380 Box2I(corner=Point2I(60, 20), dimensions=Extent2I(1, 10)), 

381 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 2)), 

382 Box2I(corner=Point2I(61, 6), dimensions=Extent2I(2, 8))] 

383 for badBox in badPixels: 

384 defects.append(badBox) 

385 

386 self.check_maskBlocks(defects, expectedDefects) 

387 

388 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

389 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

390 

391 Npix discontiguous bad pixels in column with "blobs" of "m" 

392 bad pixels to the other side, m > 

393 badOnAndOffPixelColumnThreshold (10), number of good pixel in 

394 gaps between blobs < goodPixelColumnGapThreshold (5). 

395 

396 Plots can be found in DM-19903 on Jira. 

397 

398 """ 

399 

400 expectedDefects = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 29)), 

401 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 12))] 

402 defects = self.allDefectsList 

403 badPixels = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 18)), 

404 Box2I(corner=Point2I(70, 20), dimensions=Extent2I(1, 10)), 

405 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 2)), 

406 Box2I(corner=Point2I(68, 6), dimensions=Extent2I(2, 8))] 

407 for badBox in badPixels: 

408 defects.append(badBox) 

409 

410 self.check_maskBlocks(defects, expectedDefects) 

411 

412 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

413 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

414 

415 Npix discontiguous bad pixels in column with "blobs" of "m" 

416 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold 

417 (10), number of good pixel in gaps between blobs < 

418 goodPixelColumnGapThreshold (5). 

419 

420 Plots can be found in DM-19903 on Jira. 

421 

422 """ 

423 

424 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)), 

425 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 12)), 

426 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 12))] 

427 defects = self.allDefectsList 

428 badPixels = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 18)), 

429 Box2I(corner=Point2I(75, 20), dimensions=Extent2I(1, 10)), 

430 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 2)), 

431 Box2I(corner=Point2I(73, 6), dimensions=Extent2I(2, 8)), 

432 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 2)), 

433 Box2I(corner=Point2I(76, 6), dimensions=Extent2I(2, 8))] 

434 for badBox in badPixels: 

435 defects.append(badBox) 

436 

437 self.check_maskBlocks(defects, expectedDefects) 

438 

439 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

440 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

441 

442 Npix discontiguous bad pixels in column with "blobs" of "m" 

443 bad pixels to one side, m > badOnAndOffPixelColumnThreshold 

444 (10), number of good pixel in gaps between blobs > 

445 goodPixelColumnGapThreshold (5). 

446 

447 Plots can be found in DM-19903 on Jira. 

448 

449 """ 

450 

451 expectedDefects = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 29)), 

452 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)), 

453 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))] 

454 defects = self.allDefectsList 

455 badPixels = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 18)), 

456 Box2I(corner=Point2I(80, 20), dimensions=Extent2I(1, 10)), 

457 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)), 

458 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))] 

459 for badBox in badPixels: 

460 defects.append(badBox) 

461 

462 self.check_maskBlocks(defects, expectedDefects) 

463 

464 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

465 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

466 

467 Npix discontiguous bad pixels in column with "blobs" of "m" 

468 bad pixels to the other side, m > 

469 badOnAndOffPixelColumnThreshold (10), number of good pixel in 

470 gaps between blobs > goodPixelColumnGapThreshold (5). 

471 

472 Plots can be found in DM-19903 on Jira. 

473 

474 """ 

475 

476 expectedDefects = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 29)), 

477 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)), 

478 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))] 

479 defects = self.allDefectsList 

480 badPixels = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 18)), 

481 Box2I(corner=Point2I(87, 20), dimensions=Extent2I(1, 10)), 

482 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)), 

483 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))] 

484 for badBox in badPixels: 

485 defects.append(badBox) 

486 

487 self.check_maskBlocks(defects, expectedDefects) 

488 

489 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

490 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

491 

492 Npix discontiguous bad pixels in column with "blobs" of "m" 

493 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold 

494 (10), number of good pixel in gaps between blobs > 

495 goodPixelColumnGapThreshold (5). 

496 

497 Plots can be found in DM-19903 on Jira. 

498 

499 """ 

500 

501 expectedDefects = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 34)), 

502 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 7)), 

503 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 9)), 

504 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 7)), 

505 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 9))] 

506 defects = self.allDefectsList 

507 badPixels = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 12)), 

508 Box2I(corner=Point2I(93, 15), dimensions=Extent2I(1, 20)), 

509 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 2)), 

510 Box2I(corner=Point2I(91, 7), dimensions=Extent2I(2, 2)), 

511 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 2)), 

512 Box2I(corner=Point2I(94, 7), dimensions=Extent2I(2, 2)), 

513 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 3)), 

514 Box2I(corner=Point2I(91, 24), dimensions=Extent2I(2, 3)), 

515 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 3)), 

516 Box2I(corner=Point2I(94, 24), dimensions=Extent2I(2, 3))] 

517 for badBox in badPixels: 

518 defects.append(badBox) 

519 

520 self.check_maskBlocks(defects, expectedDefects) 

521 

522 def test_defectFindingAllSensor(self): 

523 config = copy.copy(self.defaultConfig) 

524 config.nPixBorderLeftRight = 0 

525 config.nPixBorderUpDown = 0 

526 

527 task = cpPipe.defects.MeasureDefectsTask(config=config) 

528 

529 defects = task.findHotAndColdPixels(self.flatExp, [config.nSigmaBright, 

530 config.nSigmaDark]) 

531 

532 allBBoxes = self.darkBBoxes + self.brightBBoxes 

533 

534 boxesMeasured = [] 

535 for defect in defects: 

536 boxesMeasured.append(defect.getBBox()) 

537 

538 for expectedBBox in allBBoxes: 

539 self.assertIn(expectedBBox, boxesMeasured) 

540 

541 def test_defectFindingEdgeIgnore(self): 

542 config = copy.copy(self.defaultConfig) 

543 config.nPixBorderUpDown = 0 

544 task = cpPipe.defects.MeasureDefectsTask(config=config) 

545 defects = task.findHotAndColdPixels(self.flatExp, [config.nSigmaBright, 

546 config.nSigmaDark]) 

547 

548 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges] 

549 

550 boxesMeasured = [] 

551 for defect in defects: 

552 boxesMeasured.append(defect.getBBox()) 

553 

554 for expectedBBox in shouldBeFound: 

555 self.assertIn(expectedBBox, boxesMeasured) 

556 

557 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges] 

558 for boxMissed in shouldBeMissed: 

559 self.assertNotIn(boxMissed, boxesMeasured) 

560 

561 def test_pixelCounting(self): 

562 """Test that the number of defective pixels identified is as expected. 

563 """ 

564 config = copy.copy(self.defaultConfig) 

565 config.nPixBorderUpDown = 0 

566 config.nPixBorderLeftRight = 0 

567 task = cpPipe.defects.MeasureDefectsTask(config=config) 

568 defects = task.findHotAndColdPixels(self.flatExp, [config.nSigmaBright, 

569 config.nSigmaDark]) 

570 

571 defectArea = 0 

572 for defect in defects: 

573 defectArea += defect.getBBox().getArea() 

574 

575 # The columnar code will cover blocks of a column with 

576 # on-and-off pixels, thus creating more bad pixels that what 

577 # initially placed in self.brightDefects and self.darkDefects. 

578 # Thus, defectArea should be >= crossCheck. 

579 crossCheck = 0 

580 for x, y, sx, sy in self.brightDefects: 

581 crossCheck += sx*sy 

582 for x, y, sx, sy in self.darkDefects: 

583 crossCheck += sx*sy 

584 

585 # Test the result of _nPixFromDefects() 

586 # via two different ways of calculating area. 

587 self.assertEqual(defectArea, task._nPixFromDefects(defects)) 

588 # defectArea should be >= crossCheck 

589 self.assertGreaterEqual(defectArea, crossCheck) 

590 

591 def test_getNumGoodPixels(self): 

592 """Test the the number of pixels in the image not masked is as 

593 expected. 

594 """ 

595 testImage = self.flatExp.clone() 

596 mi = testImage.maskedImage 

597 

598 imageSize = testImage.getBBox().getArea() 

599 nGood = self.defaultTask._getNumGoodPixels(mi) 

600 

601 self.assertEqual(imageSize, nGood) 

602 

603 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA") 

604 

605 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6)) 

606 testImage.mask[noDataBox] |= NODATABIT 

607 

608 self.assertEqual(imageSize - noDataBox.getArea(), self.defaultTask._getNumGoodPixels(mi)) 

609 # check for misfire; we're setting NO_DATA here, not BAD 

610 self.assertEqual(imageSize, self.defaultTask._getNumGoodPixels(mi, 'BAD')) 

611 

612 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did 

613 self.assertEqual(imageSize, nGood) 

614 

615 BADBIT = mi.mask.getPlaneBitMask("BAD") 

616 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7)) 

617 testImage.mask[badBox] |= BADBIT 

618 

619 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD')) 

620 

621 def test_edgeMasking(self): 

622 """Check that the right number of edge pixels are masked by 

623 _setEdgeBits(). 

624 """ 

625 testImage = self.flatExp.clone() 

626 mi = testImage.maskedImage 

627 

628 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0) 

629 self.defaultTask._setEdgeBits(mi) 

630 

631 hEdge = self.defaultConfig.nPixBorderLeftRight 

632 vEdge = self.defaultConfig.nPixBorderUpDown 

633 xSize, ySize = mi.getDimensions() 

634 

635 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4 

636 

637 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge) 

638 

639 def test_badImage(self): 

640 """Check that fully-bad images do not fail. 

641 """ 

642 testImage = self.flatExp.clone() 

643 testImage.image.array[:, :] = 125000 

644 

645 config = copy.copy(self.defaultConfig) 

646 # Do not exclude any pixels, so the areas match. 

647 config.nPixBorderUpDown = 0 

648 config.nPixBorderLeftRight = 0 

649 

650 task = cpPipe.defects.MeasureDefectsTask(config=config) 

651 defects = task.findHotAndColdPixels(testImage, [config.nSigmaBright, 

652 config.nSigmaDark]) 

653 

654 defectArea = 0 

655 for defect in defects: 

656 defectArea += defect.getBBox().getArea() 

657 self.assertEqual(defectArea, testImage.getBBox().getArea()) 

658 

659 

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

661 pass 

662 

663 

664def setup_module(module): 

665 lsst.utils.tests.init() 

666 

667 

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

669 lsst.utils.tests.init() 

670 unittest.main()