Coverage for tests/test_defects.py: 10%
288 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-11 11:19 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-11 11:19 +0000
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.ip.isr as ipIsr
33import lsst.cp.pipe as cpPipe
34from lsst.ip.isr import isrMock, countMaskedPixels
35from lsst.geom import Box2I, Point2I, Extent2I
38class MeasureDefectsTaskTestCase(lsst.utils.tests.TestCase):
39 """A test case for the defect finding task."""
41 def setUp(self):
42 self.defaultConfig = cpPipe.defects.MeasureDefectsTask.ConfigClass()
44 self.flatMean = 2000
45 self.darkMean = 1
46 self.readNoiseAdu = 10
47 self.nSigmaBright = 8
48 self.nSigmaDark = 8
50 mockImageConfig = isrMock.IsrMock.ConfigClass()
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
57 self.flatExp = isrMock.FlatMock(config=mockImageConfig).run()
58 (shapeY, shapeX) = self.flatExp.getDimensions()
60 # x, y, size tuples
61 # always put edge defects at the start and change the value of nEdge
63 self.brightDefects = [(0, 15, 3, 3), (100, 123, 1, 1)]
65 self.darkDefects = [(5, 0, 1, 1), (7, 62, 2, 2)]
67 nEdge = 1 # NOTE: update if more edge defects are included
68 self.noEdges = slice(nEdge, None)
69 self.onlyEdges = slice(0, nEdge)
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]
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))
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
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
93 self.darkExp = self.flatExp.clone()
94 self.spareImage = self.flatExp.clone() # for testing edge bits and misc
96 self.flatExp.image.array[:] = flatData
97 self.darkExp.image.array[:] = darkData
99 self.defaultTask = cpPipe.defects.MeasureDefectsTask()
101 self.allDefectsList = ipIsr.Defects()
102 self.brightDefectsList = ipIsr.Defects()
103 self.darkDefectsList = ipIsr.Defects()
105 with self.allDefectsList.bulk_update():
106 with self.brightDefectsList.bulk_update():
107 for d in self.brightBBoxes:
108 self.brightDefectsList.append(d)
109 self.allDefectsList.append(d)
111 with self.darkDefectsList.bulk_update():
112 for d in self.darkBBoxes:
113 self.darkDefectsList.append(d)
114 self.allDefectsList.append(d)
116 def check_maskBlocks(self, inputDefects, expectedDefects):
117 """A helper function for the tests of
118 maskBlocksIfIntermitentBadPixelsInColumn.
120 """
121 config = copy.copy(self.defaultConfig)
122 config.badOnAndOffPixelColumnThreshold = 10
123 config.goodPixelColumnGapThreshold = 5
124 config.nPixBorderUpDown = 0
125 config.nPixBorderLeftRight = 0
127 task = cpPipe.defects.MeasureDefectsTask(config=config)
129 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects)
130 boxesMeasured = []
131 for defect in defectsWithColumns:
132 boxesMeasured.append(defect.getBBox())
134 for boxInput in expectedDefects:
135 self.assertIn(boxInput, boxesMeasured)
137 # Check that the code did not mask anything extra by
138 # looking in both the input list and "expanded-column" list.
139 unionInputExpectedBoxes = []
140 for defect in inputDefects:
141 unionInputExpectedBoxes.append(defect.getBBox())
142 for defect in expectedDefects:
143 unionInputExpectedBoxes.append(defect)
145 # Check that code doesn't mask more than it is supposed to.
146 for boxMeas in boxesMeasured:
147 self.assertIn(boxMeas, unionInputExpectedBoxes)
149 def test_maskBlocks_full_column(self):
150 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
152 Tests that a contigous bad column does not get split by the
153 code.
155 The mock flat has a size of 200X204 pixels. This column has a
156 maximum length of 50 pixels, otherwise there would be a split
157 along the mock amp boundary.
159 Plots can be found in DM-19903 on Jira.
161 """
163 defects = self.allDefectsList
164 defects.append(Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50)))
165 expectedDefects = [Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50))]
167 self.check_maskBlocks(defects, expectedDefects)
169 def test_maskBlocks_long_column(self):
170 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
172 Tests that a contigous bad column with Npix >=
173 badOnAndOffPixelColumnThreshold (10) does not get split by the
174 code.
176 Plots can be found in DM-19903 on Jira.
178 """
180 expectedDefects = [Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))]
181 defects = self.allDefectsList
182 defects.append(Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25)))
184 self.check_maskBlocks(defects, expectedDefects)
186 def test_maskBlocks_short_column(self):
187 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
189 Tests that a contigous bad column Npix <
190 badOnAndOffPixelColumnThreshold (10) does not get split by the
191 code.
193 Plots can be found in DM-19903 on Jira.
195 """
197 expectedDefects = [Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))]
198 defects = self.allDefectsList
199 defects.append(Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8)))
201 self.check_maskBlocks(defects, expectedDefects)
203 def test_maskBlocks_discontigous_to_single_block(self):
204 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
206 Npix discontiguous bad pixels in a column where Npix >=
207 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
208 goodPixelColumnGapThreshold (5). Under these conditions, the
209 whole block of bad pixels (including good gaps) should be
210 masked.
212 Plots can be found in DM-19903 on Jira.
214 """
216 expectedDefects = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 48))]
217 defects = self.allDefectsList
218 badPixels = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 2)),
219 Box2I(corner=Point2I(30, 5), dimensions=Extent2I(1, 3)),
220 Box2I(corner=Point2I(30, 11), dimensions=Extent2I(1, 5)),
221 Box2I(corner=Point2I(30, 19), dimensions=Extent2I(1, 5)),
222 Box2I(corner=Point2I(30, 27), dimensions=Extent2I(1, 4)),
223 Box2I(corner=Point2I(30, 34), dimensions=Extent2I(1, 15))]
224 with defects.bulk_update():
225 for badBox in badPixels:
226 defects.append(badBox)
228 self.check_maskBlocks(defects, expectedDefects)
230 def test_maskBlocks_discontigous_less_than_thresholds(self):
231 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
233 Npix discontiguous bad pixels in a column where Npix <
234 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
235 goodPixelColumnGapThreshold (5). Under these conditions, the
236 expected defect boxes should be the same as the input boxes.
238 Plots can be found in DM-19903 on Jira.
240 """
242 expectedDefects = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
243 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
244 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
245 defects = self.allDefectsList
246 badPixels = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
247 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
248 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
249 with defects.bulk_update():
250 for badBox in badPixels:
251 defects.append(badBox)
253 self.check_maskBlocks(defects, expectedDefects)
255 def test_maskBlocks_more_than_thresholds(self):
256 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
258 Npix discontiguous bad pixels in a column where Npix <
259 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
260 goodPixelColumnGapThreshold (5). Npix=34 (> 10) bad pixels
261 total, 1 "good" gap with 13 pixels big enough (13 >= 5 good
262 pixels, from y=6 (1+5) to y=19).
264 Plots can be found in DM-19903 on Jira.
266 """
268 expectedDefects = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 7)),
269 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 30))]
270 defects = self.allDefectsList
271 badPixels = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 2)),
272 Box2I(corner=Point2I(40, 5), dimensions=Extent2I(1, 3)),
273 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 5)),
274 Box2I(corner=Point2I(40, 27), dimensions=Extent2I(1, 4)),
275 Box2I(corner=Point2I(40, 34), dimensions=Extent2I(1, 15))]
276 with defects.bulk_update():
277 for badBox in badPixels:
278 defects.append(badBox)
280 self.check_maskBlocks(defects, expectedDefects)
282 def test_maskBlocks_not_enough_bad_pixels_in_column(self):
283 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
285 Npix discontiguous bad pixels in a column where Npix <
286 badOnAndOffPixelColumnThreshold (10) and and gaps of good
287 pixels > goodPixelColumnGapThreshold (5). Since Npix <
288 badOnAndOffPixelColumnThreshold, then it doesn't matter that
289 the number of good pixels in gap >
290 goodPixelColumnGapThreshold. 5<10 bad pixels total, 1 "good"
291 gap big enough (29>=5 good pixels, from y =12 (10+2) to y=30)
293 Plots can be found in DM-19903 on Jira.
295 """
297 expectedDefects = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
298 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
299 defects = self.allDefectsList
300 badPixels = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
301 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
302 with defects.bulk_update():
303 for badBox in badPixels:
304 defects.append(badBox)
306 self.check_maskBlocks(defects, expectedDefects)
308 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self):
309 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
311 Npix discontiguous bad pixels in a column where Npix >
312 badOnAndOffPixelColumnThreshold (10) and every other pixel is
313 bad.
315 Plots can be found in DM-19903 on Jira.
317 """
319 expectedDefects = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 31))]
320 defects = self.allDefectsList
321 badPixels = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 1)),
322 Box2I(corner=Point2I(50, 12), dimensions=Extent2I(1, 1)),
323 Box2I(corner=Point2I(50, 14), dimensions=Extent2I(1, 1)),
324 Box2I(corner=Point2I(50, 16), dimensions=Extent2I(1, 1)),
325 Box2I(corner=Point2I(50, 18), dimensions=Extent2I(1, 1)),
326 Box2I(corner=Point2I(50, 20), dimensions=Extent2I(1, 1)),
327 Box2I(corner=Point2I(50, 22), dimensions=Extent2I(1, 1)),
328 Box2I(corner=Point2I(50, 24), dimensions=Extent2I(1, 1)),
329 Box2I(corner=Point2I(50, 26), dimensions=Extent2I(1, 1)),
330 Box2I(corner=Point2I(50, 28), dimensions=Extent2I(1, 1)),
331 Box2I(corner=Point2I(50, 30), dimensions=Extent2I(1, 1)),
332 Box2I(corner=Point2I(50, 32), dimensions=Extent2I(1, 1)),
333 Box2I(corner=Point2I(50, 34), dimensions=Extent2I(1, 1)),
334 Box2I(corner=Point2I(50, 36), dimensions=Extent2I(1, 1)),
335 Box2I(corner=Point2I(50, 38), dimensions=Extent2I(1, 1)),
336 Box2I(corner=Point2I(50, 40), dimensions=Extent2I(1, 1))]
337 with defects.bulk_update():
338 for badBox in badPixels:
339 defects.append(badBox)
341 self.check_maskBlocks(defects, expectedDefects)
343 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self):
344 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
346 Npix discontiguous bad pixels in a column where Npix >
347 badOnAndOffPixelColumnThreshold (10) and every other pixel is
348 bad.
350 Plots can be found in DM-19903 on Jira.
352 """
354 expectedDefects = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
355 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
356 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
357 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
358 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
359 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
360 defects = self.allDefectsList
361 badPixels = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
362 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
363 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
364 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
365 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
366 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
367 with defects.bulk_update():
368 for badBox in badPixels:
369 defects.append(badBox)
371 self.check_maskBlocks(defects, expectedDefects)
373 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self):
374 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
376 Npix discontiguous bad pixels in column with "blobs" of "m"
377 bad pixels to one side, m > badOnAndOffPixelColumnThreshold
378 (10), number of good pixel in gaps between blobs <
379 goodPixelColumnGapThreshold (5).
381 Plots can be found in DM-19903 on Jira.
383 """
385 expectedDefects = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 29)),
386 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 12))]
387 defects = self.allDefectsList
388 badPixels = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 18)),
389 Box2I(corner=Point2I(60, 20), dimensions=Extent2I(1, 10)),
390 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 2)),
391 Box2I(corner=Point2I(61, 6), dimensions=Extent2I(2, 8))]
392 with defects.bulk_update():
393 for badBox in badPixels:
394 defects.append(badBox)
396 self.check_maskBlocks(defects, expectedDefects)
398 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self):
399 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
401 Npix discontiguous bad pixels in column with "blobs" of "m"
402 bad pixels to the other side, m >
403 badOnAndOffPixelColumnThreshold (10), number of good pixel in
404 gaps between blobs < goodPixelColumnGapThreshold (5).
406 Plots can be found in DM-19903 on Jira.
408 """
410 expectedDefects = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 29)),
411 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 12))]
412 defects = self.allDefectsList
413 badPixels = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 18)),
414 Box2I(corner=Point2I(70, 20), dimensions=Extent2I(1, 10)),
415 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 2)),
416 Box2I(corner=Point2I(68, 6), dimensions=Extent2I(2, 8))]
417 with defects.bulk_update():
418 for badBox in badPixels:
419 defects.append(badBox)
421 self.check_maskBlocks(defects, expectedDefects)
423 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self):
424 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
426 Npix discontiguous bad pixels in column with "blobs" of "m"
427 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold
428 (10), number of good pixel in gaps between blobs <
429 goodPixelColumnGapThreshold (5).
431 Plots can be found in DM-19903 on Jira.
433 """
435 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)),
436 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 12)),
437 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 12))]
438 defects = self.allDefectsList
439 badPixels = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 18)),
440 Box2I(corner=Point2I(75, 20), dimensions=Extent2I(1, 10)),
441 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 2)),
442 Box2I(corner=Point2I(73, 6), dimensions=Extent2I(2, 8)),
443 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 2)),
444 Box2I(corner=Point2I(76, 6), dimensions=Extent2I(2, 8))]
445 with defects.bulk_update():
446 for badBox in badPixels:
447 defects.append(badBox)
449 self.check_maskBlocks(defects, expectedDefects)
451 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self):
452 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
454 Npix discontiguous bad pixels in column with "blobs" of "m"
455 bad pixels to one side, m > badOnAndOffPixelColumnThreshold
456 (10), number of good pixel in gaps between blobs >
457 goodPixelColumnGapThreshold (5).
459 Plots can be found in DM-19903 on Jira.
461 """
463 expectedDefects = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 29)),
464 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
465 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
466 defects = self.allDefectsList
467 badPixels = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 18)),
468 Box2I(corner=Point2I(80, 20), dimensions=Extent2I(1, 10)),
469 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
470 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
471 with defects.bulk_update():
472 for badBox in badPixels:
473 defects.append(badBox)
475 self.check_maskBlocks(defects, expectedDefects)
477 def test_maskBlocks_other_side_good_greater_than_threshold(self):
478 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
480 Npix discontiguous bad pixels in column with "blobs" of "m"
481 bad pixels to the other side, m >
482 badOnAndOffPixelColumnThreshold (10), number of good pixel in
483 gaps between blobs > goodPixelColumnGapThreshold (5).
485 Plots can be found in DM-19903 on Jira.
487 """
489 expectedDefects = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 29)),
490 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
491 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
492 defects = self.allDefectsList
493 badPixels = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 18)),
494 Box2I(corner=Point2I(87, 20), dimensions=Extent2I(1, 10)),
495 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
496 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
497 with defects.bulk_update():
498 for badBox in badPixels:
499 defects.append(badBox)
501 self.check_maskBlocks(defects, expectedDefects)
503 def test_maskBlocks_both_sides_good_greater_than_threshold(self):
504 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
506 Npix discontiguous bad pixels in column with "blobs" of "m"
507 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold
508 (10), number of good pixel in gaps between blobs >
509 goodPixelColumnGapThreshold (5).
511 Plots can be found in DM-19903 on Jira.
513 """
515 expectedDefects = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 34)),
516 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 7)),
517 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 9)),
518 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 7)),
519 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 9))]
520 defects = self.allDefectsList
521 badPixels = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 12)),
522 Box2I(corner=Point2I(93, 15), dimensions=Extent2I(1, 20)),
523 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 2)),
524 Box2I(corner=Point2I(91, 7), dimensions=Extent2I(2, 2)),
525 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 2)),
526 Box2I(corner=Point2I(94, 7), dimensions=Extent2I(2, 2)),
527 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 3)),
528 Box2I(corner=Point2I(91, 24), dimensions=Extent2I(2, 3)),
529 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 3)),
530 Box2I(corner=Point2I(94, 24), dimensions=Extent2I(2, 3))]
531 with defects.bulk_update():
532 for badBox in badPixels:
533 defects.append(badBox)
535 self.check_maskBlocks(defects, expectedDefects)
537 def test_maskBlocks_y_out_of_order_dm38103(self):
538 """A test for maskBlocksIfIntermitentBadPixelsInColumn, y out of order.
540 This test is a variant of
541 notest_maskBlocks_every_other_pixel_bad_greater_than_threshold with
542 an extra out-of-y-order bad pixel to trigger DM-38103.
543 """
544 expectedDefects = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 31))]
545 defects = self.allDefectsList
546 badPixels = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 1)),
547 Box2I(corner=Point2I(50, 112), dimensions=Extent2I(1, 1)),
548 Box2I(corner=Point2I(50, 114), dimensions=Extent2I(1, 1)),
549 Box2I(corner=Point2I(50, 116), dimensions=Extent2I(1, 1)),
550 Box2I(corner=Point2I(50, 118), dimensions=Extent2I(1, 1)),
551 Box2I(corner=Point2I(50, 120), dimensions=Extent2I(1, 1)),
552 Box2I(corner=Point2I(50, 122), dimensions=Extent2I(1, 1)),
553 Box2I(corner=Point2I(50, 124), dimensions=Extent2I(1, 1)),
554 Box2I(corner=Point2I(50, 126), dimensions=Extent2I(1, 1)),
555 Box2I(corner=Point2I(50, 128), dimensions=Extent2I(1, 1)),
556 Box2I(corner=Point2I(50, 130), dimensions=Extent2I(1, 1)),
557 Box2I(corner=Point2I(50, 132), dimensions=Extent2I(1, 1)),
558 Box2I(corner=Point2I(50, 134), dimensions=Extent2I(1, 1)),
559 Box2I(corner=Point2I(50, 136), dimensions=Extent2I(1, 1)),
560 Box2I(corner=Point2I(50, 138), dimensions=Extent2I(1, 1)),
561 Box2I(corner=Point2I(50, 140), dimensions=Extent2I(1, 1)),
562 # This last point is out of order in y.
563 Box2I(corner=Point2I(50, 100), dimensions=Extent2I(1, 1))]
565 # Force defect normalization off in order to trigger DM-38301, because
566 # defects.fromFootprintList() which is called by findHotAndColdPixels
567 # does not do normalization.
568 defects._bulk_update = True
569 for badBox in badPixels:
570 defects.append(badBox)
571 defects._bulk_update = False
573 self.check_maskBlocks(defects, expectedDefects)
575 def test_defectFindingAllSensor(self):
576 config = copy.copy(self.defaultConfig)
577 config.nPixBorderLeftRight = 0
578 config.nPixBorderUpDown = 0
580 task = cpPipe.defects.MeasureDefectsTask(config=config)
582 defects = task.findHotAndColdPixels(self.flatExp, [config.nSigmaBright,
583 config.nSigmaDark])
585 allBBoxes = self.darkBBoxes + self.brightBBoxes
587 boxesMeasured = []
588 for defect in defects:
589 boxesMeasured.append(defect.getBBox())
591 for expectedBBox in allBBoxes:
592 self.assertIn(expectedBBox, boxesMeasured)
594 def test_defectFindingEdgeIgnore(self):
595 config = copy.copy(self.defaultConfig)
596 config.nPixBorderUpDown = 0
597 task = cpPipe.defects.MeasureDefectsTask(config=config)
598 defects = task.findHotAndColdPixels(self.flatExp, [config.nSigmaBright,
599 config.nSigmaDark])
601 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges]
603 boxesMeasured = []
604 for defect in defects:
605 boxesMeasured.append(defect.getBBox())
607 for expectedBBox in shouldBeFound:
608 self.assertIn(expectedBBox, boxesMeasured)
610 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges]
611 for boxMissed in shouldBeMissed:
612 self.assertNotIn(boxMissed, boxesMeasured)
614 def test_pixelCounting(self):
615 """Test that the number of defective pixels identified is as expected.
616 """
617 config = copy.copy(self.defaultConfig)
618 config.nPixBorderUpDown = 0
619 config.nPixBorderLeftRight = 0
620 task = cpPipe.defects.MeasureDefectsTask(config=config)
621 defects = task.findHotAndColdPixels(self.flatExp, [config.nSigmaBright,
622 config.nSigmaDark])
624 defectArea = 0
625 for defect in defects:
626 defectArea += defect.getBBox().getArea()
628 # The columnar code will cover blocks of a column with
629 # on-and-off pixels, thus creating more bad pixels that what
630 # initially placed in self.brightDefects and self.darkDefects.
631 # Thus, defectArea should be >= crossCheck.
632 crossCheck = 0
633 for x, y, sx, sy in self.brightDefects:
634 crossCheck += sx*sy
635 for x, y, sx, sy in self.darkDefects:
636 crossCheck += sx*sy
638 # Test the result of _nPixFromDefects()
639 # via two different ways of calculating area.
640 self.assertEqual(defectArea, task._nPixFromDefects(defects))
641 # defectArea should be >= crossCheck
642 self.assertGreaterEqual(defectArea, crossCheck)
644 def test_getNumGoodPixels(self):
645 """Test the the number of pixels in the image not masked is as
646 expected.
647 """
648 testImage = self.flatExp.clone()
649 mi = testImage.maskedImage
651 imageSize = testImage.getBBox().getArea()
652 nGood = self.defaultTask._getNumGoodPixels(mi)
654 self.assertEqual(imageSize, nGood)
656 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
658 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6))
659 testImage.mask[noDataBox] |= NODATABIT
661 self.assertEqual(imageSize - noDataBox.getArea(), self.defaultTask._getNumGoodPixels(mi))
662 # check for misfire; we're setting NO_DATA here, not BAD
663 self.assertEqual(imageSize, self.defaultTask._getNumGoodPixels(mi, 'BAD'))
665 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
666 self.assertEqual(imageSize, nGood)
668 BADBIT = mi.mask.getPlaneBitMask("BAD")
669 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7))
670 testImage.mask[badBox] |= BADBIT
672 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD'))
674 def test_edgeMasking(self):
675 """Check that the right number of edge pixels are masked by
676 _setEdgeBits().
677 """
678 testImage = self.flatExp.clone()
679 mi = testImage.maskedImage
681 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0)
682 self.defaultTask._setEdgeBits(mi)
684 hEdge = self.defaultConfig.nPixBorderLeftRight
685 vEdge = self.defaultConfig.nPixBorderUpDown
686 xSize, ySize = mi.getDimensions()
688 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4
690 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge)
692 def test_badImage(self):
693 """Check that fully-bad images do not fail.
694 """
695 testImage = self.flatExp.clone()
696 testImage.image.array[:, :] = 125000
698 config = copy.copy(self.defaultConfig)
699 # Do not exclude any pixels, so the areas match.
700 config.nPixBorderUpDown = 0
701 config.nPixBorderLeftRight = 0
703 task = cpPipe.defects.MeasureDefectsTask(config=config)
704 defects = task.findHotAndColdPixels(testImage, [config.nSigmaBright,
705 config.nSigmaDark])
707 defectArea = 0
708 for defect in defects:
709 defectArea += defect.getBBox().getArea()
710 self.assertEqual(defectArea, testImage.getBBox().getArea())
713class TestMemory(lsst.utils.tests.MemoryTestCase):
714 pass
717def setup_module(module):
718 lsst.utils.tests.init()
721if __name__ == "__main__": 721 ↛ 722line 721 didn't jump to line 722, because the condition on line 721 was never true
722 lsst.utils.tests.init()
723 unittest.main()