Coverage for tests/test_defects.py: 10%
331 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-25 11:29 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-25 11:29 -0700
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.defects.MeasureDefectsTask."""
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
36from lsst.daf.base import PropertyList
39class MeasureDefectsTaskTestCase(lsst.utils.tests.TestCase):
40 """A test case for the defect finding task."""
42 def setUp(self):
43 self.defaultConfig = cpPipe.defects.MeasureDefectsTask.ConfigClass()
45 self.flatMean = 2000
46 self.darkMean = 1
47 self.readNoiseAdu = 10
48 self.nSigmaBright = 8
49 self.nSigmaDark = 8
51 mockImageConfig = isrMock.IsrMock.ConfigClass()
53 # flatDrop is not really relevant as we replace the data
54 # but good to note it in case we change how this image is made
55 mockImageConfig.flatDrop = 0.99999
56 mockImageConfig.isTrimmed = True
58 self.flatExp = isrMock.FlatMock(config=mockImageConfig).run()
59 (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 # Set image types, the defects code will use them.
106 metaDataFlat = PropertyList()
107 metaDataFlat["IMGTYPE"] = "FLAT"
108 self.flatExp.setMetadata(metaDataFlat)
110 metaDataDark = PropertyList()
111 metaDataDark["IMGTYPE"] = "DARK"
112 self.darkExp.setMetadata(metaDataDark)
114 with self.allDefectsList.bulk_update():
115 with self.brightDefectsList.bulk_update():
116 for d in self.brightBBoxes:
117 self.brightDefectsList.append(d)
118 self.allDefectsList.append(d)
120 with self.darkDefectsList.bulk_update():
121 for d in self.darkBBoxes:
122 self.darkDefectsList.append(d)
123 self.allDefectsList.append(d)
125 def check_maskBlocks(self, inputDefects, expectedDefects):
126 """A helper function for the tests of
127 maskBlocksIfIntermitentBadPixelsInColumn.
129 """
130 config = copy.copy(self.defaultConfig)
131 config.badOnAndOffPixelColumnThreshold = 10
132 config.goodPixelColumnGapThreshold = 5
133 config.nPixBorderUpDown = 0
134 config.nPixBorderLeftRight = 0
136 task = self.defaultTask
137 task.config = config
139 defectsWithColumns, count = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects)
140 boxesMeasured = []
141 for defect in defectsWithColumns:
142 boxesMeasured.append(defect.getBBox())
144 for boxInput in expectedDefects:
145 self.assertIn(boxInput, boxesMeasured)
147 # Check that the code did not mask anything extra by
148 # looking in both the input list and "expanded-column" list.
149 unionInputExpectedBoxes = []
150 for defect in inputDefects:
151 unionInputExpectedBoxes.append(defect.getBBox())
152 for defect in expectedDefects:
153 unionInputExpectedBoxes.append(defect)
155 # Check that code doesn't mask more than it is supposed to.
156 for boxMeas in boxesMeasured:
157 self.assertIn(boxMeas, unionInputExpectedBoxes)
159 def test_maskBlocks_full_column(self):
160 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
162 Tests that a contigous bad column does not get split by the
163 code.
165 The mock flat has a size of 200X204 pixels. This column has a
166 maximum length of 50 pixels, otherwise there would be a split
167 along the mock amp boundary.
169 Plots can be found in DM-19903 on Jira.
171 """
173 defects = self.allDefectsList
174 defects.append(Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50)))
175 expectedDefects = [Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50))]
177 self.check_maskBlocks(defects, expectedDefects)
179 def test_maskBlocks_long_column(self):
180 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
182 Tests that a contigous bad column with Npix >=
183 badOnAndOffPixelColumnThreshold (10) does not get split by the
184 code.
186 Plots can be found in DM-19903 on Jira.
188 """
190 expectedDefects = [Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))]
191 defects = self.allDefectsList
192 defects.append(Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25)))
194 self.check_maskBlocks(defects, expectedDefects)
196 def test_maskBlocks_short_column(self):
197 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
199 Tests that a contigous bad column Npix <
200 badOnAndOffPixelColumnThreshold (10) does not get split by the
201 code.
203 Plots can be found in DM-19903 on Jira.
205 """
207 expectedDefects = [Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))]
208 defects = self.allDefectsList
209 defects.append(Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8)))
211 self.check_maskBlocks(defects, expectedDefects)
213 def test_maskBlocks_discontigous_to_single_block(self):
214 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
216 Npix discontiguous bad pixels in a column where Npix >=
217 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
218 goodPixelColumnGapThreshold (5). Under these conditions, the
219 whole block of bad pixels (including good gaps) should be
220 masked.
222 Plots can be found in DM-19903 on Jira.
224 """
226 expectedDefects = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 48))]
227 defects = self.allDefectsList
228 badPixels = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 2)),
229 Box2I(corner=Point2I(30, 5), dimensions=Extent2I(1, 3)),
230 Box2I(corner=Point2I(30, 11), dimensions=Extent2I(1, 5)),
231 Box2I(corner=Point2I(30, 19), dimensions=Extent2I(1, 5)),
232 Box2I(corner=Point2I(30, 27), dimensions=Extent2I(1, 4)),
233 Box2I(corner=Point2I(30, 34), dimensions=Extent2I(1, 15))]
234 with defects.bulk_update():
235 for badBox in badPixels:
236 defects.append(badBox)
238 self.check_maskBlocks(defects, expectedDefects)
240 def test_maskBlocks_discontigous_less_than_thresholds(self):
241 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
243 Npix discontiguous bad pixels in a column where Npix <
244 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
245 goodPixelColumnGapThreshold (5). Under these conditions, the
246 expected defect boxes should be the same as the input boxes.
248 Plots can be found in DM-19903 on Jira.
250 """
252 expectedDefects = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
253 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
254 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
255 defects = self.allDefectsList
256 badPixels = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
257 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
258 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
259 with defects.bulk_update():
260 for badBox in badPixels:
261 defects.append(badBox)
263 self.check_maskBlocks(defects, expectedDefects)
265 def test_maskBlocks_more_than_thresholds(self):
266 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
268 Npix discontiguous bad pixels in a column where Npix <
269 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
270 goodPixelColumnGapThreshold (5). Npix=34 (> 10) bad pixels
271 total, 1 "good" gap with 13 pixels big enough (13 >= 5 good
272 pixels, from y=6 (1+5) to y=19).
274 Plots can be found in DM-19903 on Jira.
276 """
278 expectedDefects = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 7)),
279 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 30))]
280 defects = self.allDefectsList
281 badPixels = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 2)),
282 Box2I(corner=Point2I(40, 5), dimensions=Extent2I(1, 3)),
283 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 5)),
284 Box2I(corner=Point2I(40, 27), dimensions=Extent2I(1, 4)),
285 Box2I(corner=Point2I(40, 34), dimensions=Extent2I(1, 15))]
286 with defects.bulk_update():
287 for badBox in badPixels:
288 defects.append(badBox)
290 self.check_maskBlocks(defects, expectedDefects)
292 def test_maskBlocks_not_enough_bad_pixels_in_column(self):
293 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
295 Npix discontiguous bad pixels in a column where Npix <
296 badOnAndOffPixelColumnThreshold (10) and and gaps of good
297 pixels > goodPixelColumnGapThreshold (5). Since Npix <
298 badOnAndOffPixelColumnThreshold, then it doesn't matter that
299 the number of good pixels in gap >
300 goodPixelColumnGapThreshold. 5<10 bad pixels total, 1 "good"
301 gap big enough (29>=5 good pixels, from y =12 (10+2) to y=30)
303 Plots can be found in DM-19903 on Jira.
305 """
307 expectedDefects = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
308 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
309 defects = self.allDefectsList
310 badPixels = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
311 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
312 with defects.bulk_update():
313 for badBox in badPixels:
314 defects.append(badBox)
316 self.check_maskBlocks(defects, expectedDefects)
318 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self):
319 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
321 Npix discontiguous bad pixels in a column where Npix >
322 badOnAndOffPixelColumnThreshold (10) and every other pixel is
323 bad.
325 Plots can be found in DM-19903 on Jira.
327 """
329 expectedDefects = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 31))]
330 defects = self.allDefectsList
331 badPixels = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 1)),
332 Box2I(corner=Point2I(50, 12), dimensions=Extent2I(1, 1)),
333 Box2I(corner=Point2I(50, 14), dimensions=Extent2I(1, 1)),
334 Box2I(corner=Point2I(50, 16), dimensions=Extent2I(1, 1)),
335 Box2I(corner=Point2I(50, 18), dimensions=Extent2I(1, 1)),
336 Box2I(corner=Point2I(50, 20), dimensions=Extent2I(1, 1)),
337 Box2I(corner=Point2I(50, 22), dimensions=Extent2I(1, 1)),
338 Box2I(corner=Point2I(50, 24), dimensions=Extent2I(1, 1)),
339 Box2I(corner=Point2I(50, 26), dimensions=Extent2I(1, 1)),
340 Box2I(corner=Point2I(50, 28), dimensions=Extent2I(1, 1)),
341 Box2I(corner=Point2I(50, 30), dimensions=Extent2I(1, 1)),
342 Box2I(corner=Point2I(50, 32), dimensions=Extent2I(1, 1)),
343 Box2I(corner=Point2I(50, 34), dimensions=Extent2I(1, 1)),
344 Box2I(corner=Point2I(50, 36), dimensions=Extent2I(1, 1)),
345 Box2I(corner=Point2I(50, 38), dimensions=Extent2I(1, 1)),
346 Box2I(corner=Point2I(50, 40), dimensions=Extent2I(1, 1))]
347 with defects.bulk_update():
348 for badBox in badPixels:
349 defects.append(badBox)
351 self.check_maskBlocks(defects, expectedDefects)
353 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self):
354 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
356 Npix discontiguous bad pixels in a column where Npix >
357 badOnAndOffPixelColumnThreshold (10) and every other pixel is
358 bad.
360 Plots can be found in DM-19903 on Jira.
362 """
364 expectedDefects = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
365 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
366 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
367 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
368 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
369 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
370 defects = self.allDefectsList
371 badPixels = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
372 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
373 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
374 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
375 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
376 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
377 with defects.bulk_update():
378 for badBox in badPixels:
379 defects.append(badBox)
381 self.check_maskBlocks(defects, expectedDefects)
383 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self):
384 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
386 Npix discontiguous bad pixels in column with "blobs" of "m"
387 bad pixels to one side, m > badOnAndOffPixelColumnThreshold
388 (10), number of good pixel in gaps between blobs <
389 goodPixelColumnGapThreshold (5).
391 Plots can be found in DM-19903 on Jira.
393 """
395 expectedDefects = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 29)),
396 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 12))]
397 defects = self.allDefectsList
398 badPixels = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 18)),
399 Box2I(corner=Point2I(60, 20), dimensions=Extent2I(1, 10)),
400 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 2)),
401 Box2I(corner=Point2I(61, 6), dimensions=Extent2I(2, 8))]
402 with defects.bulk_update():
403 for badBox in badPixels:
404 defects.append(badBox)
406 self.check_maskBlocks(defects, expectedDefects)
408 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self):
409 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
411 Npix discontiguous bad pixels in column with "blobs" of "m"
412 bad pixels to the other side, m >
413 badOnAndOffPixelColumnThreshold (10), number of good pixel in
414 gaps between blobs < goodPixelColumnGapThreshold (5).
416 Plots can be found in DM-19903 on Jira.
418 """
420 expectedDefects = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 29)),
421 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 12))]
422 defects = self.allDefectsList
423 badPixels = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 18)),
424 Box2I(corner=Point2I(70, 20), dimensions=Extent2I(1, 10)),
425 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 2)),
426 Box2I(corner=Point2I(68, 6), dimensions=Extent2I(2, 8))]
427 with defects.bulk_update():
428 for badBox in badPixels:
429 defects.append(badBox)
431 self.check_maskBlocks(defects, expectedDefects)
433 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self):
434 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
436 Npix discontiguous bad pixels in column with "blobs" of "m"
437 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold
438 (10), number of good pixel in gaps between blobs <
439 goodPixelColumnGapThreshold (5).
441 Plots can be found in DM-19903 on Jira.
443 """
445 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)),
446 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 12)),
447 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 12))]
448 defects = self.allDefectsList
449 badPixels = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 18)),
450 Box2I(corner=Point2I(75, 20), dimensions=Extent2I(1, 10)),
451 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 2)),
452 Box2I(corner=Point2I(73, 6), dimensions=Extent2I(2, 8)),
453 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 2)),
454 Box2I(corner=Point2I(76, 6), dimensions=Extent2I(2, 8))]
455 with defects.bulk_update():
456 for badBox in badPixels:
457 defects.append(badBox)
459 self.check_maskBlocks(defects, expectedDefects)
461 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self):
462 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
464 Npix discontiguous bad pixels in column with "blobs" of "m"
465 bad pixels to one side, m > badOnAndOffPixelColumnThreshold
466 (10), number of good pixel in gaps between blobs >
467 goodPixelColumnGapThreshold (5).
469 Plots can be found in DM-19903 on Jira.
471 """
473 expectedDefects = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 29)),
474 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
475 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
476 defects = self.allDefectsList
477 badPixels = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 18)),
478 Box2I(corner=Point2I(80, 20), dimensions=Extent2I(1, 10)),
479 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
480 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
481 with defects.bulk_update():
482 for badBox in badPixels:
483 defects.append(badBox)
485 self.check_maskBlocks(defects, expectedDefects)
487 def test_maskBlocks_other_side_good_greater_than_threshold(self):
488 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
490 Npix discontiguous bad pixels in column with "blobs" of "m"
491 bad pixels to the other side, m >
492 badOnAndOffPixelColumnThreshold (10), number of good pixel in
493 gaps between blobs > goodPixelColumnGapThreshold (5).
495 Plots can be found in DM-19903 on Jira.
497 """
499 expectedDefects = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 29)),
500 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
501 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
502 defects = self.allDefectsList
503 badPixels = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 18)),
504 Box2I(corner=Point2I(87, 20), dimensions=Extent2I(1, 10)),
505 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
506 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
507 with defects.bulk_update():
508 for badBox in badPixels:
509 defects.append(badBox)
511 self.check_maskBlocks(defects, expectedDefects)
513 def test_maskBlocks_both_sides_good_greater_than_threshold(self):
514 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
516 Npix discontiguous bad pixels in column with "blobs" of "m"
517 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold
518 (10), number of good pixel in gaps between blobs >
519 goodPixelColumnGapThreshold (5).
521 Plots can be found in DM-19903 on Jira.
523 """
525 expectedDefects = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 34)),
526 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 7)),
527 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 9)),
528 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 7)),
529 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 9))]
530 defects = self.allDefectsList
531 badPixels = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 12)),
532 Box2I(corner=Point2I(93, 15), dimensions=Extent2I(1, 20)),
533 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 2)),
534 Box2I(corner=Point2I(91, 7), dimensions=Extent2I(2, 2)),
535 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 2)),
536 Box2I(corner=Point2I(94, 7), dimensions=Extent2I(2, 2)),
537 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 3)),
538 Box2I(corner=Point2I(91, 24), dimensions=Extent2I(2, 3)),
539 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 3)),
540 Box2I(corner=Point2I(94, 24), dimensions=Extent2I(2, 3))]
541 with defects.bulk_update():
542 for badBox in badPixels:
543 defects.append(badBox)
545 self.check_maskBlocks(defects, expectedDefects)
547 def test_maskBlocks_y_out_of_order_dm38103(self):
548 """A test for maskBlocksIfIntermitentBadPixelsInColumn, y out of order.
550 This test is a variant of
551 notest_maskBlocks_every_other_pixel_bad_greater_than_threshold with
552 an extra out-of-y-order bad pixel to trigger DM-38103.
553 """
554 expectedDefects = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 31))]
555 defects = self.allDefectsList
556 badPixels = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 1)),
557 Box2I(corner=Point2I(50, 112), dimensions=Extent2I(1, 1)),
558 Box2I(corner=Point2I(50, 114), dimensions=Extent2I(1, 1)),
559 Box2I(corner=Point2I(50, 116), dimensions=Extent2I(1, 1)),
560 Box2I(corner=Point2I(50, 118), dimensions=Extent2I(1, 1)),
561 Box2I(corner=Point2I(50, 120), dimensions=Extent2I(1, 1)),
562 Box2I(corner=Point2I(50, 122), dimensions=Extent2I(1, 1)),
563 Box2I(corner=Point2I(50, 124), dimensions=Extent2I(1, 1)),
564 Box2I(corner=Point2I(50, 126), dimensions=Extent2I(1, 1)),
565 Box2I(corner=Point2I(50, 128), dimensions=Extent2I(1, 1)),
566 Box2I(corner=Point2I(50, 130), dimensions=Extent2I(1, 1)),
567 Box2I(corner=Point2I(50, 132), dimensions=Extent2I(1, 1)),
568 Box2I(corner=Point2I(50, 134), dimensions=Extent2I(1, 1)),
569 Box2I(corner=Point2I(50, 136), dimensions=Extent2I(1, 1)),
570 Box2I(corner=Point2I(50, 138), dimensions=Extent2I(1, 1)),
571 Box2I(corner=Point2I(50, 140), dimensions=Extent2I(1, 1)),
572 # This last point is out of order in y.
573 Box2I(corner=Point2I(50, 100), dimensions=Extent2I(1, 1))]
575 # Force defect normalization off in order to trigger DM-38301, because
576 # defects.fromFootprintList() which is called by _findHotAndColdPixels
577 # does not do normalization.
578 defects._bulk_update = True
579 for badBox in badPixels:
580 defects.append(badBox)
581 defects._bulk_update = False
583 self.check_maskBlocks(defects, expectedDefects)
585 def test_defectFindingAllSensor(self):
586 config = copy.copy(self.defaultConfig)
587 config.nPixBorderLeftRight = 0
588 config.nPixBorderUpDown = 0
590 task = self.defaultTask
591 task.config = config
593 defects = task._findHotAndColdPixels(self.flatExp)
595 allBBoxes = self.darkBBoxes + self.brightBBoxes
597 boxesMeasured = []
598 for defect in defects:
599 boxesMeasured.append(defect.getBBox())
601 for expectedBBox in allBBoxes:
602 self.assertIn(expectedBBox, boxesMeasured)
604 def test_defectFindingEdgeIgnore(self):
605 config = copy.copy(self.defaultConfig)
606 config.nPixBorderUpDown = 0
607 config.nPixBorderLeftRight = 7
608 task = self.defaultTask
609 task.config = config
610 defects = task._findHotAndColdPixels(self.flatExp)
612 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges]
614 boxesMeasured = []
615 for defect in defects:
616 boxesMeasured.append(defect.getBBox())
618 for expectedBBox in shouldBeFound:
619 self.assertIn(expectedBBox, boxesMeasured)
621 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges]
622 for boxMissed in shouldBeMissed:
623 self.assertNotIn(boxMissed, boxesMeasured)
625 def valueThreshold(self, fileType, saturateAmpInFlat=False):
626 """Helper function to loop over flats and darks
627 to test thresholdType = 'VALUE'."""
628 config = copy.copy(self.defaultConfig)
629 config.thresholdType = 'VALUE'
630 task = self.defaultTask
631 task.config = config
633 for amp in self.flatExp.getDetector():
634 if amp.getName() == 'C:0,0':
635 regionC00 = amp.getBBox()
637 if fileType == 'dark':
638 exp = self.darkExp
639 shouldBeFound = self.brightBBoxes[self.noEdges]
640 else:
641 exp = self.flatExp
642 if saturateAmpInFlat:
643 exp.maskedImage[regionC00].image.array[:] = 0.0
644 # Amp C:0,0: minimum=(0, 0), maximum=(99, 50)
645 x = self.defaultConfig.nPixBorderUpDown
646 y = self.defaultConfig.nPixBorderLeftRight
647 width, height = regionC00.getEndX() - x, regionC00.getEndY() - y
648 # Defects code will mark whole saturated amp as defect box.
649 shouldBeFound = [Box2I(corner=Point2I(x, y), dimensions=Extent2I(width, height))]
650 else:
651 shouldBeFound = self.darkBBoxes[self.noEdges]
652 # Change the default a bit so it works for the
653 # existing simulated defects.
654 task.config.fracThresholdFlat = 0.9
656 defects = task._findHotAndColdPixels(exp)
658 boxesMeasured = []
659 for defect in defects:
660 boxesMeasured.append(defect.getBBox())
662 for expectedBBox in shouldBeFound:
663 self.assertIn(expectedBBox, boxesMeasured)
665 def test_valueThreshold(self):
666 for fileType in ['dark', 'flat']:
667 self.valueThreshold(fileType)
668 # stdDev = 0.0
669 self.valueThreshold('flat', saturateAmpInFlat=True)
671 def test_pixelCounting(self):
672 """Test that the number of defective pixels identified is as expected.
673 """
674 config = copy.copy(self.defaultConfig)
675 config.nPixBorderUpDown = 0
676 config.nPixBorderLeftRight = 0
677 task = self.defaultTask
678 task.config = config
679 defects = task._findHotAndColdPixels(self.flatExp)
681 defectArea = 0
682 for defect in defects:
683 defectArea += defect.getBBox().getArea()
685 # The columnar code will cover blocks of a column with
686 # on-and-off pixels, thus creating more bad pixels that what
687 # initially placed in self.brightDefects and self.darkDefects.
688 # Thus, defectArea should be >= crossCheck.
689 crossCheck = 0
690 for x, y, sx, sy in self.brightDefects:
691 crossCheck += sx*sy
692 for x, y, sx, sy in self.darkDefects:
693 crossCheck += sx*sy
695 # Test the result of _nPixFromDefects()
696 # via two different ways of calculating area.
697 self.assertEqual(defectArea, task._nPixFromDefects(defects))
698 # defectArea should be >= crossCheck
699 self.assertGreaterEqual(defectArea, crossCheck)
701 def test_getNumGoodPixels(self):
702 """Test the the number of pixels in the image not masked is as
703 expected.
704 """
705 testImage = self.flatExp.clone()
706 mi = testImage.maskedImage
708 imageSize = testImage.getBBox().getArea()
709 nGood = self.defaultTask._getNumGoodPixels(mi)
711 self.assertEqual(imageSize, nGood)
713 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
715 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6))
716 testImage.mask[noDataBox] |= NODATABIT
718 self.assertEqual(imageSize - noDataBox.getArea(), self.defaultTask._getNumGoodPixels(mi))
719 # check for misfire; we're setting NO_DATA here, not BAD
720 self.assertEqual(imageSize, self.defaultTask._getNumGoodPixels(mi, 'BAD'))
722 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
723 self.assertEqual(imageSize, nGood)
725 BADBIT = mi.mask.getPlaneBitMask("BAD")
726 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7))
727 testImage.mask[badBox] |= BADBIT
729 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD'))
731 def test_edgeMasking(self):
732 """Check that the right number of edge pixels are masked by
733 _setEdgeBits().
734 """
735 testImage = self.flatExp.clone()
736 mi = testImage.maskedImage
738 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0)
739 self.defaultTask._setEdgeBits(mi)
741 hEdge = self.defaultConfig.nPixBorderLeftRight
742 vEdge = self.defaultConfig.nPixBorderUpDown
743 xSize, ySize = mi.getDimensions()
745 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4
747 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge)
749 def test_badImage(self):
750 """Check that fully-bad images do not fail.
751 """
752 testImage = self.flatExp.clone()
753 testImage.image.array[:, :] = 125000
755 config = copy.copy(self.defaultConfig)
756 # Do not exclude any pixels, so the areas match.
757 config.nPixBorderUpDown = 0
758 config.nPixBorderLeftRight = 0
760 task = self.defaultTask
761 task.config = config
762 defects = task._findHotAndColdPixels(testImage)
764 defectArea = 0
765 for defect in defects:
766 defectArea += defect.getBBox().getArea()
767 self.assertEqual(defectArea, testImage.getBBox().getArea())
770class TestMemory(lsst.utils.tests.MemoryTestCase):
771 pass
774def setup_module(module):
775 lsst.utils.tests.init()
778if __name__ == "__main__": 778 ↛ 779line 778 didn't jump to line 779, because the condition on line 778 was never true
779 lsst.utils.tests.init()
780 unittest.main()