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.doAddDistortionModel = False 

48 config.doUseOpticsTransmission = False 

49 config.doUseFilterTransmission = False 

50 config.doUseSensorTransmission = False 

51 config.doUseAtmosphereTransmission = False 

52 config.doAttachTransmissionCurve = False 

53 

54 self.flatMean = 2000 

55 self.darkMean = 1 

56 self.readNoiseAdu = 10 

57 self.nSigmaBright = 8 

58 self.nSigmaDark = 8 

59 

60 mockImageConfig = isrMock.IsrMock.ConfigClass() 

61 

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

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

64 mockImageConfig.flatDrop = 0.99999 

65 mockImageConfig.isTrimmed = True 

66 

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

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

69 

70 # x, y, size tuples 

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

72 

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

74 

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

76 

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

78 self.noEdges = slice(nEdge, None) 

79 self.onlyEdges = slice(0, nEdge) 

80 

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

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

83 

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

85 darkWidth = self.readNoiseAdu 

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

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

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

89 

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

91 for defect in self.brightDefects: 

92 y, x, sy, sx = defect 

93 # are these actually the numbers we want? 

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

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

96 

97 for defect in self.darkDefects: 

98 y, x, sy, sx = defect 

99 # are these actually the numbers we want? 

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

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

102 

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

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

105 

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

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

108 

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

110 

111 self.allDefectsList = measAlg.Defects() 

112 

113 self.brightDefectsList = measAlg.Defects() 

114 for d in self.brightBBoxes: 

115 self.brightDefectsList.append(d) 

116 self.allDefectsList.append(d) 

117 

118 self.darkDefectsList = measAlg.Defects() 

119 for d in self.darkBBoxes: 

120 self.darkDefectsList.append(d) 

121 self.allDefectsList.append(d) 

122 

123 def check_maskBlocks(self, inputDefects, expectedDefects): 

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

125 """ 

126 config = copy.copy(self.defaultConfig) 

127 config.badOnAndOffPixelColumnThreshold = 10 

128 config.goodPixelColumnGapThreshold = 5 

129 config.nPixBorderUpDown = 0 

130 config.nPixBorderLeftRight = 0 

131 

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

133 

134 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects) 

135 

136 boxesMeasured = [] 

137 for defect in defectsWithColumns: 

138 boxesMeasured.append(defect.getBBox()) 

139 

140 for boxInput in expectedDefects: 

141 self.assertIn(boxInput, boxesMeasured) 

142 

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

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

145 unionInputExpectedBoxes = [] 

146 for defect in inputDefects: 

147 unionInputExpectedBoxes.append(defect.getBBox()) 

148 for defect in expectedDefects: 

149 unionInputExpectedBoxes.append(defect) 

150 

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

152 for boxMeas in boxesMeasured: 

153 self.assertIn(boxMeas, unionInputExpectedBoxes) 

154 

155 def test_maskBlocks_full_column(self): 

156 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

158 

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

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

161 

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

163 """ 

164 

165 defects = self.allDefectsList 

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

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

168 

169 self.check_maskBlocks(defects, expectedDefects) 

170 

171 def test_maskBlocks_long_column(self): 

172 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

174 does not get split by the code. 

175 

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

177 """ 

178 

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

180 defects = self.allDefectsList 

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

182 

183 self.check_maskBlocks(defects, expectedDefects) 

184 

185 def test_maskBlocks_short_column(self): 

186 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

188 does not get split by the code. 

189 

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

191 """ 

192 

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

194 defects = self.allDefectsList 

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

196 

197 self.check_maskBlocks(defects, expectedDefects) 

198 

199 def test_maskBlocks_discontigous_to_single_block(self): 

200 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

204 

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

206 """ 

207 

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

209 defects = self.allDefectsList 

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

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

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

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

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

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

216 for badBox in badPixels: 

217 defects.append(badBox) 

218 self.check_maskBlocks(defects, expectedDefects) 

219 

220 def test_maskBlocks_discontigous_less_than_thresholds(self): 

221 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

225 

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

227 """ 

228 

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

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

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

232 defects = self.allDefectsList 

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

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

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

236 for badBox in badPixels: 

237 defects.append(badBox) 

238 

239 self.check_maskBlocks(defects, expectedDefects) 

240 

241 def test_maskBlocks_more_than_thresholds(self): 

242 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

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

247 

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

249 """ 

250 

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

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

253 defects = self.allDefectsList 

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

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

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

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

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

259 for badBox in badPixels: 

260 defects.append(badBox) 

261 

262 self.check_maskBlocks(defects, expectedDefects) 

263 

264 def test_maskBlocks_not_enough_bad_pixels_in_column(self): 

265 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

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

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

271 

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

273 """ 

274 

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

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

277 defects = self.allDefectsList 

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

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

280 for badBox in badPixels: 

281 defects.append(badBox) 

282 

283 self.check_maskBlocks(defects, expectedDefects) 

284 

285 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self): 

286 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

288 and every other pixel is bad. 

289 

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

291 """ 

292 

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

294 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

311 for badBox in badPixels: 

312 defects.append(badBox) 

313 

314 self.check_maskBlocks(defects, expectedDefects) 

315 

316 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self): 

317 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

319 and every other pixel is bad. 

320 

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

322 """ 

323 

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

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

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

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

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

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

330 defects = self.allDefectsList 

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

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

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

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

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

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

337 for badBox in badPixels: 

338 defects.append(badBox) 

339 

340 self.check_maskBlocks(defects, expectedDefects) 

341 

342 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self): 

343 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

346 goodPixelColumnGapThreshold (5). 

347 

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

349 """ 

350 

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

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

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

354 defects = self.allDefectsList 

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

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

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

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

359 for badBox in badPixels: 

360 defects.append(badBox) 

361 

362 self.check_maskBlocks(defects, expectedDefects) 

363 

364 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

365 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

368 goodPixelColumnGapThreshold (5). 

369 

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

371 """ 

372 

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

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

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

376 defects = self.allDefectsList 

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

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

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

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

381 for badBox in badPixels: 

382 defects.append(badBox) 

383 

384 self.check_maskBlocks(defects, expectedDefects) 

385 

386 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

387 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

390 goodPixelColumnGapThreshold (5). 

391 

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

393 """ 

394 

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

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

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

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

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

400 defects = self.allDefectsList 

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

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

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

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

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

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

407 for badBox in badPixels: 

408 defects.append(badBox) 

409 

410 self.check_maskBlocks(defects, expectedDefects) 

411 

412 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

413 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

416 goodPixelColumnGapThreshold (5). 

417 

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

419 """ 

420 

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

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

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

424 defects = self.allDefectsList 

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

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

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

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

429 for badBox in badPixels: 

430 defects.append(badBox) 

431 

432 self.check_maskBlocks(defects, expectedDefects) 

433 

434 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

435 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

438 goodPixelColumnGapThreshold (5). 

439 

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

441 """ 

442 

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

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

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

446 defects = self.allDefectsList 

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

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

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

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

451 for badBox in badPixels: 

452 defects.append(badBox) 

453 

454 self.check_maskBlocks(defects, expectedDefects) 

455 

456 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

457 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

460 goodPixelColumnGapThreshold (5). 

461 

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

463 """ 

464 

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

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

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

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

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

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

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

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

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

474 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

485 for badBox in badPixels: 

486 defects.append(badBox) 

487 

488 self.check_maskBlocks(defects, expectedDefects) 

489 

490 def test_defectFindingAllSensor(self): 

491 config = copy.copy(self.defaultConfig) 

492 config.nPixBorderLeftRight = 0 

493 config.nPixBorderUpDown = 0 

494 

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

496 

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

498 

499 allBBoxes = self.darkBBoxes + self.brightBBoxes 

500 

501 boxesMeasured = [] 

502 for defect in defects: 

503 boxesMeasured.append(defect.getBBox()) 

504 

505 for expectedBBox in allBBoxes: 

506 self.assertIn(expectedBBox, boxesMeasured) 

507 

508 def test_defectFindingEdgeIgnore(self): 

509 config = copy.copy(self.defaultConfig) 

510 config.nPixBorderUpDown = 0 

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

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

513 

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

515 

516 boxesMeasured = [] 

517 for defect in defects: 

518 boxesMeasured.append(defect.getBBox()) 

519 

520 for expectedBBox in shouldBeFound: 

521 self.assertIn(expectedBBox, boxesMeasured) 

522 

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

524 for boxMissed in shouldBeMissed: 

525 self.assertNotIn(boxMissed, boxesMeasured) 

526 

527 def test_postProcessDefectSets(self): 

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

529 

530 There is potential for logic errors in their combination 

531 so several combinations of defects and combination methods 

532 are tested here.""" 

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

534 

535 # defect list has length one 

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

537 self.assertEqual(defects, merged) 

538 

539 # should always be true regardless of config 

540 # defect list now has length 2 

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

542 'FRACTION') 

543 self.assertEqual(defects, merged) 

544 

545 # now start manipulating defect lists 

546 config = copy.copy(self.defaultConfig) 

547 config.combinationMode = 'FRACTION' 

548 config.combinationFraction = 0.85 

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

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

551 

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

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

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

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

556 self.assertEqual(defects, merged) 

557 

558 # remove another and should be under threshold 

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

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

561 self.assertNotEqual(defects, merged) 

562 

563 # now test the AND and OR modes 

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

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

566 self.assertEqual(defects, merged) 

567 

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

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

570 self.assertNotEqual(defects, merged) 

571 

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

573 self.assertEqual(defects, merged) 

574 

575 def test_pixelCounting(self): 

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

577 config = copy.copy(self.defaultConfig) 

578 config.nPixBorderUpDown = 0 

579 config.nPixBorderLeftRight = 0 

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

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

582 

583 defectArea = 0 

584 for defect in defects: 

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

586 

587 # The columnar code will cover blocks of a column 

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

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

590 # Thus, defectArea should be >= crossCheck. 

591 crossCheck = 0 

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

593 crossCheck += sx*sy 

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

595 crossCheck += sx*sy 

596 

597 # Test the result of _nPixFromDefects() 

598 # via two different ways of calculating area. 

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

600 # defectArea should be >= crossCheck 

601 self.assertGreaterEqual(defectArea, crossCheck) 

602 

603 def test_getNumGoodPixels(self): 

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

605 testImage = self.flatExp.clone() 

606 mi = testImage.maskedImage 

607 

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

609 nGood = self.defaultTask._getNumGoodPixels(mi) 

610 

611 self.assertEqual(imageSize, nGood) 

612 

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

614 

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

616 testImage.mask[noDataBox] |= NODATABIT 

617 

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

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

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

621 

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

623 self.assertEqual(imageSize, nGood) 

624 

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

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

627 testImage.mask[badBox] |= BADBIT 

628 

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

630 

631 def test_edgeMasking(self): 

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

633 testImage = self.flatExp.clone() 

634 mi = testImage.maskedImage 

635 

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

637 self.defaultTask._setEdgeBits(mi) 

638 

639 hEdge = self.defaultConfig.nPixBorderLeftRight 

640 vEdge = self.defaultConfig.nPixBorderUpDown 

641 xSize, ySize = mi.getDimensions() 

642 

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

644 

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

646 

647 

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

649 pass 

650 

651 

652def setup_module(module): 

653 lsst.utils.tests.init() 

654 

655 

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

657 lsst.utils.tests.init() 

658 unittest.main()