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.doAddDistortionModel = False
48 config.doUseOpticsTransmission = False
49 config.doUseFilterTransmission = False
50 config.doUseSensorTransmission = False
51 config.doUseAtmosphereTransmission = False
52 config.doAttachTransmissionCurve = False
54 self.flatMean = 2000
55 self.darkMean = 1
56 self.readNoiseAdu = 10
57 self.nSigmaBright = 8
58 self.nSigmaDark = 8
60 mockImageConfig = isrMock.IsrMock.ConfigClass()
62 # flatDrop is not really relevant as we replace the data
63 # but good to note it in case we change how this image is made
64 mockImageConfig.flatDrop = 0.99999
65 mockImageConfig.isTrimmed = True
67 self.flatExp = isrMock.FlatMock(config=mockImageConfig).run()
68 (shapeY, shapeX) = self.flatExp.getDimensions()
70 # x, y, size tuples
71 # always put edge defects at the start and change the value of nEdge
73 self.brightDefects = [(0, 15, 3, 3), (100, 123, 1, 1)]
75 self.darkDefects = [(5, 0, 1, 1), (7, 62, 2, 2)]
77 nEdge = 1 # NOTE: update if more edge defects are included
78 self.noEdges = slice(nEdge, None)
79 self.onlyEdges = slice(0, nEdge)
81 self.darkBBoxes = [Box2I(Point2I(x, y), Extent2I(sx, sy)) for (x, y, sx, sy) in self.darkDefects]
82 self.brightBBoxes = [Box2I(Point2I(x, y), Extent2I(sx, sy)) for (x, y, sx, sy) in self.brightDefects]
84 flatWidth = np.sqrt(self.flatMean) + self.readNoiseAdu
85 darkWidth = self.readNoiseAdu
86 self.rng = np.random.RandomState(0)
87 flatData = self.rng.normal(self.flatMean, flatWidth, (shapeX, shapeY))
88 darkData = self.rng.normal(self.darkMean, darkWidth, (shapeX, shapeY))
90 # NOTE: darks and flats have same defects applied deliberately to both
91 for defect in self.brightDefects:
92 y, x, sy, sx = defect
93 # are these actually the numbers we want?
94 flatData[x:x+sx, y:y+sy] += self.nSigmaBright * flatWidth
95 darkData[x:x+sx, y:y+sy] += self.nSigmaBright * darkWidth
97 for defect in self.darkDefects:
98 y, x, sy, sx = defect
99 # are these actually the numbers we want?
100 flatData[x:x+sx, y:y+sy] -= self.nSigmaDark * flatWidth
101 darkData[x:x+sx, y:y+sy] -= self.nSigmaDark * darkWidth
103 self.darkExp = self.flatExp.clone()
104 self.spareImage = self.flatExp.clone() # for testing edge bits and misc
106 self.flatExp.image.array[:] = flatData
107 self.darkExp.image.array[:] = darkData
109 self.defaultTask = cpPipe.defects.FindDefectsTask(config=self.defaultConfig)
111 self.allDefectsList = measAlg.Defects()
113 self.brightDefectsList = measAlg.Defects()
114 for d in self.brightBBoxes:
115 self.brightDefectsList.append(d)
116 self.allDefectsList.append(d)
118 self.darkDefectsList = measAlg.Defects()
119 for d in self.darkBBoxes:
120 self.darkDefectsList.append(d)
121 self.allDefectsList.append(d)
123 def check_maskBlocks(self, inputDefects, expectedDefects):
124 """A helper function for the tests of maskBlocksIfIntermitentBadPixelsInColumn.
125 """
126 config = copy.copy(self.defaultConfig)
127 config.badOnAndOffPixelColumnThreshold = 10
128 config.goodPixelColumnGapThreshold = 5
129 config.nPixBorderUpDown = 0
130 config.nPixBorderLeftRight = 0
132 task = cpPipe.defects.FindDefectsTask(config=config)
134 defectsWithColumns = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects)
136 boxesMeasured = []
137 for defect in defectsWithColumns:
138 boxesMeasured.append(defect.getBBox())
140 for boxInput in expectedDefects:
141 self.assertIn(boxInput, boxesMeasured)
143 # Check that the code did not mask anything extra by
144 # looking in both the input list and "expanded-column" list.
145 unionInputExpectedBoxes = []
146 for defect in inputDefects:
147 unionInputExpectedBoxes.append(defect.getBBox())
148 for defect in expectedDefects:
149 unionInputExpectedBoxes.append(defect)
151 # Check that code doesn't mask more than it is supposed to.
152 for boxMeas in boxesMeasured:
153 self.assertIn(boxMeas, unionInputExpectedBoxes)
155 def test_maskBlocks_full_column(self):
156 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
157 Tests that a contigous bad column does not get split by the code.
159 The mock flat has a size of 200X204 pixels. This column has a maximum length of 50
160 pixels, otherwise there would be a split along the mock amp boundary.
162 Plots can be found in DM-19903 on Jira.
163 """
165 defects = self.allDefectsList
166 defects.append(Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50)))
167 expectedDefects = [Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50))]
169 self.check_maskBlocks(defects, expectedDefects)
171 def test_maskBlocks_long_column(self):
172 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
173 Tests that a contigous bad column with Npix >= badOnAndOffPixelColumnThreshold (10)
174 does not get split by the code.
176 Plots can be found in DM-19903 on Jira.
177 """
179 expectedDefects = [Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))]
180 defects = self.allDefectsList
181 defects.append(Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25)))
183 self.check_maskBlocks(defects, expectedDefects)
185 def test_maskBlocks_short_column(self):
186 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
187 Tests that a contigous bad column Npix < badOnAndOffPixelColumnThreshold (10)
188 does not get split by the code.
190 Plots can be found in DM-19903 on Jira.
191 """
193 expectedDefects = [Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))]
194 defects = self.allDefectsList
195 defects.append(Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8)))
197 self.check_maskBlocks(defects, expectedDefects)
199 def test_maskBlocks_discontigous_to_single_block(self):
200 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
201 Npix discontiguous bad pixels in a column where Npix >= badOnAndOffPixelColumnThreshold (10)
202 and gaps of good pixels < goodPixelColumnGapThreshold (5). Under these conditions, the whole
203 block of bad pixels (including good gaps) should be masked.
205 Plots can be found in DM-19903 on Jira.
206 """
208 expectedDefects = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 48))]
209 defects = self.allDefectsList
210 badPixels = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 2)),
211 Box2I(corner=Point2I(30, 5), dimensions=Extent2I(1, 3)),
212 Box2I(corner=Point2I(30, 11), dimensions=Extent2I(1, 5)),
213 Box2I(corner=Point2I(30, 19), dimensions=Extent2I(1, 5)),
214 Box2I(corner=Point2I(30, 27), dimensions=Extent2I(1, 4)),
215 Box2I(corner=Point2I(30, 34), dimensions=Extent2I(1, 15))]
216 for badBox in badPixels:
217 defects.append(badBox)
218 self.check_maskBlocks(defects, expectedDefects)
220 def test_maskBlocks_discontigous_less_than_thresholds(self):
221 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
222 Npix discontiguous bad pixels in a column where Npix < badOnAndOffPixelColumnThreshold (10)
223 and gaps of good pixels < goodPixelColumnGapThreshold (5). Under these conditions,
224 the expected defect boxes should be the same as the input boxes.
226 Plots can be found in DM-19903 on Jira.
227 """
229 expectedDefects = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
230 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
231 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
232 defects = self.allDefectsList
233 badPixels = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
234 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
235 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
236 for badBox in badPixels:
237 defects.append(badBox)
239 self.check_maskBlocks(defects, expectedDefects)
241 def test_maskBlocks_more_than_thresholds(self):
242 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
243 Npix discontiguous bad pixels in a column where Npix < badOnAndOffPixelColumnThreshold (10)
244 and gaps of good pixels < goodPixelColumnGapThreshold (5).
245 Npix=34 (> 10) bad pixels total, 1 "good" gap with 13 pixels big enough
246 (13 >= 5 good pixels, from y=6 (1+5) to y=19).
248 Plots can be found in DM-19903 on Jira.
249 """
251 expectedDefects = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 7)),
252 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 30))]
253 defects = self.allDefectsList
254 badPixels = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 2)),
255 Box2I(corner=Point2I(40, 5), dimensions=Extent2I(1, 3)),
256 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 5)),
257 Box2I(corner=Point2I(40, 27), dimensions=Extent2I(1, 4)),
258 Box2I(corner=Point2I(40, 34), dimensions=Extent2I(1, 15))]
259 for badBox in badPixels:
260 defects.append(badBox)
262 self.check_maskBlocks(defects, expectedDefects)
264 def test_maskBlocks_not_enough_bad_pixels_in_column(self):
265 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
266 Npix discontiguous bad pixels in a column where Npix < badOnAndOffPixelColumnThreshold (10) and
267 and gaps of good pixels > goodPixelColumnGapThreshold (5). Since Npix <
268 badOnAndOffPixelColumnThreshold, then it doesn't matter that the number of good pixels in gap >
269 goodPixelColumnGapThreshold. 5<10 bad pixels total, 1 "good" gap big enough
270 (29>=5 good pixels, from y =12 (10+2) to y=30)
272 Plots can be found in DM-19903 on Jira.
273 """
275 expectedDefects = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
276 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
277 defects = self.allDefectsList
278 badPixels = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
279 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
280 for badBox in badPixels:
281 defects.append(badBox)
283 self.check_maskBlocks(defects, expectedDefects)
285 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self):
286 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
287 Npix discontiguous bad pixels in a column where Npix > badOnAndOffPixelColumnThreshold (10)
288 and every other pixel is bad.
290 Plots can be found in DM-19903 on Jira.
291 """
293 expectedDefects = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 31))]
294 defects = self.allDefectsList
295 badPixels = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 1)),
296 Box2I(corner=Point2I(50, 12), dimensions=Extent2I(1, 1)),
297 Box2I(corner=Point2I(50, 14), dimensions=Extent2I(1, 1)),
298 Box2I(corner=Point2I(50, 16), dimensions=Extent2I(1, 1)),
299 Box2I(corner=Point2I(50, 18), dimensions=Extent2I(1, 1)),
300 Box2I(corner=Point2I(50, 20), dimensions=Extent2I(1, 1)),
301 Box2I(corner=Point2I(50, 22), dimensions=Extent2I(1, 1)),
302 Box2I(corner=Point2I(50, 24), dimensions=Extent2I(1, 1)),
303 Box2I(corner=Point2I(50, 26), dimensions=Extent2I(1, 1)),
304 Box2I(corner=Point2I(50, 28), dimensions=Extent2I(1, 1)),
305 Box2I(corner=Point2I(50, 30), dimensions=Extent2I(1, 1)),
306 Box2I(corner=Point2I(50, 32), dimensions=Extent2I(1, 1)),
307 Box2I(corner=Point2I(50, 34), dimensions=Extent2I(1, 1)),
308 Box2I(corner=Point2I(50, 36), dimensions=Extent2I(1, 1)),
309 Box2I(corner=Point2I(50, 38), dimensions=Extent2I(1, 1)),
310 Box2I(corner=Point2I(50, 40), dimensions=Extent2I(1, 1))]
311 for badBox in badPixels:
312 defects.append(badBox)
314 self.check_maskBlocks(defects, expectedDefects)
316 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self):
317 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
318 Npix discontiguous bad pixels in a column where Npix > badOnAndOffPixelColumnThreshold (10)
319 and every other pixel is bad.
321 Plots can be found in DM-19903 on Jira.
322 """
324 expectedDefects = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
325 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
326 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
327 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
328 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
329 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
330 defects = self.allDefectsList
331 badPixels = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
332 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
333 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
334 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
335 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
336 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
337 for badBox in badPixels:
338 defects.append(badBox)
340 self.check_maskBlocks(defects, expectedDefects)
342 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self):
343 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
344 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to one side,
345 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs <
346 goodPixelColumnGapThreshold (5).
348 Plots can be found in DM-19903 on Jira.
349 """
351 expectedDefects = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 29)),
352 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(1, 12)),
353 Box2I(corner=Point2I(62, 2), dimensions=Extent2I(1, 12))]
354 defects = self.allDefectsList
355 badPixels = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 18)),
356 Box2I(corner=Point2I(60, 20), dimensions=Extent2I(1, 10)),
357 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 2)),
358 Box2I(corner=Point2I(61, 6), dimensions=Extent2I(2, 8))]
359 for badBox in badPixels:
360 defects.append(badBox)
362 self.check_maskBlocks(defects, expectedDefects)
364 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self):
365 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
366 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to the other side,
367 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs <
368 goodPixelColumnGapThreshold (5).
370 Plots can be found in DM-19903 on Jira.
371 """
373 expectedDefects = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 29)),
374 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(1, 12)),
375 Box2I(corner=Point2I(69, 2), dimensions=Extent2I(1, 12))]
376 defects = self.allDefectsList
377 badPixels = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 18)),
378 Box2I(corner=Point2I(70, 20), dimensions=Extent2I(1, 10)),
379 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 2)),
380 Box2I(corner=Point2I(68, 6), dimensions=Extent2I(2, 8))]
381 for badBox in badPixels:
382 defects.append(badBox)
384 self.check_maskBlocks(defects, expectedDefects)
386 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self):
387 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
388 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to both sides,
389 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs <
390 goodPixelColumnGapThreshold (5).
392 Plots can be found in DM-19903 on Jira.
393 """
395 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)),
396 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(1, 12)),
397 Box2I(corner=Point2I(74, 2), dimensions=Extent2I(1, 12)),
398 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(1, 12)),
399 Box2I(corner=Point2I(77, 2), dimensions=Extent2I(1, 12))]
400 defects = self.allDefectsList
401 badPixels = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 18)),
402 Box2I(corner=Point2I(75, 20), dimensions=Extent2I(1, 10)),
403 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 2)),
404 Box2I(corner=Point2I(73, 6), dimensions=Extent2I(2, 8)),
405 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 2)),
406 Box2I(corner=Point2I(76, 6), dimensions=Extent2I(2, 8))]
407 for badBox in badPixels:
408 defects.append(badBox)
410 self.check_maskBlocks(defects, expectedDefects)
412 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self):
413 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
414 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to one side,
415 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs >
416 goodPixelColumnGapThreshold (5).
418 Plots can be found in DM-19903 on Jira.
419 """
421 expectedDefects = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 29)),
422 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(1, 2)),
423 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(1, 8))]
424 defects = self.allDefectsList
425 badPixels = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 18)),
426 Box2I(corner=Point2I(80, 20), dimensions=Extent2I(1, 10)),
427 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
428 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
429 for badBox in badPixels:
430 defects.append(badBox)
432 self.check_maskBlocks(defects, expectedDefects)
434 def test_maskBlocks_other_side_good_greater_than_threshold(self):
435 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
436 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to the other side,
437 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs >
438 goodPixelColumnGapThreshold (5).
440 Plots can be found in DM-19903 on Jira.
441 """
443 expectedDefects = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 29)),
444 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(1, 2)),
445 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(1, 8))]
446 defects = self.allDefectsList
447 badPixels = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 18)),
448 Box2I(corner=Point2I(87, 20), dimensions=Extent2I(1, 10)),
449 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
450 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
451 for badBox in badPixels:
452 defects.append(badBox)
454 self.check_maskBlocks(defects, expectedDefects)
456 def test_maskBlocks_both_sides_good_greater_than_threshold(self):
457 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
458 Npix discontiguous bad pixels in column with "blobs" of "m" bad pixels to both sides,
459 m > badOnAndOffPixelColumnThreshold (10), number of good pixel in gaps between blobs >
460 goodPixelColumnGapThreshold (5).
462 Plots can be found in DM-19903 on Jira.
463 """
465 expectedDefects = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 34)),
466 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(1, 7)),
467 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(1, 9)),
468 Box2I(corner=Point2I(92, 2), dimensions=Extent2I(1, 7)),
469 Box2I(corner=Point2I(92, 18), dimensions=Extent2I(1, 9)),
470 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(1, 7)),
471 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(1, 9)),
472 Box2I(corner=Point2I(95, 2), dimensions=Extent2I(1, 7)),
473 Box2I(corner=Point2I(95, 18), dimensions=Extent2I(1, 9))]
474 defects = self.allDefectsList
475 badPixels = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 12)),
476 Box2I(corner=Point2I(93, 15), dimensions=Extent2I(1, 20)),
477 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 2)),
478 Box2I(corner=Point2I(91, 7), dimensions=Extent2I(2, 2)),
479 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 2)),
480 Box2I(corner=Point2I(94, 7), dimensions=Extent2I(2, 2)),
481 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 3)),
482 Box2I(corner=Point2I(91, 24), dimensions=Extent2I(2, 3)),
483 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 3)),
484 Box2I(corner=Point2I(94, 24), dimensions=Extent2I(2, 3))]
485 for badBox in badPixels:
486 defects.append(badBox)
488 self.check_maskBlocks(defects, expectedDefects)
490 def test_defectFindingAllSensor(self):
491 config = copy.copy(self.defaultConfig)
492 config.nPixBorderLeftRight = 0
493 config.nPixBorderUpDown = 0
495 task = cpPipe.defects.FindDefectsTask(config=config)
497 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
499 allBBoxes = self.darkBBoxes + self.brightBBoxes
501 boxesMeasured = []
502 for defect in defects:
503 boxesMeasured.append(defect.getBBox())
505 for expectedBBox in allBBoxes:
506 self.assertIn(expectedBBox, boxesMeasured)
508 def test_defectFindingEdgeIgnore(self):
509 config = copy.copy(self.defaultConfig)
510 config.nPixBorderUpDown = 0
511 task = cpPipe.defects.FindDefectsTask(config=config)
512 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
514 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges]
516 boxesMeasured = []
517 for defect in defects:
518 boxesMeasured.append(defect.getBBox())
520 for expectedBBox in shouldBeFound:
521 self.assertIn(expectedBBox, boxesMeasured)
523 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges]
524 for boxMissed in shouldBeMissed:
525 self.assertNotIn(boxMissed, boxesMeasured)
527 def test_postProcessDefectSets(self):
528 """Tests the way in which the defect sets merge.
530 There is potential for logic errors in their combination
531 so several combinations of defects and combination methods
532 are tested here."""
533 defects = self.defaultTask.findHotAndColdPixels(self.flatExp, 'flat')
535 # defect list has length one
536 merged = self.defaultTask._postProcessDefectSets([defects], self.flatExp.getDimensions(), 'FRACTION')
537 self.assertEqual(defects, merged)
539 # should always be true regardless of config
540 # defect list now has length 2
541 merged = self.defaultTask._postProcessDefectSets([defects, defects], self.flatExp.getDimensions(),
542 'FRACTION')
543 self.assertEqual(defects, merged)
545 # now start manipulating defect lists
546 config = copy.copy(self.defaultConfig)
547 config.combinationMode = 'FRACTION'
548 config.combinationFraction = 0.85
549 task = cpPipe.defects.FindDefectsTask(config=config)
550 merged = task._postProcessDefectSets([defects, defects], self.flatExp.getDimensions(), 'FRACTION')
552 defectList = [defects]*10 # 10 identical defect sets
553 # remove one defect from one of them, should still be over threshold
554 defectList[7] = defectList[7][:-1]
555 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'FRACTION')
556 self.assertEqual(defects, merged)
558 # remove another and should be under threshold
559 defectList[3] = defectList[3][:-1]
560 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'FRACTION')
561 self.assertNotEqual(defects, merged)
563 # now test the AND and OR modes
564 defectList = [defects]*10 # 10 identical defect sets
565 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND')
566 self.assertEqual(defects, merged)
568 defectList[7] = defectList[7][:-1]
569 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'AND')
570 self.assertNotEqual(defects, merged)
572 merged = task._postProcessDefectSets(defectList, self.flatExp.getDimensions(), 'OR')
573 self.assertEqual(defects, merged)
575 def test_pixelCounting(self):
576 """Test that the number of defective pixels identified is as expected."""
577 config = copy.copy(self.defaultConfig)
578 config.nPixBorderUpDown = 0
579 config.nPixBorderLeftRight = 0
580 task = cpPipe.defects.FindDefectsTask(config=config)
581 defects = task.findHotAndColdPixels(self.flatExp, 'flat')
583 defectArea = 0
584 for defect in defects:
585 defectArea += defect.getBBox().getArea()
587 # The columnar code will cover blocks of a column
588 # with on-and-off pixels, thus creating more bad pixels
589 # that what initially placed in self.brightDefects and self.darkDefects.
590 # Thus, defectArea should be >= crossCheck.
591 crossCheck = 0
592 for x, y, sx, sy in self.brightDefects:
593 crossCheck += sx*sy
594 for x, y, sx, sy in self.darkDefects:
595 crossCheck += sx*sy
597 # Test the result of _nPixFromDefects()
598 # via two different ways of calculating area.
599 self.assertEqual(defectArea, task._nPixFromDefects(defects))
600 # defectArea should be >= crossCheck
601 self.assertGreaterEqual(defectArea, crossCheck)
603 def test_getNumGoodPixels(self):
604 """Test the the number of pixels in the image not masked is as expected."""
605 testImage = self.flatExp.clone()
606 mi = testImage.maskedImage
608 imageSize = testImage.getBBox().getArea()
609 nGood = self.defaultTask._getNumGoodPixels(mi)
611 self.assertEqual(imageSize, nGood)
613 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
615 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6))
616 testImage.mask[noDataBox] |= NODATABIT
618 self.assertEqual(imageSize - noDataBox.getArea(), self.defaultTask._getNumGoodPixels(mi))
619 # check for misfire; we're setting NO_DATA here, not BAD
620 self.assertEqual(imageSize, self.defaultTask._getNumGoodPixels(mi, 'BAD'))
622 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
623 self.assertEqual(imageSize, nGood)
625 BADBIT = mi.mask.getPlaneBitMask("BAD")
626 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7))
627 testImage.mask[badBox] |= BADBIT
629 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD'))
631 def test_edgeMasking(self):
632 """Check that the right number of edge pixels are masked by _setEdgeBits()"""
633 testImage = self.flatExp.clone()
634 mi = testImage.maskedImage
636 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0)
637 self.defaultTask._setEdgeBits(mi)
639 hEdge = self.defaultConfig.nPixBorderLeftRight
640 vEdge = self.defaultConfig.nPixBorderUpDown
641 xSize, ySize = mi.getDimensions()
643 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4
645 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge)
648class TestMemory(lsst.utils.tests.MemoryTestCase):
649 pass
652def setup_module(module):
653 lsst.utils.tests.init()
656if __name__ == "__main__": 656 ↛ 657line 656 didn't jump to line 657, because the condition on line 656 was never true
657 lsst.utils.tests.init()
658 unittest.main()