Coverage for tests/test_defects.py: 10%

331 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-04 03:58 -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.defects.MeasureDefectsTask.""" 

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 

36from lsst.daf.base import PropertyList 

37 

38 

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

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

41 

42 def setUp(self): 

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

44 

45 self.flatMean = 2000 

46 self.darkMean = 1 

47 self.readNoiseAdu = 10 

48 self.nSigmaBright = 8 

49 self.nSigmaDark = 8 

50 

51 mockImageConfig = isrMock.IsrMock.ConfigClass() 

52 

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

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

55 mockImageConfig.flatDrop = 0.99999 

56 mockImageConfig.isTrimmed = True 

57 

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

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

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 # Set image types, the defects code will use them. 

106 metaDataFlat = PropertyList() 

107 metaDataFlat["IMGTYPE"] = "FLAT" 

108 self.flatExp.setMetadata(metaDataFlat) 

109 

110 metaDataDark = PropertyList() 

111 metaDataDark["IMGTYPE"] = "DARK" 

112 self.darkExp.setMetadata(metaDataDark) 

113 

114 with self.allDefectsList.bulk_update(): 

115 with self.brightDefectsList.bulk_update(): 

116 for d in self.brightBBoxes: 

117 self.brightDefectsList.append(d) 

118 self.allDefectsList.append(d) 

119 

120 with self.darkDefectsList.bulk_update(): 

121 for d in self.darkBBoxes: 

122 self.darkDefectsList.append(d) 

123 self.allDefectsList.append(d) 

124 

125 def check_maskBlocks(self, inputDefects, expectedDefects): 

126 """A helper function for the tests of 

127 maskBlocksIfIntermitentBadPixelsInColumn. 

128 

129 """ 

130 config = copy.copy(self.defaultConfig) 

131 config.badOnAndOffPixelColumnThreshold = 10 

132 config.goodPixelColumnGapThreshold = 5 

133 config.nPixBorderUpDown = 0 

134 config.nPixBorderLeftRight = 0 

135 

136 task = self.defaultTask 

137 task.config = config 

138 

139 defectsWithColumns, count = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects) 

140 boxesMeasured = [] 

141 for defect in defectsWithColumns: 

142 boxesMeasured.append(defect.getBBox()) 

143 

144 for boxInput in expectedDefects: 

145 self.assertIn(boxInput, boxesMeasured) 

146 

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

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

149 unionInputExpectedBoxes = [] 

150 for defect in inputDefects: 

151 unionInputExpectedBoxes.append(defect.getBBox()) 

152 for defect in expectedDefects: 

153 unionInputExpectedBoxes.append(defect) 

154 

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

156 for boxMeas in boxesMeasured: 

157 self.assertIn(boxMeas, unionInputExpectedBoxes) 

158 

159 def test_maskBlocks_full_column(self): 

160 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

161 

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

163 code. 

164 

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

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

167 along the mock amp boundary. 

168 

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

170 

171 """ 

172 

173 defects = self.allDefectsList 

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

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

176 

177 self.check_maskBlocks(defects, expectedDefects) 

178 

179 def test_maskBlocks_long_column(self): 

180 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

181 

182 Tests that a contigous bad column with Npix >= 

183 badOnAndOffPixelColumnThreshold (10) does not get split by the 

184 code. 

185 

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

187 

188 """ 

189 

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

191 defects = self.allDefectsList 

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

193 

194 self.check_maskBlocks(defects, expectedDefects) 

195 

196 def test_maskBlocks_short_column(self): 

197 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

198 

199 Tests that a contigous bad column Npix < 

200 badOnAndOffPixelColumnThreshold (10) does not get split by the 

201 code. 

202 

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

204 

205 """ 

206 

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

208 defects = self.allDefectsList 

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

210 

211 self.check_maskBlocks(defects, expectedDefects) 

212 

213 def test_maskBlocks_discontigous_to_single_block(self): 

214 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

215 

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

217 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

218 goodPixelColumnGapThreshold (5). Under these conditions, the 

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

220 masked. 

221 

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

223 

224 """ 

225 

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

227 defects = self.allDefectsList 

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

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

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

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

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

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

234 with defects.bulk_update(): 

235 for badBox in badPixels: 

236 defects.append(badBox) 

237 

238 self.check_maskBlocks(defects, expectedDefects) 

239 

240 def test_maskBlocks_discontigous_less_than_thresholds(self): 

241 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

242 

243 Npix discontiguous bad pixels in a column where Npix < 

244 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

245 goodPixelColumnGapThreshold (5). Under these conditions, the 

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

247 

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

249 

250 """ 

251 

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

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

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

255 defects = self.allDefectsList 

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

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

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

259 with defects.bulk_update(): 

260 for badBox in badPixels: 

261 defects.append(badBox) 

262 

263 self.check_maskBlocks(defects, expectedDefects) 

264 

265 def test_maskBlocks_more_than_thresholds(self): 

266 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

267 

268 Npix discontiguous bad pixels in a column where Npix < 

269 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels < 

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

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

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

273 

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

275 

276 """ 

277 

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

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

280 defects = self.allDefectsList 

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

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

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

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

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

286 with defects.bulk_update(): 

287 for badBox in badPixels: 

288 defects.append(badBox) 

289 

290 self.check_maskBlocks(defects, expectedDefects) 

291 

292 def test_maskBlocks_not_enough_bad_pixels_in_column(self): 

293 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

294 

295 Npix discontiguous bad pixels in a column where Npix < 

296 badOnAndOffPixelColumnThreshold (10) and and gaps of good 

297 pixels > goodPixelColumnGapThreshold (5). Since Npix < 

298 badOnAndOffPixelColumnThreshold, then it doesn't matter that 

299 the number of good pixels in gap > 

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

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

302 

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

304 

305 """ 

306 

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

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

309 defects = self.allDefectsList 

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

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

312 with defects.bulk_update(): 

313 for badBox in badPixels: 

314 defects.append(badBox) 

315 

316 self.check_maskBlocks(defects, expectedDefects) 

317 

318 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self): 

319 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

320 

321 Npix discontiguous bad pixels in a column where Npix > 

322 badOnAndOffPixelColumnThreshold (10) and every other pixel is 

323 bad. 

324 

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

326 

327 """ 

328 

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

330 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

347 with defects.bulk_update(): 

348 for badBox in badPixels: 

349 defects.append(badBox) 

350 

351 self.check_maskBlocks(defects, expectedDefects) 

352 

353 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self): 

354 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

355 

356 Npix discontiguous bad pixels in a column where Npix > 

357 badOnAndOffPixelColumnThreshold (10) and every other pixel is 

358 bad. 

359 

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

361 

362 """ 

363 

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

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

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

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

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

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

370 defects = self.allDefectsList 

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

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

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

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

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

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

377 with defects.bulk_update(): 

378 for badBox in badPixels: 

379 defects.append(badBox) 

380 

381 self.check_maskBlocks(defects, expectedDefects) 

382 

383 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self): 

384 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

385 

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

387 bad pixels to one side, m > badOnAndOffPixelColumnThreshold 

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

389 goodPixelColumnGapThreshold (5). 

390 

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

392 

393 """ 

394 

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

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

397 defects = self.allDefectsList 

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

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

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

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

402 with defects.bulk_update(): 

403 for badBox in badPixels: 

404 defects.append(badBox) 

405 

406 self.check_maskBlocks(defects, expectedDefects) 

407 

408 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

409 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

410 

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

412 bad pixels to the other side, m > 

413 badOnAndOffPixelColumnThreshold (10), number of good pixel in 

414 gaps between blobs < goodPixelColumnGapThreshold (5). 

415 

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

417 

418 """ 

419 

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

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

422 defects = self.allDefectsList 

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

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

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

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

427 with defects.bulk_update(): 

428 for badBox in badPixels: 

429 defects.append(badBox) 

430 

431 self.check_maskBlocks(defects, expectedDefects) 

432 

433 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

434 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

435 

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

437 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold 

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

439 goodPixelColumnGapThreshold (5). 

440 

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

442 

443 """ 

444 

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

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

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

448 defects = self.allDefectsList 

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

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

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

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

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

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

455 with defects.bulk_update(): 

456 for badBox in badPixels: 

457 defects.append(badBox) 

458 

459 self.check_maskBlocks(defects, expectedDefects) 

460 

461 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

462 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

463 

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

465 bad pixels to one side, m > badOnAndOffPixelColumnThreshold 

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

467 goodPixelColumnGapThreshold (5). 

468 

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

470 

471 """ 

472 

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

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

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

476 defects = self.allDefectsList 

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

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

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

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

481 with defects.bulk_update(): 

482 for badBox in badPixels: 

483 defects.append(badBox) 

484 

485 self.check_maskBlocks(defects, expectedDefects) 

486 

487 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

488 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

489 

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

491 bad pixels to the other side, m > 

492 badOnAndOffPixelColumnThreshold (10), number of good pixel in 

493 gaps between blobs > goodPixelColumnGapThreshold (5). 

494 

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

496 

497 """ 

498 

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

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

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

502 defects = self.allDefectsList 

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

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

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

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

507 with defects.bulk_update(): 

508 for badBox in badPixels: 

509 defects.append(badBox) 

510 

511 self.check_maskBlocks(defects, expectedDefects) 

512 

513 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

514 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

515 

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

517 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold 

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

519 goodPixelColumnGapThreshold (5). 

520 

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

522 

523 """ 

524 

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

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

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

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

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

530 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

541 with defects.bulk_update(): 

542 for badBox in badPixels: 

543 defects.append(badBox) 

544 

545 self.check_maskBlocks(defects, expectedDefects) 

546 

547 def test_maskBlocks_y_out_of_order_dm38103(self): 

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

549 

550 This test is a variant of 

551 notest_maskBlocks_every_other_pixel_bad_greater_than_threshold with 

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

553 """ 

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

555 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574 

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

576 # defects.fromFootprintList() which is called by _findHotAndColdPixels 

577 # does not do normalization. 

578 defects._bulk_update = True 

579 for badBox in badPixels: 

580 defects.append(badBox) 

581 defects._bulk_update = False 

582 

583 self.check_maskBlocks(defects, expectedDefects) 

584 

585 def test_defectFindingAllSensor(self): 

586 config = copy.copy(self.defaultConfig) 

587 config.nPixBorderLeftRight = 0 

588 config.nPixBorderUpDown = 0 

589 

590 task = self.defaultTask 

591 task.config = config 

592 

593 defects = task._findHotAndColdPixels(self.flatExp) 

594 

595 allBBoxes = self.darkBBoxes + self.brightBBoxes 

596 

597 boxesMeasured = [] 

598 for defect in defects: 

599 boxesMeasured.append(defect.getBBox()) 

600 

601 for expectedBBox in allBBoxes: 

602 self.assertIn(expectedBBox, boxesMeasured) 

603 

604 def test_defectFindingEdgeIgnore(self): 

605 config = copy.copy(self.defaultConfig) 

606 config.nPixBorderUpDown = 0 

607 config.nPixBorderLeftRight = 7 

608 task = self.defaultTask 

609 task.config = config 

610 defects = task._findHotAndColdPixels(self.flatExp) 

611 

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

613 

614 boxesMeasured = [] 

615 for defect in defects: 

616 boxesMeasured.append(defect.getBBox()) 

617 

618 for expectedBBox in shouldBeFound: 

619 self.assertIn(expectedBBox, boxesMeasured) 

620 

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

622 for boxMissed in shouldBeMissed: 

623 self.assertNotIn(boxMissed, boxesMeasured) 

624 

625 def valueThreshold(self, fileType, saturateAmpInFlat=False): 

626 """Helper function to loop over flats and darks 

627 to test thresholdType = 'VALUE'.""" 

628 config = copy.copy(self.defaultConfig) 

629 config.thresholdType = 'VALUE' 

630 task = self.defaultTask 

631 task.config = config 

632 

633 for amp in self.flatExp.getDetector(): 

634 if amp.getName() == 'C:0,0': 

635 regionC00 = amp.getBBox() 

636 

637 if fileType == 'dark': 

638 exp = self.darkExp 

639 shouldBeFound = self.brightBBoxes[self.noEdges] 

640 else: 

641 exp = self.flatExp 

642 if saturateAmpInFlat: 

643 exp.maskedImage[regionC00].image.array[:] = 0.0 

644 # Amp C:0,0: minimum=(0, 0), maximum=(99, 50) 

645 x = self.defaultConfig.nPixBorderUpDown 

646 y = self.defaultConfig.nPixBorderLeftRight 

647 width, height = regionC00.getEndX() - x, regionC00.getEndY() - y 

648 # Defects code will mark whole saturated amp as defect box. 

649 shouldBeFound = [Box2I(corner=Point2I(x, y), dimensions=Extent2I(width, height))] 

650 else: 

651 shouldBeFound = self.darkBBoxes[self.noEdges] 

652 # Change the default a bit so it works for the 

653 # existing simulated defects. 

654 task.config.fracThresholdFlat = 0.9 

655 

656 defects = task._findHotAndColdPixels(exp) 

657 

658 boxesMeasured = [] 

659 for defect in defects: 

660 boxesMeasured.append(defect.getBBox()) 

661 

662 for expectedBBox in shouldBeFound: 

663 self.assertIn(expectedBBox, boxesMeasured) 

664 

665 def test_valueThreshold(self): 

666 for fileType in ['dark', 'flat']: 

667 self.valueThreshold(fileType) 

668 # stdDev = 0.0 

669 self.valueThreshold('flat', saturateAmpInFlat=True) 

670 

671 def test_pixelCounting(self): 

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

673 """ 

674 config = copy.copy(self.defaultConfig) 

675 config.nPixBorderUpDown = 0 

676 config.nPixBorderLeftRight = 0 

677 task = self.defaultTask 

678 task.config = config 

679 defects = task._findHotAndColdPixels(self.flatExp) 

680 

681 defectArea = 0 

682 for defect in defects: 

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

684 

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

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

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

688 # Thus, defectArea should be >= crossCheck. 

689 crossCheck = 0 

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

691 crossCheck += sx*sy 

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

693 crossCheck += sx*sy 

694 

695 # Test the result of _nPixFromDefects() 

696 # via two different ways of calculating area. 

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

698 # defectArea should be >= crossCheck 

699 self.assertGreaterEqual(defectArea, crossCheck) 

700 

701 def test_getNumGoodPixels(self): 

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

703 expected. 

704 """ 

705 testImage = self.flatExp.clone() 

706 mi = testImage.maskedImage 

707 

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

709 nGood = self.defaultTask._getNumGoodPixels(mi) 

710 

711 self.assertEqual(imageSize, nGood) 

712 

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

714 

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

716 testImage.mask[noDataBox] |= NODATABIT 

717 

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

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

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

721 

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

723 self.assertEqual(imageSize, nGood) 

724 

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

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

727 testImage.mask[badBox] |= BADBIT 

728 

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

730 

731 def test_edgeMasking(self): 

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

733 _setEdgeBits(). 

734 """ 

735 testImage = self.flatExp.clone() 

736 mi = testImage.maskedImage 

737 

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

739 self.defaultTask._setEdgeBits(mi) 

740 

741 hEdge = self.defaultConfig.nPixBorderLeftRight 

742 vEdge = self.defaultConfig.nPixBorderUpDown 

743 xSize, ySize = mi.getDimensions() 

744 

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

746 

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

748 

749 def test_badImage(self): 

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

751 """ 

752 testImage = self.flatExp.clone() 

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

754 

755 config = copy.copy(self.defaultConfig) 

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

757 config.nPixBorderUpDown = 0 

758 config.nPixBorderLeftRight = 0 

759 

760 task = self.defaultTask 

761 task.config = config 

762 defects = task._findHotAndColdPixels(testImage) 

763 

764 defectArea = 0 

765 for defect in defects: 

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

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

768 

769 

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

771 pass 

772 

773 

774def setup_module(module): 

775 lsst.utils.tests.init() 

776 

777 

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

779 lsst.utils.tests.init() 

780 unittest.main()