Coverage for tests / test_empiricalVariance.py: 17%
103 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-25 08:29 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-25 08:29 +0000
1#
2# LSST Data Management System
3# Copyright 2018 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22import numpy as np
23import unittest
25import lsst.utils.tests
27from lsst.daf.base import PropertyList
28import lsst.afw.cameraGeom as cameraGeom
29from lsst.geom import Point2I, Extent2I, Box2I, Extent2D
30from lsst.afw.image import ExposureF, VisitInfo
32from lsst.ip.isr.isrTask import IsrTask
35def makeAmplifier(name, bbox, rawImageBox, overscanBox, gain, readNoise, saturation):
36 amp = cameraGeom.Amplifier.Builder()
37 amp.setName(name)
38 amp.setBBox(bbox)
39 amp.setRawDataBBox(rawImageBox)
40 amp.setRawHorizontalOverscanBBox(overscanBox)
41 amp.setGain(gain)
42 amp.setReadNoise(readNoise)
43 amp.setSaturation(saturation)
44 amp.setSuspectLevel(np.nan)
45 return amp
48class EmpiricalVarianceTestCast(lsst.utils.tests.TestCase):
49 def setUp(self):
50 """Constructs a CCD with two amplifiers and prepares for ISR"""
51 rng = np.random.Generator(np.random.MT19937(42))
53 baseValue = 100.0
54 gain = 1.0
55 readNoise = 123456789.0
56 saturation = 987654321.0
57 height = 234
58 imageSize = Extent2I(123, height)
59 overscanSize = Extent2I(16, height)
60 self.sigma = 1.234
62 # Set up the various regions
63 overscan1 = Box2I(Point2I(0, 0), overscanSize)
64 image1 = Box2I(Point2I(overscanSize[0], 0), imageSize)
65 image2 = Box2I(Point2I(overscanSize[0] + imageSize[0], 0), imageSize)
66 overscan2 = Box2I(Point2I(overscanSize[0] + 2*imageSize[0], 0), overscanSize)
68 leftBox = Box2I(overscan1.getMin(), Extent2I(overscan1.getWidth() + image1.getWidth(), height))
69 rightBox = Box2I(image2.getMin(), Extent2I(image2.getWidth() + overscan2.getWidth(), height))
71 target1 = Box2I(Point2I(0, 0), imageSize)
72 target2 = Box2I(Point2I(image1.getWidth(), 0), imageSize)
74 # Set the pixels
75 exposure = ExposureF(Box2I(Point2I(0, 0), Extent2I(imageSize[0]*2 + overscanSize[0]*2, height)))
76 yy = np.arange(0, height, 1, dtype=np.float32)
77 leftImage = ExposureF(exposure, leftBox)
78 leftImage.image.array[:] = baseValue + yy[:, np.newaxis]
79 rightImage = ExposureF(exposure, rightBox)
80 rightImage.image.array[:] = baseValue - yy[:, np.newaxis]
82 leftOverscan = ExposureF(exposure, overscan1)
83 leftOverscan.image.array += rng.normal(0.0, self.sigma, leftOverscan.image.array.shape)
84 rightOverscan = ExposureF(exposure, overscan2)
85 rightOverscan.image.array += rng.normal(0.0, self.sigma, leftOverscan.image.array.shape)
86 exposure.mask.array[:] = 0.0
87 exposure.variance.array[:] = np.nan
89 # Construct the detectors
90 amp1 = makeAmplifier("left", target1, image1, overscan1, gain, readNoise, saturation)
91 amp2 = makeAmplifier("right", target2, image2, overscan2, gain, readNoise, saturation)
92 ccdBox = Box2I(Point2I(0, 0), Extent2I(image1.getWidth() + image2.getWidth(), height))
93 camBuilder = cameraGeom.Camera.Builder("fakeCam")
94 detBuilder = camBuilder.add("detector", 1)
95 detBuilder.setSerial("det1")
96 detBuilder.setBBox(ccdBox)
97 detBuilder.setPixelSize(Extent2D(1.0, 1.0))
98 detBuilder.setOrientation(cameraGeom.Orientation())
99 detBuilder.append(amp1)
100 detBuilder.append(amp2)
101 cam = camBuilder.finish()
102 exposure.setDetector(cam.get('detector'))
104 header = PropertyList()
105 header.add("EXPTIME", 0.0)
106 exposure.getInfo().setVisitInfo(VisitInfo(header))
108 self.exposure = exposure
109 self.config = IsrTask.ConfigClass()
111 # Disable everything we don't care about
112 self.config.doBias = False
113 self.config.doDark = False
114 self.config.doFlat = False
115 self.config.doFringe = False
116 self.config.doDefect = False
117 self.config.doWrite = False
118 self.config.expectWcs = False
119 self.config.doLinearize = False
120 self.config.doCrosstalk = False
121 self.config.doBrighterFatter = False
122 self.config.doAttachTransmissionCurve = False
123 self.config.doAssembleCcd = False
124 self.config.doNanMasking = False
125 self.config.doInterpolate = False
127 self.config.maskNegativeVariance = False # This runs on mocks.
128 # Set the things that match our test setup
129 self.config.overscan.fitType = "CHEB"
130 self.config.overscan.order = 1
131 self.config.doEmpiricalReadNoise = True
133 self.task = IsrTask(config=self.config)
135 def tearDown(self):
136 del self.exposure
138 def testEmpiricalVariance(self):
139 results = self.task.run(self.exposure)
140 postIsr = results.exposure
142 self.assertFloatsEqual(postIsr.mask.array, 0)
143 # Image is not exactly zero because the noise in the overscan (required
144 # to be able to set the empirical variance) leads to a slight
145 # misestimate in the polynomial fit.
146 self.assertFloatsAlmostEqual(np.median(postIsr.image.array), 0.0, atol=5.0e-2)
147 self.assertFloatsAlmostEqual(np.nanmedian(postIsr.variance.array), self.sigma**2, rtol=5.0e-2)
150class MemoryTester(lsst.utils.tests.MemoryTestCase):
151 pass
154def setup_module(module):
155 lsst.utils.tests.init()
158if __name__ == "__main__": 158 ↛ 159line 158 didn't jump to line 159 because the condition on line 158 was never true
159 import sys
160 setup_module(sys.modules[__name__])
161 unittest.main()