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.ip.isr as ipIsr 

33import lsst.cp.pipe as cpPipe 

34from lsst.ip.isr import isrMock 

35from lsst.geom import Box2I, Point2I, Extent2I 

36 

37 

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

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

40 

41 def setUp(self): 

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

43 

44 self.flatMean = 2000 

45 self.darkMean = 1 

46 self.readNoiseAdu = 10 

47 self.nSigmaBright = 8 

48 self.nSigmaDark = 8 

49 

50 mockImageConfig = isrMock.IsrMock.ConfigClass() 

51 

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

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

54 mockImageConfig.flatDrop = 0.99999 

55 mockImageConfig.isTrimmed = True 

56 

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

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

59 

60 # x, y, size tuples 

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

62 

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

64 

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

66 

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

68 self.noEdges = slice(nEdge, None) 

69 self.onlyEdges = slice(0, nEdge) 

70 

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

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

73 

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

75 darkWidth = self.readNoiseAdu 

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

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

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

79 

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

81 for defect in self.brightDefects: 

82 y, x, sy, sx = defect 

83 # are these actually the numbers we want? 

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

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

86 

87 for defect in self.darkDefects: 

88 y, x, sy, sx = defect 

89 # are these actually the numbers we want? 

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

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

92 

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

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

95 

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

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

98 

99 self.defaultTask = cpPipe.defects.FindDefectsTask() # config=self.defaultConfig) 

100 

101 self.allDefectsList = ipIsr.Defects() 

102 

103 self.brightDefectsList = ipIsr.Defects() 

104 for d in self.brightBBoxes: 

105 self.brightDefectsList.append(d) 

106 self.allDefectsList.append(d) 

107 

108 self.darkDefectsList = ipIsr.Defects() 

109 for d in self.darkBBoxes: 

110 self.darkDefectsList.append(d) 

111 self.allDefectsList.append(d) 

112 

113 def check_maskBlocks(self, inputDefects, expectedDefects): 

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

115 """ 

116 config = copy.copy(self.defaultConfig) 

117 config.measure.badOnAndOffPixelColumnThreshold = 10 

118 config.measure.goodPixelColumnGapThreshold = 5 

119 config.measure.nPixBorderUpDown = 0 

120 config.measure.nPixBorderLeftRight = 0 

121 

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

123 

124 defectsWithColumns = task.measure.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects) 

125 boxesMeasured = [] 

126 for defect in defectsWithColumns: 

127 boxesMeasured.append(defect.getBBox()) 

128 

129 for boxInput in expectedDefects: 

130 self.assertIn(boxInput, boxesMeasured) 

131 

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

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

134 unionInputExpectedBoxes = [] 

135 for defect in inputDefects: 

136 unionInputExpectedBoxes.append(defect.getBBox()) 

137 for defect in expectedDefects: 

138 unionInputExpectedBoxes.append(defect) 

139 

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

141 for boxMeas in boxesMeasured: 

142 self.assertIn(boxMeas, unionInputExpectedBoxes) 

143 

144 def test_maskBlocks_full_column(self): 

145 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

147 

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

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

150 

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

152 """ 

153 

154 defects = self.allDefectsList 

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

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

157 

158 self.check_maskBlocks(defects, expectedDefects) 

159 

160 def test_maskBlocks_long_column(self): 

161 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

163 does not get split by the code. 

164 

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

166 """ 

167 

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

169 defects = self.allDefectsList 

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

171 

172 self.check_maskBlocks(defects, expectedDefects) 

173 

174 def test_maskBlocks_short_column(self): 

175 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

177 does not get split by the code. 

178 

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

180 """ 

181 

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

183 defects = self.allDefectsList 

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

185 

186 self.check_maskBlocks(defects, expectedDefects) 

187 

188 def test_maskBlocks_discontigous_to_single_block(self): 

189 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

193 

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

195 """ 

196 

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

198 defects = self.allDefectsList 

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

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

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

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

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

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

205 for badBox in badPixels: 

206 defects.append(badBox) 

207 

208 self.check_maskBlocks(defects, expectedDefects) 

209 

210 def test_maskBlocks_discontigous_less_than_thresholds(self): 

211 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

215 

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

217 """ 

218 

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

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

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

222 defects = self.allDefectsList 

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

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

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

226 for badBox in badPixels: 

227 defects.append(badBox) 

228 

229 self.check_maskBlocks(defects, expectedDefects) 

230 

231 def test_maskBlocks_more_than_thresholds(self): 

232 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

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

237 

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

239 """ 

240 

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

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

243 defects = self.allDefectsList 

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

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

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

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

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

249 for badBox in badPixels: 

250 defects.append(badBox) 

251 

252 self.check_maskBlocks(defects, expectedDefects) 

253 

254 def test_maskBlocks_not_enough_bad_pixels_in_column(self): 

255 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

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

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

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

261 

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

263 """ 

264 

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

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

267 defects = self.allDefectsList 

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

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

270 for badBox in badPixels: 

271 defects.append(badBox) 

272 

273 self.check_maskBlocks(defects, expectedDefects) 

274 

275 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self): 

276 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

278 and every other pixel is bad. 

279 

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

281 """ 

282 

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

284 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

301 for badBox in badPixels: 

302 defects.append(badBox) 

303 

304 self.check_maskBlocks(defects, expectedDefects) 

305 

306 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self): 

307 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

309 and every other pixel is bad. 

310 

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

312 """ 

313 

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

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

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

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

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

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

320 defects = self.allDefectsList 

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

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

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

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

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

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

327 for badBox in badPixels: 

328 defects.append(badBox) 

329 

330 self.check_maskBlocks(defects, expectedDefects) 

331 

332 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self): 

333 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

336 goodPixelColumnGapThreshold (5). 

337 

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

339 """ 

340 

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

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

343 defects = self.allDefectsList 

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

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

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

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

348 for badBox in badPixels: 

349 defects.append(badBox) 

350 

351 self.check_maskBlocks(defects, expectedDefects) 

352 

353 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self): 

354 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

357 goodPixelColumnGapThreshold (5). 

358 

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

360 """ 

361 

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

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

364 defects = self.allDefectsList 

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

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

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

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

369 for badBox in badPixels: 

370 defects.append(badBox) 

371 

372 self.check_maskBlocks(defects, expectedDefects) 

373 

374 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self): 

375 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

378 goodPixelColumnGapThreshold (5). 

379 

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

381 """ 

382 

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

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

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

386 defects = self.allDefectsList 

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

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

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

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

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

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

393 for badBox in badPixels: 

394 defects.append(badBox) 

395 

396 self.check_maskBlocks(defects, expectedDefects) 

397 

398 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self): 

399 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

402 goodPixelColumnGapThreshold (5). 

403 

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

405 """ 

406 

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

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

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

410 defects = self.allDefectsList 

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

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

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

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

415 for badBox in badPixels: 

416 defects.append(badBox) 

417 

418 self.check_maskBlocks(defects, expectedDefects) 

419 

420 def test_maskBlocks_other_side_good_greater_than_threshold(self): 

421 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

424 goodPixelColumnGapThreshold (5). 

425 

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

427 """ 

428 

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

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

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

432 defects = self.allDefectsList 

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

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

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

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

437 for badBox in badPixels: 

438 defects.append(badBox) 

439 

440 self.check_maskBlocks(defects, expectedDefects) 

441 

442 def test_maskBlocks_both_sides_good_greater_than_threshold(self): 

443 """A test for maskBlocksIfIntermitentBadPixelsInColumn. 

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

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

446 goodPixelColumnGapThreshold (5). 

447 

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

449 """ 

450 

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

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

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

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

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

456 defects = self.allDefectsList 

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

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

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

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

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

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

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

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

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

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

467 for badBox in badPixels: 

468 defects.append(badBox) 

469 

470 self.check_maskBlocks(defects, expectedDefects) 

471 

472 def test_defectFindingAllSensor(self): 

473 config = copy.copy(self.defaultConfig) 

474 config.measure.nPixBorderLeftRight = 0 

475 config.measure.nPixBorderUpDown = 0 

476 

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

478 

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

480 config.measure.nSigmaDark]) 

481 

482 allBBoxes = self.darkBBoxes + self.brightBBoxes 

483 

484 boxesMeasured = [] 

485 for defect in defects: 

486 boxesMeasured.append(defect.getBBox()) 

487 

488 for expectedBBox in allBBoxes: 

489 self.assertIn(expectedBBox, boxesMeasured) 

490 

491 def test_defectFindingEdgeIgnore(self): 

492 config = copy.copy(self.defaultConfig) 

493 config.measure.nPixBorderUpDown = 0 

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

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

496 config.measure.nSigmaDark]) 

497 

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

499 

500 boxesMeasured = [] 

501 for defect in defects: 

502 boxesMeasured.append(defect.getBBox()) 

503 

504 for expectedBBox in shouldBeFound: 

505 self.assertIn(expectedBBox, boxesMeasured) 

506 

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

508 for boxMissed in shouldBeMissed: 

509 self.assertNotIn(boxMissed, boxesMeasured) 

510 

511 def test_pixelCounting(self): 

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

513 config = copy.copy(self.defaultConfig) 

514 config.measure.nPixBorderUpDown = 0 

515 config.measure.nPixBorderLeftRight = 0 

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

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

518 config.measure.nSigmaDark]) 

519 

520 defectArea = 0 

521 for defect in defects: 

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

523 

524 # The columnar code will cover blocks of a column 

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

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

527 # Thus, defectArea should be >= crossCheck. 

528 crossCheck = 0 

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

530 crossCheck += sx*sy 

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

532 crossCheck += sx*sy 

533 

534 # Test the result of _nPixFromDefects() 

535 # via two different ways of calculating area. 

536 self.assertEqual(defectArea, task.measure._nPixFromDefects(defects)) 

537 # defectArea should be >= crossCheck 

538 self.assertGreaterEqual(defectArea, crossCheck) 

539 

540 def test_getNumGoodPixels(self): 

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

542 testImage = self.flatExp.clone() 

543 mi = testImage.maskedImage 

544 

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

546 nGood = self.defaultTask.measure._getNumGoodPixels(mi) 

547 

548 self.assertEqual(imageSize, nGood) 

549 

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

551 

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

553 testImage.mask[noDataBox] |= NODATABIT 

554 

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

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

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

558 

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

560 self.assertEqual(imageSize, nGood) 

561 

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

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

564 testImage.mask[badBox] |= BADBIT 

565 

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

567 

568 def test_edgeMasking(self): 

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

570 testImage = self.flatExp.clone() 

571 mi = testImage.maskedImage 

572 

573 self.assertEqual(cpPipe.utils.countMaskedPixels(mi, 'EDGE'), 0) 

574 self.defaultTask.measure._setEdgeBits(mi) 

575 

576 hEdge = self.defaultConfig.measure.nPixBorderLeftRight 

577 vEdge = self.defaultConfig.measure.nPixBorderUpDown 

578 xSize, ySize = mi.getDimensions() 

579 

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

581 

582 self.assertEqual(cpPipe.utils.countMaskedPixels(mi, 'EDGE'), nEdge) 

583 

584 

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

586 pass 

587 

588 

589def setup_module(module): 

590 lsst.utils.tests.init() 

591 

592 

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

594 lsst.utils.tests.init() 

595 unittest.main()