Coverage for tests / test_overscanAmpConfig.py: 12%
148 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 09:10 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 09:10 +0000
1#
2# LSST Data Management System
3# Copyright 2023 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#
23import copy
24import math
25import tempfile
26import unittest
28import lsst.utils.tests
29import lsst.afw.cameraGeom as cameraGeom
31from lsst.ip.isr.overscanAmpConfig import (
32 OverscanAmpConfig,
33 OverscanDetectorConfig,
34 OverscanCameraConfig,
35)
38class OverscanAmpConfigTestCase(lsst.utils.tests.TestCase):
39 def _makeCamera(self):
40 self.detectors = {
41 "detector0": (0, "0010"),
42 "detector1": (1, "0011"),
43 "detector2": (2, "0012"),
44 }
45 self.amps = ["amp0", "amp1", "amp2", "amp3"]
47 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera")
48 for detector in self.detectors:
49 detectorBuilder = cameraBuilder.add(detector, self.detectors[detector][0])
50 detectorBuilder.setSerial(self.detectors[detector][1])
52 for amp in self.amps:
53 ampBuilder = cameraGeom.Amplifier.Builder()
54 ampBuilder.setName(amp)
55 detectorBuilder.append(ampBuilder)
57 return cameraBuilder.finish()
59 def _serializeAndReadConfig(self, configIn):
60 # This bit of code serializes and reads the config
61 # to ensure that everything here works as expected
62 # with the nested dictionaries of configs.
64 with tempfile.NamedTemporaryFile(suffix=".py") as f:
65 configIn.save(f.name)
66 configOut = OverscanCameraConfig()
67 configOut.load(f.name)
69 return configOut
71 def _checkOverscanConfig(
72 self,
73 overscanAmpConfig,
74 doSerialOverscan=True,
75 doParallelOverscan=True,
76 serialFitType="MEDIAN_PER_ROW",
77 parallelFitType="MEDIAN_PER_ROW",
78 saturation=float("NaN"),
79 gain=float("NaN"),
80 suspectLevel=float("NaN"),
81 ):
82 self.assertEqual(overscanAmpConfig.doSerialOverscan, doSerialOverscan)
83 self.assertEqual(overscanAmpConfig.doParallelOverscan, doParallelOverscan)
84 self.assertEqual(overscanAmpConfig.serialOverscanConfig.fitType, serialFitType)
85 self.assertEqual(overscanAmpConfig.parallelOverscanConfig.fitType, parallelFitType)
86 if math.isnan(saturation):
87 self.assertTrue(math.isnan(overscanAmpConfig.saturation))
88 else:
89 self.assertEqual(overscanAmpConfig.saturation, saturation)
90 if math.isnan(gain):
91 self.assertTrue(math.isnan(overscanAmpConfig.gain))
92 else:
93 self.assertEqual(overscanAmpConfig.gain, gain)
94 if math.isnan(suspectLevel):
95 self.assertTrue(math.isnan(overscanAmpConfig.suspectLevel))
96 else:
97 self.assertEqual(overscanAmpConfig.suspectLevel, suspectLevel)
99 def _checkAnyOverscanConfig(
100 self,
101 config,
102 doSerialOverscan=True,
103 doParallelOverscan=True,
104 ):
105 self.assertEqual(config.doAnySerialOverscan, doSerialOverscan)
106 self.assertEqual(config.doAnyParallelOverscan, doParallelOverscan)
108 def _checkDetectorOverscanConfig(
109 self,
110 overscanDetectorConfig,
111 integerDitherMode="SYMMETRIC",
112 ):
113 self.assertEqual(overscanDetectorConfig.integerDitherMode, integerDitherMode)
115 def testAmpConfigNoOverrides(self):
116 camera = self._makeCamera()
118 config = OverscanCameraConfig()
120 config = self._serializeAndReadConfig(config)
122 for detector in camera:
123 for amp in detector:
124 detectorConfig = config.getOverscanDetectorConfig(detector)
125 ampConfig = detectorConfig.getOverscanAmpConfig(amp)
126 self._checkOverscanConfig(ampConfig)
128 self._checkAnyOverscanConfig(config)
130 def testAmpConfigOverrideDetectorDefault(self):
131 camera = self._makeCamera()
133 overscanAmpConfig = OverscanAmpConfig(
134 doSerialOverscan=False,
135 doParallelOverscan=False,
136 saturation=100_000.0,
137 gain=1.7,
138 suspectLevel=90_000.0,
139 )
141 overscanDetectorConfig = OverscanDetectorConfig(defaultAmpConfig=overscanAmpConfig)
143 config = OverscanCameraConfig(defaultDetectorConfig=overscanDetectorConfig)
145 config = self._serializeAndReadConfig(config)
147 for detector in camera:
148 for amp in detector:
149 detectorConfig = config.getOverscanDetectorConfig(detector)
150 overscanAmpConfig = detectorConfig.getOverscanAmpConfig(amp)
151 self._checkOverscanConfig(
152 overscanAmpConfig,
153 doSerialOverscan=False,
154 doParallelOverscan=False,
155 saturation=100_000.0,
156 gain=1.7,
157 suspectLevel=90_000.0,
158 )
160 self._checkAnyOverscanConfig(
161 config,
162 doSerialOverscan=False,
163 doParallelOverscan=False,
164 )
166 def testAmpConfigOverrideDetectorDefaultWithOneAmp(self):
167 camera = self._makeCamera()
169 overscanAmpConfigOverride = OverscanAmpConfig()
170 overscanAmpConfigOverride.parallelOverscanConfig.fitType = "MEDIAN"
172 overscanDetectorConfig = OverscanDetectorConfig()
173 overscanDetectorConfig.ampRules["amp2"] = overscanAmpConfigOverride
175 config = OverscanCameraConfig(defaultDetectorConfig=overscanDetectorConfig)
177 config = self._serializeAndReadConfig(config)
179 for detector in camera:
180 for amp in detector:
181 detectorConfig = config.getOverscanDetectorConfig(detector)
182 ampConfig = detectorConfig.getOverscanAmpConfig(amp)
183 if amp.getName() == "amp2":
184 self._checkOverscanConfig(ampConfig, parallelFitType="MEDIAN")
185 else:
186 self._checkOverscanConfig(ampConfig)
188 self._checkAnyOverscanConfig(config)
190 def testAmpConfigOverrideOneDetector(self):
191 camera = self._makeCamera()
193 overscanAmpConfigOverride = OverscanAmpConfig(doParallelOverscan=False)
194 overscanDetectorConfigOverride = OverscanDetectorConfig(
195 defaultAmpConfig=overscanAmpConfigOverride,
196 integerDitherMode="NONE",
197 )
199 for keyType in ["NAME", "SERIAL", "ID"]:
200 config = OverscanCameraConfig()
202 match keyType:
203 case "NAME":
204 key = camera[1].getName()
205 case "SERIAL":
206 key = camera[1].getSerial()
207 case "ID":
208 key = str(camera[1].getId())
210 config.detectorRuleKeyType = keyType
211 config.detectorRules[key] = overscanDetectorConfigOverride
213 config = self._serializeAndReadConfig(config)
215 for detector in camera:
216 detectorConfig = config.getOverscanDetectorConfig(detector)
217 if detector.getName() == camera[1].getName():
218 self._checkDetectorOverscanConfig(detectorConfig, integerDitherMode="NONE")
219 else:
220 self._checkDetectorOverscanConfig(detectorConfig)
222 for amp in detector:
223 ampConfig = detectorConfig.getOverscanAmpConfig(amp)
224 if detector.getName() == camera[1].getName():
225 self._checkOverscanConfig(ampConfig, doParallelOverscan=False)
226 else:
227 self._checkOverscanConfig(ampConfig)
229 self._checkAnyOverscanConfig(config)
231 def testAmpConfigOverrideOneDetectorOneAmp(self):
232 camera = self._makeCamera()
234 overscanAmpConfigOverride = OverscanAmpConfig()
235 overscanAmpConfigOverride.serialOverscanConfig.fitType = "MEDIAN"
236 overscanDetectorConfigOverride = OverscanDetectorConfig()
237 overscanDetectorConfigOverride.ampRules["amp3"] = overscanAmpConfigOverride
239 config = OverscanCameraConfig()
240 config.detectorRules["detector0"] = overscanDetectorConfigOverride
242 config = self._serializeAndReadConfig(config)
244 for detector in camera:
245 for amp in detector:
246 detectorConfig = config.getOverscanDetectorConfig(detector)
247 ampConfig = detectorConfig.getOverscanAmpConfig(amp)
248 if detector.getName() == "detector0" and amp.getName() == "amp3":
249 self._checkOverscanConfig(ampConfig, serialFitType="MEDIAN")
250 else:
251 self._checkOverscanConfig(ampConfig)
253 self._checkAnyOverscanConfig(config)
255 def testAmpConfigMd5(self):
256 # Check a default detectorConfig
257 detectorConfig1 = OverscanDetectorConfig()
258 configMd51 = detectorConfig1.md5
260 # Make sure copying it has the same hash.
261 detectorConfig2 = copy.copy(detectorConfig1)
262 configMd52 = detectorConfig2.md5
264 self.assertEqual(configMd51, configMd52)
266 # Make a new one with an amp override.
267 overscanAmpConfigOverride = OverscanAmpConfig()
268 overscanAmpConfigOverride.parallelOverscanConfig.fitType = "MEDIAN"
270 detectorConfig3 = OverscanDetectorConfig()
271 detectorConfig3.ampRules["amp2"] = overscanAmpConfigOverride
273 self.assertNotEqual(detectorConfig3.md5, detectorConfig1.md5)
275 # Override another amp but with the default. This should
276 # give the same answer because amp overrides that match the default
277 # are not hashed.
278 detectorConfig4 = copy.copy(detectorConfig3)
279 detectorConfig4.ampRules["amp3"] = OverscanAmpConfig()
281 self.assertEqual(detectorConfig4.md5, detectorConfig3.md5)
284class MemoryTester(lsst.utils.tests.MemoryTestCase):
285 pass
288def setup_module(module):
289 lsst.utils.tests.init()
292if __name__ == "__main__": 292 ↛ 293line 292 didn't jump to line 293 because the condition on line 292 was never true
293 lsst.utils.tests.init()
294 unittest.main()