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 

135 boxesMeasured = [] 

136 for defect in defectsWithColumns: 

137 boxesMeasured.append(defect.getBBox()) 

138 

139 for boxInput in expectedDefects: 

140 self.assertIn(boxInput, boxesMeasured) 

141 

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

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

144 unionInputExpectedBoxes = [] 

145 for defect in inputDefects: 

146 unionInputExpectedBoxes.append(defect.getBBox()) 

147 for defect in expectedDefects: 

148 unionInputExpectedBoxes.append(defect) 

149 

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

151 for boxMeas in boxesMeasured: 

152 self.assertIn(boxMeas, unionInputExpectedBoxes) 

153 

154 def test_maskBlocks_full_column(self): 

155 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

157 

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

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

160 

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

162 """ 

163 

164 defects = self.allDefectsList 

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

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

167 

168 self.check_maskBlocks(defects, expectedDefects) 

169 

170 def test_maskBlocks_long_column(self): 

171 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

172 Tests that a contigous bad column with Npix >= badOnAndOffPixelColumnThreshold (10) 

173 does not get split by the code. 

174 

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

176 """ 

177 

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

179 defects = self.allDefectsList 

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

181 

182 self.check_maskBlocks(defects, expectedDefects) 

183 

184 def test_maskBlocks_short_column(self): 

185 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

186 Tests that a contigous bad column Npix < badOnAndOffPixelColumnThreshold (10) 

187 does not get split by the code. 

188 

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

190 """ 

191 

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

193 defects = self.allDefectsList 

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

195 

196 self.check_maskBlocks(defects, expectedDefects) 

197 

198 def test_maskBlocks_discontigous_to_single_block(self): 

199 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

203 

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

205 """ 

206 

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

208 defects = self.allDefectsList 

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

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

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

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

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

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

215 for badBox in badPixels: 

216 defects.append(badBox) 

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(1, 12)), 

352 Box2I(corner=Point2I(62, 2), dimensions=Extent2I(1, 12))] 

353 defects = self.allDefectsList 

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

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

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

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

358 for badBox in badPixels: 

359 defects.append(badBox) 

360 

361 self.check_maskBlocks(defects, expectedDefects) 

362 

363 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

364 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

367 goodPixelColumnGapThreshold (5). 

368 

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

370 """ 

371 

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

373 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(1, 12)), 

374 Box2I(corner=Point2I(69, 2), dimensions=Extent2I(1, 12))] 

375 defects = self.allDefectsList 

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

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

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

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

380 for badBox in badPixels: 

381 defects.append(badBox) 

382 

383 self.check_maskBlocks(defects, expectedDefects) 

384 

385 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

386 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

388 m > badOnAndOffPixelColumnThreshold (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 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)), 

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

396 Box2I(corner=Point2I(74, 2), dimensions=Extent2I(1, 12)), 

397 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(1, 12)), 

398 Box2I(corner=Point2I(77, 2), dimensions=Extent2I(1, 12))] 

399 defects = self.allDefectsList 

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

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

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

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

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

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

406 for badBox in badPixels: 

407 defects.append(badBox) 

408 

409 self.check_maskBlocks(defects, expectedDefects) 

410 

411 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

412 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

415 goodPixelColumnGapThreshold (5). 

416 

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

418 """ 

419 

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

421 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(1, 2)), 

422 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(1, 8))] 

423 defects = self.allDefectsList 

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

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

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

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

428 for badBox in badPixels: 

429 defects.append(badBox) 

430 

431 self.check_maskBlocks(defects, expectedDefects) 

432 

433 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

434 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

437 goodPixelColumnGapThreshold (5). 

438 

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

440 """ 

441 

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

443 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(1, 2)), 

444 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(1, 8))] 

445 defects = self.allDefectsList 

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

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

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

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

450 for badBox in badPixels: 

451 defects.append(badBox) 

452 

453 self.check_maskBlocks(defects, expectedDefects) 

454 

455 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

456 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

459 goodPixelColumnGapThreshold (5). 

460 

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

462 """ 

463 

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

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

466 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(1, 9)), 

467 Box2I(corner=Point2I(92, 2), dimensions=Extent2I(1, 7)), 

468 Box2I(corner=Point2I(92, 18), dimensions=Extent2I(1, 9)), 

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

470 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(1, 9)), 

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

472 Box2I(corner=Point2I(95, 18), dimensions=Extent2I(1, 9))] 

473 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

484 for badBox in badPixels: 

485 defects.append(badBox) 

486 

487 self.check_maskBlocks(defects, expectedDefects) 

488 

489 def test_defectFindingAllSensor(self): 

490 config = copy.copy(self.defaultConfig) 

491 config.nPixBorderLeftRight = 0 

492 config.nPixBorderUpDown = 0 

493 

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

495 

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

497 

498 allBBoxes = self.darkBBoxes + self.brightBBoxes 

499 

500 boxesMeasured = [] 

501 for defect in defects: 

502 boxesMeasured.append(defect.getBBox()) 

503 

504 for expectedBBox in allBBoxes: 

505 self.assertIn(expectedBBox, boxesMeasured) 

506 

507 def test_defectFindingEdgeIgnore(self): 

508 config = copy.copy(self.defaultConfig) 

509 config.nPixBorderUpDown = 0 

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

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

512 

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

514 

515 boxesMeasured = [] 

516 for defect in defects: 

517 boxesMeasured.append(defect.getBBox()) 

518 

519 for expectedBBox in shouldBeFound: 

520 self.assertIn(expectedBBox, boxesMeasured) 

521 

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

523 for boxMissed in shouldBeMissed: 

524 self.assertNotIn(boxMissed, boxesMeasured) 

525 

526 def test_postProcessDefectSets(self): 

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

528 

529 There is potential for logic errors in their combination 

530 so several combinations of defects and combination methods 

531 are tested here.""" 

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

533 

534 # defect list has length one 

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

536 self.assertEqual(defects, merged) 

537 

538 # should always be true regardless of config 

539 # defect list now has length 2 

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

541 'FRACTION') 

542 self.assertEqual(defects, merged) 

543 

544 # now start manipulating defect lists 

545 config = copy.copy(self.defaultConfig) 

546 config.combinationMode = 'FRACTION' 

547 config.combinationFraction = 0.85 

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

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

550 

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

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

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

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

555 self.assertEqual(defects, merged) 

556 

557 # remove another and should be under threshold 

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

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

560 self.assertNotEqual(defects, merged) 

561 

562 # now test the AND and OR modes 

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

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

565 self.assertEqual(defects, merged) 

566 

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

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

569 self.assertNotEqual(defects, merged) 

570 

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

572 self.assertEqual(defects, merged) 

573 

574 def test_pixelCounting(self): 

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

576 config = copy.copy(self.defaultConfig) 

577 config.nPixBorderUpDown = 0 

578 config.nPixBorderLeftRight = 0 

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

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

581 

582 defectArea = 0 

583 for defect in defects: 

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

585 

586 # The columnar code will cover blocks of a column 

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

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

589 # Thus, defectArea should be >= crossCheck. 

590 crossCheck = 0 

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

592 crossCheck += sx*sy 

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

594 crossCheck += sx*sy 

595 

596 # Test the result of _nPixFromDefects() 

597 # via two different ways of calculating area. 

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

599 # defectArea should be >= crossCheck 

600 self.assertGreaterEqual(defectArea, crossCheck) 

601 

602 def test_getNumGoodPixels(self): 

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

604 testImage = self.flatExp.clone() 

605 mi = testImage.maskedImage 

606 

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

608 nGood = self.defaultTask._getNumGoodPixels(mi) 

609 

610 self.assertEqual(imageSize, nGood) 

611 

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

613 

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

615 testImage.mask[noDataBox] |= NODATABIT 

616 

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

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

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

620 

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

622 self.assertEqual(imageSize, nGood) 

623 

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

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

626 testImage.mask[badBox] |= BADBIT 

627 

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

629 

630 def test_edgeMasking(self): 

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

632 testImage = self.flatExp.clone() 

633 mi = testImage.maskedImage 

634 

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

636 self.defaultTask._setEdgeBits(mi) 

637 

638 hEdge = self.defaultConfig.nPixBorderLeftRight 

639 vEdge = self.defaultConfig.nPixBorderUpDown 

640 xSize, ySize = mi.getDimensions() 

641 

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

643 

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

645 

646 

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

648 pass 

649 

650 

651def setup_module(module): 

652 lsst.utils.tests.init() 

653 

654 

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

656 lsst.utils.tests.init() 

657 unittest.main()