Coverage for tests / test_defects.py: 8%
553 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 08:52 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 08:52 +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.MeasureDefectsTask."""
25import unittest
26import numpy as np
27import copy
28from scipy.interpolate import Akima1DInterpolator
30import lsst.utils
31import lsst.utils.tests
33import lsst.afw.image as afwImage
34import lsst.ip.isr as ipIsr
35import lsst.cp.pipe as cpPipe
36from lsst.ip.isr import isrMock, countMaskedPixels
37from lsst.geom import Box2I, Point2I, Extent2I
38from lsst.daf.base import PropertyList
41class MeasureDefectsTaskTestCase(lsst.utils.tests.TestCase):
42 """A test case for the defect finding task."""
44 def setUp(self):
45 self.defaultConfig = cpPipe.MeasureDefectsTask.ConfigClass()
47 self.flatMean = 2000
48 self.darkMean = 1
49 self.readNoiseAdu = 10
50 self.nSigmaBright = 8
51 self.nSigmaDark = 8
53 mockImageConfig = isrMock.IsrMock.ConfigClass()
55 # flatDrop is not really relevant as we replace the data
56 # but good to note it in case we change how this image is made
57 mockImageConfig.flatDrop = 0.99999
58 mockImageConfig.isTrimmed = True
60 self.flatExp = isrMock.FlatMock(config=mockImageConfig).run()
61 (shapeY, shapeX) = self.flatExp.getDimensions()
62 # x, y, size tuples
63 # always put edge defects at the start and change the value of nEdge
65 self.brightDefects = [(0, 15, 3, 3), (100, 123, 1, 1)]
67 self.darkDefects = [(5, 0, 1, 1), (7, 62, 2, 2)]
69 nEdge = 1 # NOTE: update if more edge defects are included
70 self.noEdges = slice(nEdge, None)
71 self.onlyEdges = slice(0, nEdge)
73 self.darkBBoxes = [Box2I(Point2I(x, y), Extent2I(sx, sy)) for (x, y, sx, sy) in self.darkDefects]
74 self.brightBBoxes = [Box2I(Point2I(x, y), Extent2I(sx, sy)) for (x, y, sx, sy) in self.brightDefects]
76 flatWidth = np.sqrt(self.flatMean) + self.readNoiseAdu
77 darkWidth = self.readNoiseAdu
78 self.rng = np.random.RandomState(0)
79 flatData = self.rng.normal(self.flatMean, flatWidth, (shapeX, shapeY))
80 darkData = self.rng.normal(self.darkMean, darkWidth, (shapeX, shapeY))
82 # NOTE: darks and flats have same defects applied deliberately to both
83 for defect in self.brightDefects:
84 y, x, sy, sx = defect
85 # are these actually the numbers we want?
86 flatData[x:x+sx, y:y+sy] += self.nSigmaBright * flatWidth
87 darkData[x:x+sx, y:y+sy] += self.nSigmaBright * darkWidth
89 for defect in self.darkDefects:
90 y, x, sy, sx = defect
91 # are these actually the numbers we want?
92 flatData[x:x+sx, y:y+sy] -= self.nSigmaDark * flatWidth
93 darkData[x:x+sx, y:y+sy] -= self.nSigmaDark * darkWidth
95 self.darkExp = self.flatExp.clone()
96 self.spareImage = self.flatExp.clone() # for testing edge bits and misc
98 self.flatExp.image.array[:] = flatData
99 self.darkExp.image.array[:] = darkData
101 self.defaultTask = cpPipe.MeasureDefectsTask()
103 self.allDefectsList = ipIsr.Defects()
104 self.brightDefectsList = ipIsr.Defects()
105 self.darkDefectsList = ipIsr.Defects()
107 # Set image types, the defects code will use them.
108 metaDataFlat = PropertyList()
109 metaDataFlat["IMGTYPE"] = "FLAT"
110 self.flatExp.setMetadata(metaDataFlat)
112 metaDataDark = PropertyList()
113 metaDataDark["IMGTYPE"] = "DARK"
114 self.darkExp.setMetadata(metaDataDark)
116 with self.allDefectsList.bulk_update():
117 with self.brightDefectsList.bulk_update():
118 for d in self.brightBBoxes:
119 self.brightDefectsList.append(d)
120 self.allDefectsList.append(d)
122 with self.darkDefectsList.bulk_update():
123 for d in self.darkBBoxes:
124 self.darkDefectsList.append(d)
125 self.allDefectsList.append(d)
127 def check_maskBlocks(self, inputDefects, expectedDefects):
128 """A helper function for the tests of
129 maskBlocksIfIntermitentBadPixelsInColumn.
131 """
132 config = copy.copy(self.defaultConfig)
133 config.badOnAndOffPixelColumnThreshold = 10
134 config.goodPixelColumnGapThreshold = 5
135 config.nPixBorderUpDown = 0
136 config.nPixBorderLeftRight = 0
138 task = self.defaultTask
139 task.config = config
141 defectsWithColumns, count = task.maskBlocksIfIntermitentBadPixelsInColumn(inputDefects)
142 boxesMeasured = []
143 for defect in defectsWithColumns:
144 boxesMeasured.append(defect.getBBox())
146 for boxInput in expectedDefects:
147 self.assertIn(boxInput, boxesMeasured)
149 # Check that the code did not mask anything extra by
150 # looking in both the input list and "expanded-column" list.
151 unionInputExpectedBoxes = []
152 for defect in inputDefects:
153 unionInputExpectedBoxes.append(defect.getBBox())
154 for defect in expectedDefects:
155 unionInputExpectedBoxes.append(defect)
157 # Check that code doesn't mask more than it is supposed to.
158 for boxMeas in boxesMeasured:
159 self.assertIn(boxMeas, unionInputExpectedBoxes)
161 def test_maskBlocks_full_column(self):
162 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
164 Tests that a contigous bad column does not get split by the
165 code.
167 The mock flat has a size of 200X204 pixels. This column has a
168 maximum length of 50 pixels, otherwise there would be a split
169 along the mock amp boundary.
171 Plots can be found in DM-19903 on Jira.
173 """
175 defects = self.allDefectsList
176 defects.append(Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50)))
177 expectedDefects = [Box2I(corner=Point2I(15, 1), dimensions=Extent2I(1, 50))]
179 self.check_maskBlocks(defects, expectedDefects)
181 def test_maskBlocks_long_column(self):
182 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
184 Tests that a contigous bad column with Npix >=
185 badOnAndOffPixelColumnThreshold (10) does not get split by the
186 code.
188 Plots can be found in DM-19903 on Jira.
190 """
192 expectedDefects = [Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25))]
193 defects = self.allDefectsList
194 defects.append(Box2I(corner=Point2I(20, 1), dimensions=Extent2I(1, 25)))
196 self.check_maskBlocks(defects, expectedDefects)
198 def test_maskBlocks_short_column(self):
199 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
201 Tests that a contigous bad column Npix <
202 badOnAndOffPixelColumnThreshold (10) does not get split by the
203 code.
205 Plots can be found in DM-19903 on Jira.
207 """
209 expectedDefects = [Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8))]
210 defects = self.allDefectsList
211 defects.append(Box2I(corner=Point2I(25, 1), dimensions=Extent2I(1, 8)))
213 self.check_maskBlocks(defects, expectedDefects)
215 def test_maskBlocks_discontigous_to_single_block(self):
216 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
218 Npix discontiguous bad pixels in a column where Npix >=
219 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
220 goodPixelColumnGapThreshold (5). Under these conditions, the
221 whole block of bad pixels (including good gaps) should be
222 masked.
224 Plots can be found in DM-19903 on Jira.
226 """
228 expectedDefects = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 48))]
229 defects = self.allDefectsList
230 badPixels = [Box2I(corner=Point2I(30, 1), dimensions=Extent2I(1, 2)),
231 Box2I(corner=Point2I(30, 5), dimensions=Extent2I(1, 3)),
232 Box2I(corner=Point2I(30, 11), dimensions=Extent2I(1, 5)),
233 Box2I(corner=Point2I(30, 19), dimensions=Extent2I(1, 5)),
234 Box2I(corner=Point2I(30, 27), dimensions=Extent2I(1, 4)),
235 Box2I(corner=Point2I(30, 34), dimensions=Extent2I(1, 15))]
236 with defects.bulk_update():
237 for badBox in badPixels:
238 defects.append(badBox)
240 self.check_maskBlocks(defects, expectedDefects)
242 def test_maskBlocks_discontigous_less_than_thresholds(self):
243 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
245 Npix discontiguous bad pixels in a column where Npix <
246 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
247 goodPixelColumnGapThreshold (5). Under these conditions, the
248 expected defect boxes should be the same as the input boxes.
250 Plots can be found in DM-19903 on Jira.
252 """
254 expectedDefects = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
255 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
256 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
257 defects = self.allDefectsList
258 badPixels = [Box2I(corner=Point2I(35, 1), dimensions=Extent2I(1, 2)),
259 Box2I(corner=Point2I(35, 5), dimensions=Extent2I(1, 3)),
260 Box2I(corner=Point2I(35, 11), dimensions=Extent2I(1, 2))]
261 with defects.bulk_update():
262 for badBox in badPixels:
263 defects.append(badBox)
265 self.check_maskBlocks(defects, expectedDefects)
267 def test_maskBlocks_more_than_thresholds(self):
268 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
270 Npix discontiguous bad pixels in a column where Npix <
271 badOnAndOffPixelColumnThreshold (10) and gaps of good pixels <
272 goodPixelColumnGapThreshold (5). Npix=34 (> 10) bad pixels
273 total, 1 "good" gap with 13 pixels big enough (13 >= 5 good
274 pixels, from y=6 (1+5) to y=19).
276 Plots can be found in DM-19903 on Jira.
278 """
280 expectedDefects = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 7)),
281 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 30))]
282 defects = self.allDefectsList
283 badPixels = [Box2I(corner=Point2I(40, 1), dimensions=Extent2I(1, 2)),
284 Box2I(corner=Point2I(40, 5), dimensions=Extent2I(1, 3)),
285 Box2I(corner=Point2I(40, 19), dimensions=Extent2I(1, 5)),
286 Box2I(corner=Point2I(40, 27), dimensions=Extent2I(1, 4)),
287 Box2I(corner=Point2I(40, 34), dimensions=Extent2I(1, 15))]
288 with defects.bulk_update():
289 for badBox in badPixels:
290 defects.append(badBox)
292 self.check_maskBlocks(defects, expectedDefects)
294 def test_maskBlocks_not_enough_bad_pixels_in_column(self):
295 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
297 Npix discontiguous bad pixels in a column where Npix <
298 badOnAndOffPixelColumnThreshold (10) and and gaps of good
299 pixels > goodPixelColumnGapThreshold (5). Since Npix <
300 badOnAndOffPixelColumnThreshold, then it doesn't matter that
301 the number of good pixels in gap >
302 goodPixelColumnGapThreshold. 5<10 bad pixels total, 1 "good"
303 gap big enough (29>=5 good pixels, from y =12 (10+2) to y=30)
305 Plots can be found in DM-19903 on Jira.
307 """
309 expectedDefects = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
310 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
311 defects = self.allDefectsList
312 badPixels = [Box2I(corner=Point2I(45, 10), dimensions=Extent2I(1, 2)),
313 Box2I(corner=Point2I(45, 30), dimensions=Extent2I(1, 3))]
314 with defects.bulk_update():
315 for badBox in badPixels:
316 defects.append(badBox)
318 self.check_maskBlocks(defects, expectedDefects)
320 def test_maskBlocks_every_other_pixel_bad_greater_than_threshold(self):
321 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
323 Npix discontiguous bad pixels in a column where Npix >
324 badOnAndOffPixelColumnThreshold (10) and every other pixel is
325 bad.
327 Plots can be found in DM-19903 on Jira.
329 """
331 expectedDefects = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 31))]
332 defects = self.allDefectsList
333 badPixels = [Box2I(corner=Point2I(50, 10), dimensions=Extent2I(1, 1)),
334 Box2I(corner=Point2I(50, 12), dimensions=Extent2I(1, 1)),
335 Box2I(corner=Point2I(50, 14), dimensions=Extent2I(1, 1)),
336 Box2I(corner=Point2I(50, 16), dimensions=Extent2I(1, 1)),
337 Box2I(corner=Point2I(50, 18), dimensions=Extent2I(1, 1)),
338 Box2I(corner=Point2I(50, 20), dimensions=Extent2I(1, 1)),
339 Box2I(corner=Point2I(50, 22), dimensions=Extent2I(1, 1)),
340 Box2I(corner=Point2I(50, 24), dimensions=Extent2I(1, 1)),
341 Box2I(corner=Point2I(50, 26), dimensions=Extent2I(1, 1)),
342 Box2I(corner=Point2I(50, 28), dimensions=Extent2I(1, 1)),
343 Box2I(corner=Point2I(50, 30), dimensions=Extent2I(1, 1)),
344 Box2I(corner=Point2I(50, 32), dimensions=Extent2I(1, 1)),
345 Box2I(corner=Point2I(50, 34), dimensions=Extent2I(1, 1)),
346 Box2I(corner=Point2I(50, 36), dimensions=Extent2I(1, 1)),
347 Box2I(corner=Point2I(50, 38), dimensions=Extent2I(1, 1)),
348 Box2I(corner=Point2I(50, 40), dimensions=Extent2I(1, 1))]
349 with defects.bulk_update():
350 for badBox in badPixels:
351 defects.append(badBox)
353 self.check_maskBlocks(defects, expectedDefects)
355 def test_maskBlocks_every_other_pixel_bad_less_than_threshold(self):
356 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
358 Npix discontiguous bad pixels in a column where Npix >
359 badOnAndOffPixelColumnThreshold (10) and every other pixel is
360 bad.
362 Plots can be found in DM-19903 on Jira.
364 """
366 expectedDefects = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
367 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
368 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
369 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
370 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
371 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
372 defects = self.allDefectsList
373 badPixels = [Box2I(corner=Point2I(55, 20), dimensions=Extent2I(1, 1)),
374 Box2I(corner=Point2I(55, 22), dimensions=Extent2I(1, 1)),
375 Box2I(corner=Point2I(55, 24), dimensions=Extent2I(1, 1)),
376 Box2I(corner=Point2I(55, 26), dimensions=Extent2I(1, 1)),
377 Box2I(corner=Point2I(55, 28), dimensions=Extent2I(1, 1)),
378 Box2I(corner=Point2I(55, 30), dimensions=Extent2I(1, 1))]
379 with defects.bulk_update():
380 for badBox in badPixels:
381 defects.append(badBox)
383 self.check_maskBlocks(defects, expectedDefects)
385 def test_maskBlocks_blobs_one_side_good_less_than_threshold(self):
386 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
388 Npix discontiguous bad pixels in column with "blobs" of "m"
389 bad pixels to one side, m > badOnAndOffPixelColumnThreshold
390 (10), number of good pixel in gaps between blobs <
391 goodPixelColumnGapThreshold (5).
393 Plots can be found in DM-19903 on Jira.
395 """
397 expectedDefects = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 29)),
398 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 12))]
399 defects = self.allDefectsList
400 badPixels = [Box2I(corner=Point2I(60, 1), dimensions=Extent2I(1, 18)),
401 Box2I(corner=Point2I(60, 20), dimensions=Extent2I(1, 10)),
402 Box2I(corner=Point2I(61, 2), dimensions=Extent2I(2, 2)),
403 Box2I(corner=Point2I(61, 6), dimensions=Extent2I(2, 8))]
404 with defects.bulk_update():
405 for badBox in badPixels:
406 defects.append(badBox)
408 self.check_maskBlocks(defects, expectedDefects)
410 def test_maskBlocks_blobs_other_side_good_less_than_threshold(self):
411 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
413 Npix discontiguous bad pixels in column with "blobs" of "m"
414 bad pixels to the other side, m >
415 badOnAndOffPixelColumnThreshold (10), number of good pixel in
416 gaps between blobs < goodPixelColumnGapThreshold (5).
418 Plots can be found in DM-19903 on Jira.
420 """
422 expectedDefects = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 29)),
423 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 12))]
424 defects = self.allDefectsList
425 badPixels = [Box2I(corner=Point2I(70, 1), dimensions=Extent2I(1, 18)),
426 Box2I(corner=Point2I(70, 20), dimensions=Extent2I(1, 10)),
427 Box2I(corner=Point2I(68, 2), dimensions=Extent2I(2, 2)),
428 Box2I(corner=Point2I(68, 6), dimensions=Extent2I(2, 8))]
429 with defects.bulk_update():
430 for badBox in badPixels:
431 defects.append(badBox)
433 self.check_maskBlocks(defects, expectedDefects)
435 def test_maskBlocks_blob_both_sides_good_less_than_threshold(self):
436 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
438 Npix discontiguous bad pixels in column with "blobs" of "m"
439 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold
440 (10), number of good pixel in gaps between blobs <
441 goodPixelColumnGapThreshold (5).
443 Plots can be found in DM-19903 on Jira.
445 """
447 expectedDefects = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 29)),
448 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 12)),
449 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 12))]
450 defects = self.allDefectsList
451 badPixels = [Box2I(corner=Point2I(75, 1), dimensions=Extent2I(1, 18)),
452 Box2I(corner=Point2I(75, 20), dimensions=Extent2I(1, 10)),
453 Box2I(corner=Point2I(73, 2), dimensions=Extent2I(2, 2)),
454 Box2I(corner=Point2I(73, 6), dimensions=Extent2I(2, 8)),
455 Box2I(corner=Point2I(76, 2), dimensions=Extent2I(2, 2)),
456 Box2I(corner=Point2I(76, 6), dimensions=Extent2I(2, 8))]
457 with defects.bulk_update():
458 for badBox in badPixels:
459 defects.append(badBox)
461 self.check_maskBlocks(defects, expectedDefects)
463 def test_maskBlocks_blob_one_side_good_greater_than_threshold(self):
464 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
466 Npix discontiguous bad pixels in column with "blobs" of "m"
467 bad pixels to one side, m > badOnAndOffPixelColumnThreshold
468 (10), number of good pixel in gaps between blobs >
469 goodPixelColumnGapThreshold (5).
471 Plots can be found in DM-19903 on Jira.
473 """
475 expectedDefects = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 29)),
476 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
477 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
478 defects = self.allDefectsList
479 badPixels = [Box2I(corner=Point2I(80, 1), dimensions=Extent2I(1, 18)),
480 Box2I(corner=Point2I(80, 20), dimensions=Extent2I(1, 10)),
481 Box2I(corner=Point2I(81, 2), dimensions=Extent2I(2, 2)),
482 Box2I(corner=Point2I(81, 8), dimensions=Extent2I(2, 8))]
483 with defects.bulk_update():
484 for badBox in badPixels:
485 defects.append(badBox)
487 self.check_maskBlocks(defects, expectedDefects)
489 def test_maskBlocks_other_side_good_greater_than_threshold(self):
490 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
492 Npix discontiguous bad pixels in column with "blobs" of "m"
493 bad pixels to the other side, m >
494 badOnAndOffPixelColumnThreshold (10), number of good pixel in
495 gaps between blobs > goodPixelColumnGapThreshold (5).
497 Plots can be found in DM-19903 on Jira.
499 """
501 expectedDefects = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 29)),
502 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
503 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
504 defects = self.allDefectsList
505 badPixels = [Box2I(corner=Point2I(87, 1), dimensions=Extent2I(1, 18)),
506 Box2I(corner=Point2I(87, 20), dimensions=Extent2I(1, 10)),
507 Box2I(corner=Point2I(85, 2), dimensions=Extent2I(2, 2)),
508 Box2I(corner=Point2I(85, 8), dimensions=Extent2I(2, 8))]
509 with defects.bulk_update():
510 for badBox in badPixels:
511 defects.append(badBox)
513 self.check_maskBlocks(defects, expectedDefects)
515 def test_maskBlocks_both_sides_good_greater_than_threshold(self):
516 """A test for maskBlocksIfIntermitentBadPixelsInColumn.
518 Npix discontiguous bad pixels in column with "blobs" of "m"
519 bad pixels to both sides, m > badOnAndOffPixelColumnThreshold
520 (10), number of good pixel in gaps between blobs >
521 goodPixelColumnGapThreshold (5).
523 Plots can be found in DM-19903 on Jira.
525 """
527 expectedDefects = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 34)),
528 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 7)),
529 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 9)),
530 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 7)),
531 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 9))]
532 defects = self.allDefectsList
533 badPixels = [Box2I(corner=Point2I(93, 1), dimensions=Extent2I(1, 12)),
534 Box2I(corner=Point2I(93, 15), dimensions=Extent2I(1, 20)),
535 Box2I(corner=Point2I(91, 2), dimensions=Extent2I(2, 2)),
536 Box2I(corner=Point2I(91, 7), dimensions=Extent2I(2, 2)),
537 Box2I(corner=Point2I(94, 2), dimensions=Extent2I(2, 2)),
538 Box2I(corner=Point2I(94, 7), dimensions=Extent2I(2, 2)),
539 Box2I(corner=Point2I(91, 18), dimensions=Extent2I(2, 3)),
540 Box2I(corner=Point2I(91, 24), dimensions=Extent2I(2, 3)),
541 Box2I(corner=Point2I(94, 18), dimensions=Extent2I(2, 3)),
542 Box2I(corner=Point2I(94, 24), dimensions=Extent2I(2, 3))]
543 with defects.bulk_update():
544 for badBox in badPixels:
545 defects.append(badBox)
547 self.check_maskBlocks(defects, expectedDefects)
549 def test_maskBlocks_y_out_of_order_dm38103(self):
550 """A test for maskBlocksIfIntermitentBadPixelsInColumn, y out of order.
552 This test is a variant of
553 test_maskBlocks_every_other_pixel_bad_greater_than_threshold with
554 an extra out-of-y-order bad pixel to trigger DM-38103.
555 """
556 expectedDefects = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 31))]
557 defects = self.allDefectsList
558 badPixels = [Box2I(corner=Point2I(50, 110), dimensions=Extent2I(1, 1)),
559 Box2I(corner=Point2I(50, 112), dimensions=Extent2I(1, 1)),
560 Box2I(corner=Point2I(50, 114), dimensions=Extent2I(1, 1)),
561 Box2I(corner=Point2I(50, 116), dimensions=Extent2I(1, 1)),
562 Box2I(corner=Point2I(50, 118), dimensions=Extent2I(1, 1)),
563 Box2I(corner=Point2I(50, 120), dimensions=Extent2I(1, 1)),
564 Box2I(corner=Point2I(50, 122), dimensions=Extent2I(1, 1)),
565 Box2I(corner=Point2I(50, 124), dimensions=Extent2I(1, 1)),
566 Box2I(corner=Point2I(50, 126), dimensions=Extent2I(1, 1)),
567 Box2I(corner=Point2I(50, 128), dimensions=Extent2I(1, 1)),
568 Box2I(corner=Point2I(50, 130), dimensions=Extent2I(1, 1)),
569 Box2I(corner=Point2I(50, 132), dimensions=Extent2I(1, 1)),
570 Box2I(corner=Point2I(50, 134), dimensions=Extent2I(1, 1)),
571 Box2I(corner=Point2I(50, 136), dimensions=Extent2I(1, 1)),
572 Box2I(corner=Point2I(50, 138), dimensions=Extent2I(1, 1)),
573 Box2I(corner=Point2I(50, 140), dimensions=Extent2I(1, 1)),
574 # This last point is out of order in y.
575 Box2I(corner=Point2I(50, 100), dimensions=Extent2I(1, 1))]
577 # Force defect normalization off in order to trigger DM-38301, because
578 # defects.fromFootprintList() which is called by _findHotAndColdPixels
579 # does not do normalization.
580 defects._bulk_update = True
581 for badBox in badPixels:
582 defects.append(badBox)
583 defects._bulk_update = False
585 self.check_maskBlocks(defects, expectedDefects)
587 def check_maskBadColumns(self, exp, inputDefects, expectedDefects):
588 """A helper function for the tests of
589 maskBadColumns.
591 """
592 config = copy.copy(self.defaultConfig)
593 config.badPixelsToFillColumnThreshold = 25
594 config.saturatedPixelsToFillColumnThreshold = 5
596 task = self.defaultTask
597 task.config = config
599 defectsWithColumns, count = task.maskBadColumns(exp, inputDefects)
601 self.assertEqual(count, len(expectedDefects))
603 boxesMeasured = []
604 for defect in defectsWithColumns:
605 boxesMeasured.append(defect.getBBox())
607 for boxInput in expectedDefects:
608 self.assertIn(boxInput, boxesMeasured)
610 # Check that the code did not mask anything extra by
611 # looking in both the input list and "expanded-column" list.
612 unionInputExpectedBoxes = []
613 for defect in inputDefects:
614 unionInputExpectedBoxes.append(defect.getBBox())
615 for defect in expectedDefects:
616 unionInputExpectedBoxes.append(defect)
618 # Check that code doesn't mask more than it is supposed to.
619 for boxMeas in boxesMeasured:
620 self.assertIn(boxMeas, unionInputExpectedBoxes)
622 def test_maskBadColumns_extend_full_columns(self):
623 """Test maskBadColumns, extend to full column.
624 """
625 expectedDefects = [Box2I(corner=Point2I(20, 0), dimensions=Extent2I(1, 51)),
626 Box2I(corner=Point2I(150, 0), dimensions=Extent2I(1, 51)),
627 Box2I(corner=Point2I(50, 153), dimensions=Extent2I(1, 51))]
628 defects = self.allDefectsList
629 defects.append(Box2I(corner=Point2I(20, 10), dimensions=Extent2I(1, 30)))
630 defects.append(Box2I(corner=Point2I(150, 5), dimensions=Extent2I(1, 25)))
631 defects.append(Box2I(corner=Point2I(50, 170), dimensions=Extent2I(1, 30)))
633 self.check_maskBadColumns(self.flatExp, defects, expectedDefects)
635 def test_maskBadColumns_no_extend_partial_columns(self):
636 """Test maskBadColumns, do not extend to full column.
637 """
638 expectedDefects = []
639 defects = self.allDefectsList
640 defects.append(Box2I(corner=Point2I(20, 10), dimensions=Extent2I(1, 20)))
641 defects.append(Box2I(corner=Point2I(150, 5), dimensions=Extent2I(1, 22)))
642 defects.append(Box2I(corner=Point2I(50, 170), dimensions=Extent2I(1, 24)))
644 self.check_maskBadColumns(self.flatExp, defects, expectedDefects)
646 def test_maskBadColumns_extend_saturated_columns(self):
647 """Test maskBadColumns, extend saturation to full column.
648 """
649 exp = self.flatExp.clone()
651 mask = afwImage.Mask.getPlaneBitMask("SAT")
653 expectedDefects = [Box2I(corner=Point2I(20, 0), dimensions=Extent2I(1, 51)),
654 Box2I(corner=Point2I(150, 0), dimensions=Extent2I(1, 51)),
655 Box2I(corner=Point2I(50, 153), dimensions=Extent2I(1, 51))]
656 defects = self.allDefectsList
658 # These defects are too small to trigger the former column extension
659 # (as tested in test_maskBadColumns_no_extend_partial_columns) but
660 # should still trigger the saturation extension code.
661 satColumns = [Box2I(corner=Point2I(20, 10), dimensions=Extent2I(1, 5)),
662 Box2I(corner=Point2I(150, 5), dimensions=Extent2I(1, 5)),
663 Box2I(corner=Point2I(50, 170), dimensions=Extent2I(1, 5))]
664 for satColumn in satColumns:
665 exp.mask[satColumn] |= mask
666 defects.append(satColumn)
668 self.check_maskBadColumns(exp, defects, expectedDefects)
670 def check_dilateSaturatedColumns(self, exp, inputDefects, expectedDefects):
671 config = copy.copy(self.defaultConfig)
672 config.saturatedColumnDilationRadius = 2
674 task = self.defaultTask
675 task.config = config
677 defectsDilated = task.dilateSaturatedColumns(exp, inputDefects)
679 boxesMeasured = []
680 for defect in defectsDilated:
681 boxesMeasured.append(defect.getBBox())
683 for boxInput in expectedDefects:
684 self.assertIn(boxInput, boxesMeasured)
686 # Check that the code did not mask anything extra by
687 # looking in both the input list and "expanded-column" list.
688 unionInputExpectedBoxes = []
689 for defect in inputDefects:
690 unionInputExpectedBoxes.append(defect.getBBox())
691 for defect in expectedDefects:
692 unionInputExpectedBoxes.append(defect)
694 # Check that code doesn't mask more than it is supposed to.
695 for boxMeas in boxesMeasured:
696 self.assertIn(boxMeas, unionInputExpectedBoxes)
698 def test_dilateSaturatedColumns_saturated_column(self):
699 exp = self.flatExp.clone()
701 mask = afwImage.Mask.getPlaneBitMask("SAT")
703 # We include saturated defects hitting the side to ensure we do not
704 # have any overflow.
705 # The dilation radius is set to 2 pixels.
706 expectedDefects = [Box2I(corner=Point2I(20, 5), dimensions=Extent2I(5, 20)),
707 Box2I(corner=Point2I(197, 5), dimensions=Extent2I(3, 10)),
708 Box2I(corner=Point2I(0, 160), dimensions=Extent2I(4, 15))]
709 defects = self.allDefectsList
711 satColumns = [Box2I(corner=Point2I(22, 5), dimensions=Extent2I(1, 20)),
712 Box2I(corner=Point2I(199, 5), dimensions=Extent2I(1, 10)),
713 Box2I(corner=Point2I(1, 160), dimensions=Extent2I(1, 15))]
714 for satColumn in satColumns:
715 exp.mask[satColumn] |= mask
716 defects.append(satColumn)
718 self.check_dilateSaturatedColumns(exp, defects, expectedDefects)
720 def test_dilateSaturatedColumns_no_saturated_column(self):
721 exp = self.flatExp.clone()
723 # These are marked BAD but not saturated.
724 mask = afwImage.Mask.getPlaneBitMask("BAD")
726 expectedDefects = []
727 defects = self.allDefectsList
729 satColumns = [Box2I(corner=Point2I(22, 5), dimensions=Extent2I(1, 20)),
730 Box2I(corner=Point2I(199, 5), dimensions=Extent2I(1, 10)),
731 Box2I(corner=Point2I(1, 160), dimensions=Extent2I(1, 15))]
732 for satColumn in satColumns:
733 exp.mask[satColumn] |= mask
734 defects.append(satColumn)
736 self.check_dilateSaturatedColumns(exp, defects, expectedDefects)
738 def test_defectVampirePixels(self):
739 config = copy.copy(self.defaultConfig)
740 # We set configs to mask vampire pixels
741 config.doVampirePixels = True
742 # We choose small radius to get a only 3 bbox to check
743 config.radiusVampirePixels = 1
744 # This is the threshold type that is actually used
745 # for defects search in flats
746 config.thresholdType = 'VALUE'
747 # Flat mock has higher pixel values than real flatBootstrap
748 config.thresholdVampirePixels = 2550.
750 task = self.defaultTask
751 task.config = config
753 exp = self.flatExp.clone()
755 # We set one bright pixel to test the vampire pixel masking
756 yVampirePixel = 130
757 xVampirePixel = 50
758 exp.image.array[yVampirePixel, xVampirePixel] = 2600.
760 # Find usual hot and pixels as well as the bright defect
761 defects = task._findHotAndColdPixels(exp)
763 # Make bright defect BBox
764 vampireBBox = [Box2I(corner=Point2I(50, 129), dimensions=Extent2I(1, 3)),
765 Box2I(corner=Point2I(49, 130), dimensions=Extent2I(1, 1)),
766 Box2I(corner=Point2I(51, 130), dimensions=Extent2I(1, 1))]
767 # Test that the BBox is within the defects measured
768 boxesMeasured = []
769 for defect in defects:
770 boxesMeasured.append(defect.getBBox())
771 for expectedBBox in vampireBBox:
772 self.assertIn(expectedBBox, boxesMeasured)
774 def test_defectFindingAllSensor(self):
775 config = copy.copy(self.defaultConfig)
776 config.nPixBorderLeftRight = 0
777 config.nPixBorderUpDown = 0
779 task = self.defaultTask
780 task.config = config
782 defects = task._findHotAndColdPixels(self.flatExp)
784 allBBoxes = self.darkBBoxes + self.brightBBoxes
786 boxesMeasured = []
787 for defect in defects:
788 boxesMeasured.append(defect.getBBox())
790 for expectedBBox in allBBoxes:
791 self.assertIn(expectedBBox, boxesMeasured)
793 def test_defectFindingEdgeIgnore(self):
794 config = copy.copy(self.defaultConfig)
795 config.nPixBorderUpDown = 0
796 config.nPixBorderLeftRight = 7
797 task = self.defaultTask
798 task.config = config
799 defects = task._findHotAndColdPixels(self.flatExp)
801 shouldBeFound = self.darkBBoxes[self.noEdges] + self.brightBBoxes[self.noEdges]
803 boxesMeasured = []
804 for defect in defects:
805 boxesMeasured.append(defect.getBBox())
807 for expectedBBox in shouldBeFound:
808 self.assertIn(expectedBBox, boxesMeasured)
810 shouldBeMissed = self.darkBBoxes[self.onlyEdges] + self.brightBBoxes[self.onlyEdges]
811 for boxMissed in shouldBeMissed:
812 self.assertNotIn(boxMissed, boxesMeasured)
814 def valueThreshold(self, fileType, saturateAmpInFlat=False):
815 """Helper function to loop over flats and darks
816 to test thresholdType = 'VALUE'."""
817 config = copy.copy(self.defaultConfig)
818 config.thresholdType = 'VALUE'
819 task = self.defaultTask
820 task.config = config
822 for amp in self.flatExp.getDetector():
823 if amp.getName() == 'C:0,0':
824 regionC00 = amp.getBBox()
826 if fileType == 'dark':
827 exp = self.darkExp
828 shouldBeFound = self.brightBBoxes[self.noEdges]
829 else:
830 exp = self.flatExp
831 if saturateAmpInFlat:
832 exp.maskedImage[regionC00].image.array[:] = 0.0
833 # Amp C:0,0: minimum=(0, 0), maximum=(99, 50)
834 x = self.defaultConfig.nPixBorderUpDown
835 y = self.defaultConfig.nPixBorderLeftRight
836 width, height = regionC00.getEndX() - x, regionC00.getEndY() - y
837 # Defects code will mark whole saturated amp as defect box.
838 shouldBeFound = [Box2I(corner=Point2I(x, y), dimensions=Extent2I(width, height))]
839 else:
840 shouldBeFound = self.darkBBoxes[self.noEdges]
841 # Change the default a bit so it works for the
842 # existing simulated defects.
843 task.config.fracThresholdFlat = 0.9
845 defects = task._findHotAndColdPixels(exp)
847 boxesMeasured = []
848 for defect in defects:
849 boxesMeasured.append(defect.getBBox())
851 for expectedBBox in shouldBeFound:
852 self.assertIn(expectedBBox, boxesMeasured)
854 def test_valueThreshold(self):
855 for fileType in ['dark', 'flat']:
856 self.valueThreshold(fileType)
857 # stdDev = 0.0
858 self.valueThreshold('flat', saturateAmpInFlat=True)
860 def test_pixelCounting(self):
861 """Test that the number of defective pixels identified is as expected.
862 """
863 config = copy.copy(self.defaultConfig)
864 config.nPixBorderUpDown = 0
865 config.nPixBorderLeftRight = 0
866 task = self.defaultTask
867 task.config = config
868 defects = task._findHotAndColdPixels(self.flatExp)
870 defectArea = 0
871 for defect in defects:
872 defectArea += defect.getBBox().getArea()
874 # The columnar code will cover blocks of a column with
875 # on-and-off pixels, thus creating more bad pixels that what
876 # initially placed in self.brightDefects and self.darkDefects.
877 # Thus, defectArea should be >= crossCheck.
878 crossCheck = 0
879 for x, y, sx, sy in self.brightDefects:
880 crossCheck += sx*sy
881 for x, y, sx, sy in self.darkDefects:
882 crossCheck += sx*sy
884 # Test the result of _nPixFromDefects()
885 # via two different ways of calculating area.
886 self.assertEqual(defectArea, task._nPixFromDefects(defects))
887 # defectArea should be >= crossCheck
888 self.assertGreaterEqual(defectArea, crossCheck)
890 def test_getNumGoodPixels(self):
891 """Test the the number of pixels in the image not masked is as
892 expected.
893 """
894 testImage = self.flatExp.clone()
895 mi = testImage.maskedImage
897 imageSize = testImage.getBBox().getArea()
898 nGood = self.defaultTask._getNumGoodPixels(mi)
900 self.assertEqual(imageSize, nGood)
902 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
904 noDataBox = Box2I(Point2I(31, 49), Extent2I(3, 6))
905 testImage.mask[noDataBox] |= NODATABIT
907 self.assertEqual(imageSize - noDataBox.getArea(), self.defaultTask._getNumGoodPixels(mi))
908 # check for misfire; we're setting NO_DATA here, not BAD
909 self.assertEqual(imageSize, self.defaultTask._getNumGoodPixels(mi, 'BAD'))
911 testImage.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
912 self.assertEqual(imageSize, nGood)
914 BADBIT = mi.mask.getPlaneBitMask("BAD")
915 badBox = Box2I(Point2I(85, 98), Extent2I(4, 7))
916 testImage.mask[badBox] |= BADBIT
918 self.assertEqual(imageSize - badBox.getArea(), self.defaultTask._getNumGoodPixels(mi, 'BAD'))
920 def test_edgeMasking(self):
921 """Check that the right number of edge pixels are masked by
922 _setEdgeBits().
923 """
924 testImage = self.flatExp.clone()
925 mi = testImage.maskedImage
927 self.assertEqual(countMaskedPixels(mi, 'EDGE'), 0)
928 self.defaultTask._setEdgeBits(mi)
930 hEdge = self.defaultConfig.nPixBorderLeftRight
931 vEdge = self.defaultConfig.nPixBorderUpDown
932 xSize, ySize = mi.getDimensions()
934 nEdge = xSize*vEdge*2 + ySize*hEdge*2 - hEdge*vEdge*4
936 self.assertEqual(countMaskedPixels(mi, 'EDGE'), nEdge)
938 def test_badImage(self):
939 """Check that fully-bad images do not fail.
940 """
941 testImage = self.flatExp.clone()
942 testImage.image.array[:, :] = 125000
944 config = copy.copy(self.defaultConfig)
945 # Do not exclude any pixels, so the areas match.
946 config.nPixBorderUpDown = 0
947 config.nPixBorderLeftRight = 0
949 task = self.defaultTask
950 task.config = config
951 defects = task._findHotAndColdPixels(testImage)
953 defectArea = 0
954 for defect in defects:
955 defectArea += defect.getBBox().getArea()
956 self.assertEqual(defectArea, testImage.getBBox().getArea())
958 def test_e2vMidline(self):
959 """Test handling of E2V midline."""
961 class MockE2VAmp:
962 def __init__(self, name, bbox):
963 self._name = name
964 self._bbox = bbox
966 def getName(self):
967 return self._name
969 def getBBox(self):
970 return self._bbox
972 def __repr__(self):
973 return f"MockE2VAmp({self._name})"
975 class MockE2VDetector(list):
976 def __init__(self):
977 amps = []
978 for i in range(8):
979 name = f"C1{i}"
980 bbox = Box2I(corner=Point2I(i*512, 2002), dimensions=Extent2I(512, 2002))
981 amps.append(MockE2VAmp(name, bbox))
982 for i in reversed(range(8)):
983 name = f"C0{i}"
984 bbox = Box2I(corner=Point2I(i*512, 0), dimensions=Extent2I(512, 2002))
985 amps.append(MockE2VAmp(name, bbox))
987 super().__init__(amps)
989 def getBBox(self):
990 return Box2I(corner=Point2I(0, 0), dimensions=Extent2I(4096, 4004))
992 def getPhysicalType(self):
993 return "E2V"
995 class MockE2VExposure(afwImage.ExposureF):
996 def __init__(self, *args, **kwargs):
997 super().__init__(*args, **kwargs)
999 def setDetector(self, detector):
1000 self._detector = detector
1002 def getDetector(self):
1003 return self._detector
1005 detector = MockE2VDetector()
1006 flat = MockE2VExposure(detector.getBBox())
1007 flat.setDetector(detector)
1008 flat.metadata["IMGTYPE"] = "FLAT"
1009 visitInfo = afwImage.VisitInfo(exposureTime=10.0, darkTime=10.0)
1010 flat.info.setVisitInfo(visitInfo)
1012 flat.mask.array[:, :] = 0
1014 rng = np.random.RandomState(0)
1016 # Make a flat, and make a very dark midline.
1017 flat.image.array[:, :] = rng.normal(1.0, 0.01, flat.image.array.shape)
1018 flat.image.array[2001, :] = 0.0
1019 flat.image.array[2002, :] = 0.0
1021 # First test that with midline checking off it is masked.
1022 config = cpPipe.MeasureDefectsTask.ConfigClass()
1023 config.thresholdType = "VALUE"
1024 config.fracThresholdFlat = 0.9
1025 config.e2vMidlineBreakNRow = 0
1026 task = cpPipe.MeasureDefectsTask(config=config)
1028 defects = task._findHotAndColdPixels(flat)
1030 # There should be 16 defects (one for each amp).
1031 self.assertEqual(len(defects), len(detector))
1033 flat.mask.array[:, :] = 0
1034 for defect in defects:
1035 flat.mask[defect.getBBox()].array |= 1
1037 np.testing.assert_array_equal(flat.mask.array[2001, :], 1)
1038 np.testing.assert_array_equal(flat.mask.array[2002, :], 1)
1040 # Test again *ignoring* the midline.
1041 config = cpPipe.MeasureDefectsTask.ConfigClass()
1042 config.thresholdType = "VALUE"
1043 config.fracThresholdFlat = 0.9
1044 config.e2vMidlineBreakNRow = 1
1045 task = cpPipe.MeasureDefectsTask(config=config)
1047 flat.mask.array[:, :] = 0
1048 defects = task._findHotAndColdPixels(flat)
1050 self.assertEqual(len(defects), 0)
1052 # Test again *requiring* the midline mask.
1053 config = cpPipe.MeasureDefectsTask.ConfigClass()
1054 config.thresholdType = "VALUE"
1055 config.fracThresholdFlat = 0.9
1056 config.e2vMidlineBreakNRow = 1
1057 config.e2vMidlineBreakOption = "MASK"
1058 task = cpPipe.MeasureDefectsTask(config=config)
1060 flat.mask.array[:, :] = 0
1061 flat.image.array[:, :] = rng.normal(1.0, 0.01, flat.image.array.shape)
1063 defects = task._findHotAndColdPixels(flat)
1065 # There should be 16 defects (one for each amp).
1066 self.assertEqual(len(defects), len(detector))
1068 flat.mask.array[:, :] = 0
1069 for defect in defects:
1070 flat.mask[defect.getBBox()].array |= 1
1072 np.testing.assert_array_equal(flat.mask.array[2001, :], 1)
1073 np.testing.assert_array_equal(flat.mask.array[2002, :], 1)
1075 def test_flatGradient(self):
1076 """Test handling of a strong gradient."""
1077 mock = ipIsr.IsrMockLSST()
1078 camera = mock.getCamera()
1079 detector = camera[10]
1081 # Create a flat with a strong gradient.
1082 flat = afwImage.ExposureF(detector.getBBox())
1083 flat.setDetector(detector)
1085 xx = np.arange(flat.image.array.shape[1], dtype=np.float64)
1086 yy = np.arange(flat.image.array.shape[0], dtype=np.float64)
1087 x, y = np.meshgrid(xx, yy)
1088 x = x.ravel()
1089 y = y.ravel()
1091 transform = detector.getTransform(lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE)
1092 xy = np.vstack((x, y))
1093 xf, yf = np.vsplit(transform.getMapping().applyForward(xy), 2)
1094 xf = xf.ravel()
1095 yf = yf.ravel()
1097 radius = np.sqrt(xf**2. + yf**2.)
1098 radialNodes = np.linspace(radius.min(), radius.max(), 4)
1099 radialValues = [1.0, 0.8, 0.5, 0.3]
1100 spl = Akima1DInterpolator(radialNodes, radialValues, method="akima")
1102 value = spl(np.clip(radius, radialNodes[0], radialNodes[-1]))
1103 flat.image.array[:, :] = value.reshape(flat.image.array.shape)
1105 visitInfo = afwImage.VisitInfo(exposureTime=10.0, darkTime=10.0)
1106 flat.info.setVisitInfo(visitInfo)
1108 # Change all the gains.
1109 rng = np.random.RandomState(seed=12345)
1110 gains = rng.uniform(low=1.5, high=2.5, size=len(detector))
1112 for i, amp in enumerate(detector):
1113 flat[amp.getBBox()].image.array /= gains[i]
1115 # Look for defects with default settings; there should be some
1116 # (which we don't want).
1118 config = cpPipe.MeasureDefectsTask.ConfigClass()
1119 config.thresholdType = "VALUE"
1120 config.fracThresholdFlat = 0.9
1122 task1 = cpPipe.MeasureDefectsTask(config=config)
1123 defects1 = task1.run(flat, camera).outputDefects
1125 # There are almost 200 defects here, but we're just checking that
1126 # there are some false defects.
1127 self.assertGreater(len(defects1), 10)
1129 # Turn on amp gradient fitting with special configs for small
1130 # test amplifiers.
1131 config.fitAmpGradient = True
1132 config.ampGradientBinFactor = 2
1133 config.ampGradientBoundarySize = 0
1135 task2 = cpPipe.MeasureDefectsTask(config=config)
1136 defects2 = task2.run(flat, camera).outputDefects
1138 # Check that there are no false defects.
1139 self.assertEqual(len(defects2), 0)
1142class TestMemory(lsst.utils.tests.MemoryTestCase):
1143 pass
1146def setup_module(module):
1147 lsst.utils.tests.init()
1150if __name__ == "__main__": 1150 ↛ 1151line 1150 didn't jump to line 1151 because the condition on line 1150 was never true
1151 lsst.utils.tests.init()
1152 unittest.main()