Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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.cp.pipe as cpPipe 

33from lsst.cp.pipe.utils import countMaskedPixels 

34from lsst.ip.isr import isrMock 

35from lsst.geom import Box2I, Point2I, Extent2I 

36import lsst.meas.algorithms as measAlg 

37 

38 

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

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

41 

42 def setUp(self): 

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

44 

45 for config in [self.defaultConfig.isrForDarks, self.defaultConfig.isrForFlats]: 

46 config.doCrosstalk = False 

47 config.doUseOpticsTransmission = False 

48 config.doUseFilterTransmission = False 

49 config.doUseSensorTransmission = False 

50 config.doUseAtmosphereTransmission = False 

51 config.doAttachTransmissionCurve = False 

52 

53 self.flatMean = 2000 

54 self.darkMean = 1 

55 self.readNoiseAdu = 10 

56 self.nSigmaBright = 8 

57 self.nSigmaDark = 8 

58 

59 mockImageConfig = isrMock.IsrMock.ConfigClass() 

60 

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

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

63 mockImageConfig.flatDrop = 0.99999 

64 mockImageConfig.isTrimmed = True 

65 

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

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

68 

69 # x, y, size tuples 

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

71 

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

73 

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

75 

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

77 self.noEdges = slice(nEdge, None) 

78 self.onlyEdges = slice(0, nEdge) 

79 

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

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

82 

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

84 darkWidth = self.readNoiseAdu 

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

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

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

88 

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

90 for defect in self.brightDefects: 

91 y, x, sy, sx = defect 

92 # are these actually the numbers we want? 

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

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

95 

96 for defect in self.darkDefects: 

97 y, x, sy, sx = defect 

98 # are these actually the numbers we want? 

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

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

101 

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

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

104 

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

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

107 

108 self.defaultTask = cpPipe.defects.FindDefectsTask(config=self.defaultConfig) 

109 

110 self.allDefectsList = measAlg.Defects() 

111 

112 self.brightDefectsList = measAlg.Defects() 

113 for d in self.brightBBoxes: 

114 self.brightDefectsList.append(d) 

115 self.allDefectsList.append(d) 

116 

117 self.darkDefectsList = measAlg.Defects() 

118 for d in self.darkBBoxes: 

119 self.darkDefectsList.append(d) 

120 self.allDefectsList.append(d) 

121 

122 def check_maskBlocks(self, inputDefects, expectedDefects): 

123 """A helper function for the tests of maskBlocksIfIntermitentBadPixelsInColumn. 

124 """ 

125 config = copy.copy(self.defaultConfig) 

126 config.badOnAndOffPixelColumnThreshold = 10 

127 config.goodPixelColumnGapThreshold = 5 

128 config.nPixBorderUpDown = 0 

129 config.nPixBorderLeftRight = 0 

130 

131 task = cpPipe.defects.FindDefectsTask(config=config) 

132 

133 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects) 

134 boxesMeasured = [] 

135 for defect in defectsWithColumns: 

136 boxesMeasured.append(defect.getBBox()) 

137 

138 for boxInput in expectedDefects: 

139 self.assertIn(boxInput, boxesMeasured) 

140 

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

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

143 unionInputExpectedBoxes = [] 

144 for defect in inputDefects: 

145 unionInputExpectedBoxes.append(defect.getBBox()) 

146 for defect in expectedDefects: 

147 unionInputExpectedBoxes.append(defect) 

148 

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

150 for boxMeas in boxesMeasured: 

151 self.assertIn(boxMeas, unionInputExpectedBoxes) 

152 

153 def test_maskBlocks_full_column(self): 

154 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

155 Tests that a contigous bad column does not get split by the code. 

156 

157 The mock flat has a size of 200X204 pixels. This column has a maximum length of 50 

158 pixels, otherwise there would be a split along the mock amp boundary. 

159 

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

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 Tests that a contigous bad column with Npix >= badOnAndOffPixelColumnThreshold (10) 

172 does not get split by the code. 

173 

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

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 Tests that a contigous bad column Npix < badOnAndOffPixelColumnThreshold (10) 

186 does not get split by the code. 

187 

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

189 """ 

190 

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

192 defects = self.allDefectsList 

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

194 

195 self.check_maskBlocks(defects, expectedDefects) 

196 

197 def test_maskBlocks_discontigous_to_single_block(self): 

198 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

199 Npix discontiguous bad pixels in a column where Npix >= badOnAndOffPixelColumnThreshold (10) 

200 and gaps of good pixels < goodPixelColumnGapThreshold (5). Under these conditions, the whole 

201 block of bad pixels (including good gaps) should be masked. 

202 

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

204 """ 

205 

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

207 defects = self.allDefectsList 

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

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

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

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

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

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

214 for badBox in badPixels: 

215 defects.append(badBox) 

216 

217 self.check_maskBlocks(defects, expectedDefects) 

218 

219 def test_maskBlocks_discontigous_less_than_thresholds(self): 

220 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

221 Npix discontiguous bad pixels in a column where Npix < badOnAndOffPixelColumnThreshold (10) 

222 and gaps of good pixels < goodPixelColumnGapThreshold (5). Under these conditions, 

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

224 

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

226 """ 

227 

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

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

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

231 defects = self.allDefectsList 

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

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

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

235 for badBox in badPixels: 

236 defects.append(badBox) 

237 

238 self.check_maskBlocks(defects, expectedDefects) 

239 

240 def test_maskBlocks_more_than_thresholds(self): 

241 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

242 Npix discontiguous bad pixels in a column where Npix < badOnAndOffPixelColumnThreshold (10) 

243 and gaps of good pixels < goodPixelColumnGapThreshold (5). 

244 Npix=34 (> 10) bad pixels total, 1 "good" gap with 13 pixels big enough 

245 (13 >= 5 good pixels, from y=6 (1+5) to y=19). 

246 

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

248 """ 

249 

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

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

252 defects = self.allDefectsList 

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

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

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

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

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

258 for badBox in badPixels: 

259 defects.append(badBox) 

260 

261 self.check_maskBlocks(defects, expectedDefects) 

262 

263 def test_maskBlocks_not_enough_bad_pixels_in_column(self): 

264 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

265 Npix discontiguous bad pixels in a column where Npix < badOnAndOffPixelColumnThreshold (10) and 

266 and gaps of good pixels > goodPixelColumnGapThreshold (5). Since Npix < 

267 badOnAndOffPixelColumnThreshold, then it doesn't matter that the number of good pixels in gap > 

268 goodPixelColumnGapThreshold. 5<10 bad pixels total, 1 "good" gap big enough 

269 (29>=5 good pixels, from y =12 (10+2) to y=30) 

270 

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

272 """ 

273 

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

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

276 defects = self.allDefectsList 

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

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

279 for badBox in badPixels: 

280 defects.append(badBox) 

281 

282 self.check_maskBlocks(defects, expectedDefects) 

283 

284 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self): 

285 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

286 Npix discontiguous bad pixels in a column where Npix > badOnAndOffPixelColumnThreshold (10) 

287 and every other pixel is bad. 

288 

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

290 """ 

291 

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

293 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

310 for badBox in badPixels: 

311 defects.append(badBox) 

312 

313 self.check_maskBlocks(defects, expectedDefects) 

314 

315 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self): 

316 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

317 Npix discontiguous bad pixels in a column where Npix > badOnAndOffPixelColumnThreshold (10) 

318 and every other pixel is bad. 

319 

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

321 """ 

322 

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

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

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

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

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

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

329 defects = self.allDefectsList 

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

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

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

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

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

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

336 for badBox in badPixels: 

337 defects.append(badBox) 

338 

339 self.check_maskBlocks(defects, expectedDefects) 

340 

341 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self): 

342 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

343 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to one side, 

344 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs < 

345 goodPixelColumnGapThreshold (5). 

346 

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

348 """ 

349 

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

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

352 defects = self.allDefectsList 

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

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

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

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

357 for badBox in badPixels: 

358 defects.append(badBox) 

359 

360 self.check_maskBlocks(defects, expectedDefects) 

361 

362 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

363 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

364 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to the other side, 

365 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs < 

366 goodPixelColumnGapThreshold (5). 

367 

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

369 """ 

370 

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

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

373 defects = self.allDefectsList 

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

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

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

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

378 for badBox in badPixels: 

379 defects.append(badBox) 

380 

381 self.check_maskBlocks(defects, expectedDefects) 

382 

383 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

384 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

385 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to both sides, 

386 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs < 

387 goodPixelColumnGapThreshold (5). 

388 

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

390 """ 

391 

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

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

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

395 defects = self.allDefectsList 

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

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

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

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

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

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

402 for badBox in badPixels: 

403 defects.append(badBox) 

404 

405 self.check_maskBlocks(defects, expectedDefects) 

406 

407 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

408 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

409 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to one side, 

410 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs > 

411 goodPixelColumnGapThreshold (5). 

412 

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

414 """ 

415 

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

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

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

419 defects = self.allDefectsList 

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

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

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

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

424 for badBox in badPixels: 

425 defects.append(badBox) 

426 

427 self.check_maskBlocks(defects, expectedDefects) 

428 

429 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

430 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

431 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to the other side, 

432 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs > 

433 goodPixelColumnGapThreshold (5). 

434 

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

436 """ 

437 

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

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

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

441 defects = self.allDefectsList 

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

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

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

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

446 for badBox in badPixels: 

447 defects.append(badBox) 

448 

449 self.check_maskBlocks(defects, expectedDefects) 

450 

451 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

452 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

453 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to both sides, 

454 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs > 

455 goodPixelColumnGapThreshold (5). 

456 

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

458 """ 

459 

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

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

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

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

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

465 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

476 for badBox in badPixels: 

477 defects.append(badBox) 

478 

479 self.check_maskBlocks(defects, expectedDefects) 

480 

481 def test_defectFindingAllSensor(self): 

482 config = copy.copy(self.defaultConfig) 

483 config.nPixBorderLeftRight = 0 

484 config.nPixBorderUpDown = 0 

485 

486 task = cpPipe.defects.FindDefectsTask(config=config) 

487 

488 defects = task.findHotAndColdPixels(self.flatExp, 'flat') 

489 

490 allBBoxes = self.darkBBoxes + self.brightBBoxes 

491 

492 boxesMeasured = [] 

493 for defect in defects: 

494 boxesMeasured.append(defect.getBBox()) 

495 

496 for expectedBBox in allBBoxes: 

497 self.assertIn(expectedBBox, boxesMeasured) 

498 

499 def test_defectFindingEdgeIgnore(self): 

500 config = copy.copy(self.defaultConfig) 

501 config.nPixBorderUpDown = 0 

502 task = cpPipe.defects.FindDefectsTask(config=config) 

503 defects = task.findHotAndColdPixels(self.flatExp, 'flat') 

504 

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

506 

507 boxesMeasured = [] 

508 for defect in defects: 

509 boxesMeasured.append(defect.getBBox()) 

510 

511 for expectedBBox in shouldBeFound: 

512 self.assertIn(expectedBBox, boxesMeasured) 

513 

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

515 for boxMissed in shouldBeMissed: 

516 self.assertNotIn(boxMissed, boxesMeasured) 

517 

518 def test_postProcessDefectSets(self): 

519 """Tests the way in which the defect sets merge. 

520 

521 There is potential for logic errors in their combination 

522 so several combinations of defects and combination methods 

523 are tested here.""" 

524 defects = self.defaultTask.findHotAndColdPixels(self.flatExp, 'flat') 

525 

526 # defect list has length one 

527 merged = self.defaultTask._postProcessDefectSets([defects], self.flatExp.getDimensions(), 'FRACTION') 

528 self.assertEqual(defects, merged) 

529 

530 # should always be true regardless of config 

531 # defect list now has length 2 

532 merged = self.defaultTask._postProcessDefectSets([defects, defects], self.flatExp.getDimensions(), 

533 'FRACTION') 

534 self.assertEqual(defects, merged) 

535 

536 # now start manipulating defect lists 

537 config = copy.copy(self.defaultConfig) 

538 config.combinationMode = 'FRACTION' 

539 config.combinationFraction = 0.85 

540 task = cpPipe.defects.FindDefectsTask(config=config) 

541 merged = task._postProcessDefectSets([defects, defects], self.flatExp.getDimensions(), 'FRACTION') 

542 

543 defectList = [defects]*10 # 10 identical defect sets 

544 # remove one defect from one of them, should still be over threshold 

545 defectList[7] = defectList[7][:-1] 

546 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'FRACTION') 

547 self.assertEqual(defects, merged) 

548 

549 # remove another and should be under threshold 

550 defectList[3] = defectList[3][:-1] 

551 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'FRACTION') 

552 self.assertNotEqual(defects, merged) 

553 

554 # now test the AND and OR modes 

555 defectList = [defects]*10 # 10 identical defect sets 

556 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND') 

557 self.assertEqual(defects, merged) 

558 

559 defectList[7] = defectList[7][:-1] 

560 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND') 

561 self.assertNotEqual(defects, merged) 

562 

563 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'OR') 

564 self.assertEqual(defects, merged) 

565 

566 def test_pixelCounting(self): 

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

568 config = copy.copy(self.defaultConfig) 

569 config.nPixBorderUpDown = 0 

570 config.nPixBorderLeftRight = 0 

571 task = cpPipe.defects.FindDefectsTask(config=config) 

572 defects = task.findHotAndColdPixels(self.flatExp, 'flat') 

573 

574 defectArea = 0 

575 for defect in defects: 

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

577 

578 # The columnar code will cover blocks of a column 

579 # with on-and-off pixels, thus creating more bad pixels 

580 # that what initially placed in self.brightDefects and self.darkDefects. 

581 # Thus, defectArea should be >= crossCheck. 

582 crossCheck = 0 

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

584 crossCheck += sx*sy 

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

586 crossCheck += sx*sy 

587 

588 # Test the result of _nPixFromDefects() 

589 # via two different ways of calculating area. 

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

591 # defectArea should be >= crossCheck 

592 self.assertGreaterEqual(defectArea, crossCheck) 

593 

594 def test_getNumGoodPixels(self): 

595 """Test the the number of pixels in the image not masked is as expected.""" 

596 testImage = self.flatExp.clone() 

597 mi = testImage.maskedImage 

598 

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

600 nGood = self.defaultTask._getNumGoodPixels(mi) 

601 

602 self.assertEqual(imageSize, nGood) 

603 

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

605 

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

607 testImage.mask[noDataBox] |= NODATABIT 

608 

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

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

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

612 

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

614 self.assertEqual(imageSize, nGood) 

615 

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

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

618 testImage.mask[badBox] |= BADBIT 

619 

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

621 

622 def test_edgeMasking(self): 

623 """Check that the right number of edge pixels are masked by _setEdgeBits()""" 

624 testImage = self.flatExp.clone() 

625 mi = testImage.maskedImage 

626 

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

628 self.defaultTask._setEdgeBits(mi) 

629 

630 hEdge = self.defaultConfig.nPixBorderLeftRight 

631 vEdge = self.defaultConfig.nPixBorderUpDown 

632 xSize, ySize = mi.getDimensions() 

633 

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

635 

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

637 

638 

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

640 pass 

641 

642 

643def setup_module(module): 

644 lsst.utils.tests.init() 

645 

646 

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

648 lsst.utils.tests.init() 

649 unittest.main()