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)
134 boxesMeasured = []
135 for defect in defectsWithColumns:
136 boxesMeasured.append(defect.getBBox())
138 for boxInput in expectedDefects:
139 self.assertIn(boxInput, boxesMeasured)
141 # Check that the code did not mask anything extra by
142 # looking in both the input list and "expanded-column" list.
143 unionInputExpectedBoxes = []
144 for defect in inputDefects:
145 unionInputExpectedBoxes.append(defect.getBBox())
146 for defect in expectedDefects:
147 unionInputExpectedBoxes.append(defect)
149 # Check that code doesn't mask more than it is supposed to.
150 for boxMeas in boxesMeasured:
151 self.assertIn(boxMeas, unionInputExpectedBoxes)
153 def test_maskBlocks_full_column(self):
154 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
155 Tests that a contigous bad column does not get split by the code.
157 The mock flat has a size of 200X204 pixels. This column has a maximum length of 50
158 pixels, otherwise there would be a split along the mock amp boundary.
160 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.
171 Tests that a contigous bad column with Npix >= badOnAndOffPixelColumnThreshold (10)
172 does not get split by the code.
174 Plots can be found in DM-19903 on Jira.
175 """
177 expectedDefects = [Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))]
178 defects = self.allDefectsList
179 defects.append(Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25)))
181 self.check_maskBlocks(defects, expectedDefects)
183 def test_maskBlocks_short_column(self):
184 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
185 Tests that a contigous bad column Npix < badOnAndOffPixelColumnThreshold (10)
186 does not get split by the code.
188 Plots can be found in DM-19903 on Jira.
189 """
191 expectedDefects = [Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))]
192 defects = self.allDefectsList
193 defects.append(Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8)))
195 self.check_maskBlocks(defects, expectedDefects)
197 def test_maskBlocks_discontigous_to_single_block(self):
198 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
199 Npix discontiguous bad pixels in a column where Npix >= badOnAndOffPixelColumnThreshold (10)
200 and gaps of good pixels < goodPixelColumnGapThreshold (5). Under these conditions, the whole
201 block of bad pixels (including good gaps) should be masked.
203 Plots can be found in DM-19903 on Jira.
204 """
206 expectedDefects = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 48))]
207 defects = self.allDefectsList
208 badPixels = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 2)),
209 Box2I(corner=Point2I(30, 5), dimensions=Extent2I(1, 3)),
210 Box2I(corner=Point2I(30, 11), dimensions=Extent2I(1, 5)),
211 Box2I(corner=Point2I(30, 19), dimensions=Extent2I(1, 5)),
212 Box2I(corner=Point2I(30, 27), dimensions=Extent2I(1, 4)),
213 Box2I(corner=Point2I(30, 34), dimensions=Extent2I(1, 15))]
214 for badBox in badPixels:
215 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(2, 12))]
352 defects = self.allDefectsList
353 badPixels = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 18)),
354 Box2I(corner=Point2I(60, 20), dimensions=Extent2I(1, 10)),
355 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 2)),
356 Box2I(corner=Point2I(61, 6), dimensions=Extent2I(2, 8))]
357 for badBox in badPixels:
358 defects.append(badBox)
360 self.check_maskBlocks(defects, expectedDefects)
362 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self):
363 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
364 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to the other side,
365 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs <
366 goodPixelColumnGapThreshold (5).
368 Plots can be found in DM-19903 on Jira.
369 """
371 expectedDefects = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 29)),
372 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 12))]
373 defects = self.allDefectsList
374 badPixels = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 18)),
375 Box2I(corner=Point2I(70, 20), dimensions=Extent2I(1, 10)),
376 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 2)),
377 Box2I(corner=Point2I(68, 6), dimensions=Extent2I(2, 8))]
378 for badBox in badPixels:
379 defects.append(badBox)
381 self.check_maskBlocks(defects, expectedDefects)
383 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self):
384 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
385 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to both sides,
386 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs <
387 goodPixelColumnGapThreshold (5).
389 Plots can be found in DM-19903 on Jira.
390 """
392 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)),
393 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 12)),
394 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 12))]
395 defects = self.allDefectsList
396 badPixels = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 18)),
397 Box2I(corner=Point2I(75, 20), dimensions=Extent2I(1, 10)),
398 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 2)),
399 Box2I(corner=Point2I(73, 6), dimensions=Extent2I(2, 8)),
400 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 2)),
401 Box2I(corner=Point2I(76, 6), dimensions=Extent2I(2, 8))]
402 for badBox in badPixels:
403 defects.append(badBox)
405 self.check_maskBlocks(defects, expectedDefects)
407 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self):
408 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
409 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to one side,
410 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs >
411 goodPixelColumnGapThreshold (5).
413 Plots can be found in DM-19903 on Jira.
414 """
416 expectedDefects = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 29)),
417 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
418 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
419 defects = self.allDefectsList
420 badPixels = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 18)),
421 Box2I(corner=Point2I(80, 20), dimensions=Extent2I(1, 10)),
422 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
423 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
424 for badBox in badPixels:
425 defects.append(badBox)
427 self.check_maskBlocks(defects, expectedDefects)
429 def test_maskBlocks_other_side_good_greater_than_threshold(self):
430 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
431 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to the other side,
432 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs >
433 goodPixelColumnGapThreshold (5).
435 Plots can be found in DM-19903 on Jira.
436 """
438 expectedDefects = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 29)),
439 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
440 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
441 defects = self.allDefectsList
442 badPixels = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 18)),
443 Box2I(corner=Point2I(87, 20), dimensions=Extent2I(1, 10)),
444 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
445 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
446 for badBox in badPixels:
447 defects.append(badBox)
449 self.check_maskBlocks(defects, expectedDefects)
451 def test_maskBlocks_both_sides_good_greater_than_threshold(self):
452 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
453 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to both sides,
454 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs >
455 goodPixelColumnGapThreshold (5).
457 Plots can be found in DM-19903 on Jira.
458 """
460 expectedDefects = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 34)),
461 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 7)),
462 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 9)),
463 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 7)),
464 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 9))]
465 defects = self.allDefectsList
466 badPixels = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 12)),
467 Box2I(corner=Point2I(93, 15), dimensions=Extent2I(1, 20)),
468 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 2)),
469 Box2I(corner=Point2I(91, 7), dimensions=Extent2I(2, 2)),
470 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 2)),
471 Box2I(corner=Point2I(94, 7), dimensions=Extent2I(2, 2)),
472 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 3)),
473 Box2I(corner=Point2I(91, 24), dimensions=Extent2I(2, 3)),
474 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 3)),
475 Box2I(corner=Point2I(94, 24), dimensions=Extent2I(2, 3))]
476 for badBox in badPixels:
477 defects.append(badBox)
479 self.check_maskBlocks(defects, expectedDefects)
481 def test_defectFindingAllSensor(self):
482 config = copy.copy(self.defaultConfig)
483 config.nPixBorderLeftRight = 0
484 config.nPixBorderUpDown = 0
486 task = cpPipe.defects.FindDefectsTask(config=config)
488 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
490 allBBoxes = self.darkBBoxes + self.brightBBoxes
492 boxesMeasured = []
493 for defect in defects:
494 boxesMeasured.append(defect.getBBox())
496 for expectedBBox in allBBoxes:
497 self.assertIn(expectedBBox, boxesMeasured)
499 def test_defectFindingEdgeIgnore(self):
500 config = copy.copy(self.defaultConfig)
501 config.nPixBorderUpDown = 0
502 task = cpPipe.defects.FindDefectsTask(config=config)
503 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
505 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges]
507 boxesMeasured = []
508 for defect in defects:
509 boxesMeasured.append(defect.getBBox())
511 for expectedBBox in shouldBeFound:
512 self.assertIn(expectedBBox, boxesMeasured)
514 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges]
515 for boxMissed in shouldBeMissed:
516 self.assertNotIn(boxMissed, boxesMeasured)
518 def test_postProcessDefectSets(self):
519 """Tests the way in which the defect sets merge.
521 There is potential for logic errors in their combination
522 so several combinations of defects and combination methods
523 are tested here."""
524 defects = self.defaultTask.findHotAndColdPixels(self.flatExp, 'flat')
526 # defect list has length one
527 merged = self.defaultTask._postProcessDefectSets([defects], self.flatExp.getDimensions(), 'FRACTION')
528 self.assertEqual(defects, merged)
530 # should always be true regardless of config
531 # defect list now has length 2
532 merged = self.defaultTask._postProcessDefectSets([defects, defects], self.flatExp.getDimensions(),
533 'FRACTION')
534 self.assertEqual(defects, merged)
536 # now start manipulating defect lists
537 config = copy.copy(self.defaultConfig)
538 config.combinationMode = 'FRACTION'
539 config.combinationFraction = 0.85
540 task = cpPipe.defects.FindDefectsTask(config=config)
541 merged = task._postProcessDefectSets([defects, defects], self.flatExp.getDimensions(), 'FRACTION')
543 defectList = [defects]*10 # 10 identical defect sets
544 # remove one defect from one of them, should still be over threshold
545 defectList[7] = defectList[7][:-1]
546 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'FRACTION')
547 self.assertEqual(defects, merged)
549 # remove another and should be under threshold
550 defectList[3] = defectList[3][:-1]
551 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'FRACTION')
552 self.assertNotEqual(defects, merged)
554 # now test the AND and OR modes
555 defectList = [defects]*10 # 10 identical defect sets
556 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND')
557 self.assertEqual(defects, merged)
559 defectList[7] = defectList[7][:-1]
560 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND')
561 self.assertNotEqual(defects, merged)
563 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'OR')
564 self.assertEqual(defects, merged)
566 def test_pixelCounting(self):
567 """Test that the number of defective pixels identified is as expected."""
568 config = copy.copy(self.defaultConfig)
569 config.nPixBorderUpDown = 0
570 config.nPixBorderLeftRight = 0
571 task = cpPipe.defects.FindDefectsTask(config=config)
572 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
574 defectArea = 0
575 for defect in defects:
576 defectArea += defect.getBBox().getArea()
578 # The columnar code will cover blocks of a column
579 # with on-and-off pixels, thus creating more bad pixels
580 # that what initially placed in self.brightDefects and self.darkDefects.
581 # Thus, defectArea should be >= crossCheck.
582 crossCheck = 0
583 for x, y, sx, sy in self.brightDefects:
584 crossCheck += sx*sy
585 for x, y, sx, sy in self.darkDefects:
586 crossCheck += sx*sy
588 # Test the result of _nPixFromDefects()
589 # via two different ways of calculating area.
590 self.assertEqual(defectArea, task._nPixFromDefects(defects))
591 # defectArea should be >= crossCheck
592 self.assertGreaterEqual(defectArea, crossCheck)
594 def test_getNumGoodPixels(self):
595 """Test the the number of pixels in the image not masked is as expected."""
596 testImage = self.flatExp.clone()
597 mi = testImage.maskedImage
599 imageSize = testImage.getBBox().getArea()
600 nGood = self.defaultTask._getNumGoodPixels(mi)
602 self.assertEqual(imageSize, nGood)
604 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
606 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6))
607 testImage.mask[noDataBox] |= NODATABIT
609 self.assertEqual(imageSize - noDataBox.getArea(), self.defaultTask._getNumGoodPixels(mi))
610 # check for misfire; we're setting NO_DATA here, not BAD
611 self.assertEqual(imageSize, self.defaultTask._getNumGoodPixels(mi, 'BAD'))
613 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
614 self.assertEqual(imageSize, nGood)
616 BADBIT = mi.mask.getPlaneBitMask("BAD")
617 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7))
618 testImage.mask[badBox] |= BADBIT
620 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD'))
622 def test_edgeMasking(self):
623 """Check that the right number of edge pixels are masked by _setEdgeBits()"""
624 testImage = self.flatExp.clone()
625 mi = testImage.maskedImage
627 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0)
628 self.defaultTask._setEdgeBits(mi)
630 hEdge = self.defaultConfig.nPixBorderLeftRight
631 vEdge = self.defaultConfig.nPixBorderUpDown
632 xSize, ySize = mi.getDimensions()
634 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4
636 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge)
639class TestMemory(lsst.utils.tests.MemoryTestCase):
640 pass
643def setup_module(module):
644 lsst.utils.tests.init()
647if __name__ == "__main__": 647 ↛ 648line 647 didn't jump to line 648, because the condition on line 647 was never true
648 lsst.utils.tests.init()
649 unittest.main()