Coverage for tests/test_defects.py: 10%

288 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-05 01:51 -0700

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 self.brightDefectsList = ipIsr.Defects() 

103 self.darkDefectsList = ipIsr.Defects() 

104 

105 with self.allDefectsList.bulk_update(): 

106 with self.brightDefectsList.bulk_update(): 

107 for d in self.brightBBoxes: 

108 self.brightDefectsList.append(d) 

109 self.allDefectsList.append(d) 

110 

111 with self.darkDefectsList.bulk_update(): 

112 for d in self.darkBBoxes: 

113 self.darkDefectsList.append(d) 

114 self.allDefectsList.append(d) 

115 

116 def check_maskBlocks(self, inputDefects, expectedDefects): 

117 """A helper function for the tests of 

118 maskBlocksIfIntermitentBadPixelsInColumn. 

119 

120 """ 

121 config = copy.copy(self.defaultConfig) 

122 config.badOnAndOffPixelColumnThreshold = 10 

123 config.goodPixelColumnGapThreshold = 5 

124 config.nPixBorderUpDown = 0 

125 config.nPixBorderLeftRight = 0 

126 

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

128 

129 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects) 

130 boxesMeasured = [] 

131 for defect in defectsWithColumns: 

132 boxesMeasured.append(defect.getBBox()) 

133 

134 for boxInput in expectedDefects: 

135 self.assertIn(boxInput, boxesMeasured) 

136 

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

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

139 unionInputExpectedBoxes = [] 

140 for defect in inputDefects: 

141 unionInputExpectedBoxes.append(defect.getBBox()) 

142 for defect in expectedDefects: 

143 unionInputExpectedBoxes.append(defect) 

144 

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

146 for boxMeas in boxesMeasured: 

147 self.assertIn(boxMeas, unionInputExpectedBoxes) 

148 

149 def test_maskBlocks_full_column(self): 

150 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

151 

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

153 code. 

154 

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

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

157 along the mock amp boundary. 

158 

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

160 

161 """ 

162 

163 defects = self.allDefectsList 

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

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

166 

167 self.check_maskBlocks(defects, expectedDefects) 

168 

169 def test_maskBlocks_long_column(self): 

170 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

171 

172 Tests that a contigous bad column with Npix >= 

173 badOnAndOffPixelColumnThreshold (10) does not get split by the 

174 code. 

175 

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

177 

178 """ 

179 

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

181 defects = self.allDefectsList 

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

183 

184 self.check_maskBlocks(defects, expectedDefects) 

185 

186 def test_maskBlocks_short_column(self): 

187 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

188 

189 Tests that a contigous bad column Npix < 

190 badOnAndOffPixelColumnThreshold (10) does not get split by the 

191 code. 

192 

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

194 

195 """ 

196 

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

198 defects = self.allDefectsList 

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

200 

201 self.check_maskBlocks(defects, expectedDefects) 

202 

203 def test_maskBlocks_discontigous_to_single_block(self): 

204 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

205 

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

207 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

208 goodPixelColumnGapThreshold (5). Under these conditions, the 

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

210 masked. 

211 

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

213 

214 """ 

215 

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

217 defects = self.allDefectsList 

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

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

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

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

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

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

224 with defects.bulk_update(): 

225 for badBox in badPixels: 

226 defects.append(badBox) 

227 

228 self.check_maskBlocks(defects, expectedDefects) 

229 

230 def test_maskBlocks_discontigous_less_than_thresholds(self): 

231 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

232 

233 Npix discontiguous bad pixels in a column where Npix < 

234 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

235 goodPixelColumnGapThreshold (5). Under these conditions, the 

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

237 

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

239 

240 """ 

241 

242 expectedDefects = [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 defects = self.allDefectsList 

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

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

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

249 with defects.bulk_update(): 

250 for badBox in badPixels: 

251 defects.append(badBox) 

252 

253 self.check_maskBlocks(defects, expectedDefects) 

254 

255 def test_maskBlocks_more_than_thresholds(self): 

256 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

257 

258 Npix discontiguous bad pixels in a column where Npix < 

259 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

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

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

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

263 

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

265 

266 """ 

267 

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

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

270 defects = self.allDefectsList 

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

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

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

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

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

276 with defects.bulk_update(): 

277 for badBox in badPixels: 

278 defects.append(badBox) 

279 

280 self.check_maskBlocks(defects, expectedDefects) 

281 

282 def test_maskBlocks_not_enough_bad_pixels_in_column(self): 

283 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

284 

285 Npix discontiguous bad pixels in a column where Npix < 

286 badOnAndOffPixelColumnThreshold (10) and and gaps of good 

287 pixels > goodPixelColumnGapThreshold (5). Since Npix < 

288 badOnAndOffPixelColumnThreshold, then it doesn't matter that 

289 the number of good pixels in gap > 

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

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

292 

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

294 

295 """ 

296 

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

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

299 defects = self.allDefectsList 

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

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

302 with defects.bulk_update(): 

303 for badBox in badPixels: 

304 defects.append(badBox) 

305 

306 self.check_maskBlocks(defects, expectedDefects) 

307 

308 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self): 

309 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

310 

311 Npix discontiguous bad pixels in a column where Npix > 

312 badOnAndOffPixelColumnThreshold (10) and every other pixel is 

313 bad. 

314 

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

316 

317 """ 

318 

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

320 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

337 with defects.bulk_update(): 

338 for badBox in badPixels: 

339 defects.append(badBox) 

340 

341 self.check_maskBlocks(defects, expectedDefects) 

342 

343 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self): 

344 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

345 

346 Npix discontiguous bad pixels in a column where Npix > 

347 badOnAndOffPixelColumnThreshold (10) and every other pixel is 

348 bad. 

349 

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

351 

352 """ 

353 

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

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

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

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

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

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

360 defects = self.allDefectsList 

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

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

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

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

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

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

367 with defects.bulk_update(): 

368 for badBox in badPixels: 

369 defects.append(badBox) 

370 

371 self.check_maskBlocks(defects, expectedDefects) 

372 

373 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self): 

374 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

375 

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

377 bad pixels to one side, m > badOnAndOffPixelColumnThreshold 

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

379 goodPixelColumnGapThreshold (5). 

380 

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

382 

383 """ 

384 

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

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

387 defects = self.allDefectsList 

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

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

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

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

392 with defects.bulk_update(): 

393 for badBox in badPixels: 

394 defects.append(badBox) 

395 

396 self.check_maskBlocks(defects, expectedDefects) 

397 

398 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

399 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

400 

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

402 bad pixels to the other side, m > 

403 badOnAndOffPixelColumnThreshold (10), number of good pixel in 

404 gaps between blobs < goodPixelColumnGapThreshold (5). 

405 

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

407 

408 """ 

409 

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

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

412 defects = self.allDefectsList 

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

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

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

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

417 with defects.bulk_update(): 

418 for badBox in badPixels: 

419 defects.append(badBox) 

420 

421 self.check_maskBlocks(defects, expectedDefects) 

422 

423 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

424 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

425 

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

427 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold 

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

429 goodPixelColumnGapThreshold (5). 

430 

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

432 

433 """ 

434 

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

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

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

438 defects = self.allDefectsList 

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

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

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

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

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

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

445 with defects.bulk_update(): 

446 for badBox in badPixels: 

447 defects.append(badBox) 

448 

449 self.check_maskBlocks(defects, expectedDefects) 

450 

451 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

452 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

453 

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

455 bad pixels to one side, m > badOnAndOffPixelColumnThreshold 

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

457 goodPixelColumnGapThreshold (5). 

458 

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

460 

461 """ 

462 

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

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

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

466 defects = self.allDefectsList 

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

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

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

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

471 with defects.bulk_update(): 

472 for badBox in badPixels: 

473 defects.append(badBox) 

474 

475 self.check_maskBlocks(defects, expectedDefects) 

476 

477 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

478 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

479 

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

481 bad pixels to the other side, m > 

482 badOnAndOffPixelColumnThreshold (10), number of good pixel in 

483 gaps between blobs > goodPixelColumnGapThreshold (5). 

484 

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

486 

487 """ 

488 

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

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

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

492 defects = self.allDefectsList 

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

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

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

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

497 with defects.bulk_update(): 

498 for badBox in badPixels: 

499 defects.append(badBox) 

500 

501 self.check_maskBlocks(defects, expectedDefects) 

502 

503 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

504 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

505 

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

507 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold 

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

509 goodPixelColumnGapThreshold (5). 

510 

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

512 

513 """ 

514 

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

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

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

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

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

520 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

531 with defects.bulk_update(): 

532 for badBox in badPixels: 

533 defects.append(badBox) 

534 

535 self.check_maskBlocks(defects, expectedDefects) 

536 

537 def test_maskBlocks_y_out_of_order_dm38103(self): 

538 """A test for maskBlocksIfIntermitentBadPixelsInColumn, y out of order. 

539 

540 This test is a variant of 

541 notest_maskBlocks_every_other_pixel_bad_greater_than_threshold with 

542 an extra out-of-y-order bad pixel to trigger DM-38103. 

543 """ 

544 expectedDefects = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 31))] 

545 defects = self.allDefectsList 

546 badPixels = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 1)), 

547 Box2I(corner=Point2I(50, 112), dimensions=Extent2I(1, 1)), 

548 Box2I(corner=Point2I(50, 114), dimensions=Extent2I(1, 1)), 

549 Box2I(corner=Point2I(50, 116), dimensions=Extent2I(1, 1)), 

550 Box2I(corner=Point2I(50, 118), dimensions=Extent2I(1, 1)), 

551 Box2I(corner=Point2I(50, 120), dimensions=Extent2I(1, 1)), 

552 Box2I(corner=Point2I(50, 122), dimensions=Extent2I(1, 1)), 

553 Box2I(corner=Point2I(50, 124), dimensions=Extent2I(1, 1)), 

554 Box2I(corner=Point2I(50, 126), dimensions=Extent2I(1, 1)), 

555 Box2I(corner=Point2I(50, 128), dimensions=Extent2I(1, 1)), 

556 Box2I(corner=Point2I(50, 130), dimensions=Extent2I(1, 1)), 

557 Box2I(corner=Point2I(50, 132), dimensions=Extent2I(1, 1)), 

558 Box2I(corner=Point2I(50, 134), dimensions=Extent2I(1, 1)), 

559 Box2I(corner=Point2I(50, 136), dimensions=Extent2I(1, 1)), 

560 Box2I(corner=Point2I(50, 138), dimensions=Extent2I(1, 1)), 

561 Box2I(corner=Point2I(50, 140), dimensions=Extent2I(1, 1)), 

562 # This last point is out of order in y. 

563 Box2I(corner=Point2I(50, 100), dimensions=Extent2I(1, 1))] 

564 

565 # Force defect normalization off in order to trigger DM-38301, because 

566 # defects.fromFootprintList() which is called by findHotAndColdPixels 

567 # does not do normalization. 

568 defects._bulk_update = True 

569 for badBox in badPixels: 

570 defects.append(badBox) 

571 defects._bulk_update = False 

572 

573 self.check_maskBlocks(defects, expectedDefects) 

574 

575 def test_defectFindingAllSensor(self): 

576 config = copy.copy(self.defaultConfig) 

577 config.nPixBorderLeftRight = 0 

578 config.nPixBorderUpDown = 0 

579 

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

581 

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

583 config.nSigmaDark]) 

584 

585 allBBoxes = self.darkBBoxes + self.brightBBoxes 

586 

587 boxesMeasured = [] 

588 for defect in defects: 

589 boxesMeasured.append(defect.getBBox()) 

590 

591 for expectedBBox in allBBoxes: 

592 self.assertIn(expectedBBox, boxesMeasured) 

593 

594 def test_defectFindingEdgeIgnore(self): 

595 config = copy.copy(self.defaultConfig) 

596 config.nPixBorderUpDown = 0 

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

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

599 config.nSigmaDark]) 

600 

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

602 

603 boxesMeasured = [] 

604 for defect in defects: 

605 boxesMeasured.append(defect.getBBox()) 

606 

607 for expectedBBox in shouldBeFound: 

608 self.assertIn(expectedBBox, boxesMeasured) 

609 

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

611 for boxMissed in shouldBeMissed: 

612 self.assertNotIn(boxMissed, boxesMeasured) 

613 

614 def test_pixelCounting(self): 

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

616 """ 

617 config = copy.copy(self.defaultConfig) 

618 config.nPixBorderUpDown = 0 

619 config.nPixBorderLeftRight = 0 

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

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

622 config.nSigmaDark]) 

623 

624 defectArea = 0 

625 for defect in defects: 

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

627 

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

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

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

631 # Thus, defectArea should be >= crossCheck. 

632 crossCheck = 0 

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

634 crossCheck += sx*sy 

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

636 crossCheck += sx*sy 

637 

638 # Test the result of _nPixFromDefects() 

639 # via two different ways of calculating area. 

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

641 # defectArea should be >= crossCheck 

642 self.assertGreaterEqual(defectArea, crossCheck) 

643 

644 def test_getNumGoodPixels(self): 

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

646 expected. 

647 """ 

648 testImage = self.flatExp.clone() 

649 mi = testImage.maskedImage 

650 

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

652 nGood = self.defaultTask._getNumGoodPixels(mi) 

653 

654 self.assertEqual(imageSize, nGood) 

655 

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

657 

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

659 testImage.mask[noDataBox] |= NODATABIT 

660 

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

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

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

664 

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

666 self.assertEqual(imageSize, nGood) 

667 

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

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

670 testImage.mask[badBox] |= BADBIT 

671 

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

673 

674 def test_edgeMasking(self): 

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

676 _setEdgeBits(). 

677 """ 

678 testImage = self.flatExp.clone() 

679 mi = testImage.maskedImage 

680 

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

682 self.defaultTask._setEdgeBits(mi) 

683 

684 hEdge = self.defaultConfig.nPixBorderLeftRight 

685 vEdge = self.defaultConfig.nPixBorderUpDown 

686 xSize, ySize = mi.getDimensions() 

687 

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

689 

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

691 

692 def test_badImage(self): 

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

694 """ 

695 testImage = self.flatExp.clone() 

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

697 

698 config = copy.copy(self.defaultConfig) 

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

700 config.nPixBorderUpDown = 0 

701 config.nPixBorderLeftRight = 0 

702 

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

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

705 config.nSigmaDark]) 

706 

707 defectArea = 0 

708 for defect in defects: 

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

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

711 

712 

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

714 pass 

715 

716 

717def setup_module(module): 

718 lsst.utils.tests.init() 

719 

720 

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

722 lsst.utils.tests.init() 

723 unittest.main()