Coverage for tests/test_defects.py : 11%

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."""
25import unittest
26import numpy as np
27import copy
29import lsst.utils
30import lsst.utils.tests
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
39class FindDefectsTaskTestCase(lsst.utils.tests.TestCase):
40 """A test case for the defect finding task."""
42 def setUp(self):
43 self.defaultConfig = cpPipe.defects.FindDefectsTask.ConfigClass()
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
53 self.flatMean = 2000
54 self.darkMean = 1
55 self.readNoiseAdu = 10
56 self.nSigmaBright = 8
57 self.nSigmaDark = 8
59 mockImageConfig = isrMock.IsrMock.ConfigClass()
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
66 self.flatExp = isrMock.FlatMock(config=mockImageConfig).run()
67 (shapeY, shapeX) = self.flatExp.getDimensions()
69 # x, y, size tuples
70 # always put edge defects at the start and change the value of nEdge
72 self.brightDefects = [(0, 15, 3, 3), (100, 123, 1, 1)]
74 self.darkDefects = [(5, 0, 1, 1), (7, 62, 2, 2)]
76 nEdge = 1 # NOTE: update if more edge defects are included
77 self.noEdges = slice(nEdge, None)
78 self.onlyEdges = slice(0, nEdge)
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]
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))
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
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
102 self.darkExp = self.flatExp.clone()
103 self.spareImage = self.flatExp.clone() # for testing edge bits and misc
105 self.flatExp.image.array[:] = flatData
106 self.darkExp.image.array[:] = darkData
108 self.defaultTask = cpPipe.defects.FindDefectsTask(config=self.defaultConfig)
110 self.allDefectsList = measAlg.Defects()
112 self.brightDefectsList = measAlg.Defects()
113 for d in self.brightBBoxes:
114 self.brightDefectsList.append(d)
115 self.allDefectsList.append(d)
117 self.darkDefectsList = measAlg.Defects()
118 for d in self.darkBBoxes:
119 self.darkDefectsList.append(d)
120 self.allDefectsList.append(d)
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
131 task = cpPipe.defects.FindDefectsTask(config=config)
133 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects)
135 boxesMeasured = []
136 for defect in defectsWithColumns:
137 boxesMeasured.append(defect.getBBox())
139 for boxInput in expectedDefects:
140 self.assertIn(boxInput, boxesMeasured)
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)
150 # Check that code doesn't mask more than it is supposed to.
151 for boxMeas in boxesMeasured:
152 self.assertIn(boxMeas, unionInputExpectedBoxes)
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.
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.
161 Plots can be found in DM-19903 on Jira.
162 """
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))]
168 self.check_maskBlocks(defects, expectedDefects)
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.
175 Plots can be found in DM-19903 on Jira.
176 """
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)))
182 self.check_maskBlocks(defects, expectedDefects)
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.
189 Plots can be found in DM-19903 on Jira.
190 """
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)))
196 self.check_maskBlocks(defects, expectedDefects)
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.
204 Plots can be found in DM-19903 on Jira.
205 """
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)
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.
225 Plots can be found in DM-19903 on Jira.
226 """
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)
238 self.check_maskBlocks(defects, expectedDefects)
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).
247 Plots can be found in DM-19903 on Jira.
248 """
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)
261 self.check_maskBlocks(defects, expectedDefects)
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)
271 Plots can be found in DM-19903 on Jira.
272 """
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)
282 self.check_maskBlocks(defects, expectedDefects)
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.
289 Plots can be found in DM-19903 on Jira.
290 """
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)
313 self.check_maskBlocks(defects, expectedDefects)
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.
320 Plots can be found in DM-19903 on Jira.
321 """
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)
339 self.check_maskBlocks(defects, expectedDefects)
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).
347 Plots can be found in DM-19903 on Jira.
348 """
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)
361 self.check_maskBlocks(defects, expectedDefects)
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).
369 Plots can be found in DM-19903 on Jira.
370 """
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)
383 self.check_maskBlocks(defects, expectedDefects)
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).
391 Plots can be found in DM-19903 on Jira.
392 """
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)
409 self.check_maskBlocks(defects, expectedDefects)
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).
417 Plots can be found in DM-19903 on Jira.
418 """
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)
431 self.check_maskBlocks(defects, expectedDefects)
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).
439 Plots can be found in DM-19903 on Jira.
440 """
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)
453 self.check_maskBlocks(defects, expectedDefects)
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).
461 Plots can be found in DM-19903 on Jira.
462 """
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)
487 self.check_maskBlocks(defects, expectedDefects)
489 def test_defectFindingAllSensor(self):
490 config = copy.copy(self.defaultConfig)
491 config.nPixBorderLeftRight = 0
492 config.nPixBorderUpDown = 0
494 task = cpPipe.defects.FindDefectsTask(config=config)
496 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
498 allBBoxes = self.darkBBoxes + self.brightBBoxes
500 boxesMeasured = []
501 for defect in defects:
502 boxesMeasured.append(defect.getBBox())
504 for expectedBBox in allBBoxes:
505 self.assertIn(expectedBBox, boxesMeasured)
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')
513 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges]
515 boxesMeasured = []
516 for defect in defects:
517 boxesMeasured.append(defect.getBBox())
519 for expectedBBox in shouldBeFound:
520 self.assertIn(expectedBBox, boxesMeasured)
522 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges]
523 for boxMissed in shouldBeMissed:
524 self.assertNotIn(boxMissed, boxesMeasured)
526 def test_postProcessDefectSets(self):
527 """Tests the way in which the defect sets merge.
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')
534 # defect list has length one
535 merged = self.defaultTask._postProcessDefectSets([defects], self.flatExp.getDimensions(), 'FRACTION')
536 self.assertEqual(defects, merged)
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)
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')
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)
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)
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)
567 defectList[7] = defectList[7][:-1]
568 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND')
569 self.assertNotEqual(defects, merged)
571 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'OR')
572 self.assertEqual(defects, merged)
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')
582 defectArea = 0
583 for defect in defects:
584 defectArea += defect.getBBox().getArea()
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
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)
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
607 imageSize = testImage.getBBox().getArea()
608 nGood = self.defaultTask._getNumGoodPixels(mi)
610 self.assertEqual(imageSize, nGood)
612 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
614 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6))
615 testImage.mask[noDataBox] |= NODATABIT
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'))
621 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
622 self.assertEqual(imageSize, nGood)
624 BADBIT = mi.mask.getPlaneBitMask("BAD")
625 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7))
626 testImage.mask[badBox] |= BADBIT
628 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD'))
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
635 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0)
636 self.defaultTask._setEdgeBits(mi)
638 hEdge = self.defaultConfig.nPixBorderLeftRight
639 vEdge = self.defaultConfig.nPixBorderUpDown
640 xSize, ySize = mi.getDimensions()
642 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4
644 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge)
647class TestMemory(lsst.utils.tests.MemoryTestCase):
648 pass
651def setup_module(module):
652 lsst.utils.tests.init()
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()