Coverage for tests / test_deferredCharge.py: 17%
112 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:58 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:58 +0000
1# This file is part of ip_isr.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
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 GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21import unittest
22import numpy as np
23import tempfile
24import lsst.utils.tests
26from lsst.ip.isr import (DeferredChargeCalib, DeferredChargeTask, SerialTrap)
29class SerialTrapTestCase(lsst.utils.tests.TestCase):
30 def setUp(self):
31 self.nx = 64
32 self.ny = 128
33 self.prescan = 8
34 self.trapSize = 10.0
35 self.trapDecay = 3.0
36 self.trapPixel = self.nx + self.prescan - 3
37 self.trapCoeffs = [5.0, 1e-3]
39 def testTrapsLinear(self):
40 trap = SerialTrap(self.trapSize, self.trapDecay,
41 self.trapPixel, 'linear', self.trapCoeffs)
42 trap.initialize(self.ny, self.nx, self.prescan)
44 signals = np.full((self.ny, self.nx + self.prescan), 100.0)
45 signals[:, 0:self.prescan] = 0.0
47 freeCharge = np.full((self.ny, self.nx + self.prescan), 100.0)
48 freeCharge[:, 0:self.prescan] = 0.0
50 # Captured charge is of size trapSize, stored in column
51 # trapPixel, and introduced to all rows.
52 capturedCharge = trap.trap_charge(signals)
53 self.assertEqual(capturedCharge.shape, (self.ny, self.nx + self.prescan))
54 self.assertEqual(np.sum(capturedCharge), self.trapSize * (self.ny))
56 # Released charge is the amount released after one decay.
57 releasedCharge = trap.release_charge()
58 self.assertEqual(releasedCharge.shape, (self.ny, self.nx + self.prescan))
59 self.assertAlmostEqual(np.sum(releasedCharge), 362.839922, 4)
61 def testTrapsLogistic(self):
62 trap = SerialTrap(self.trapSize, self.trapDecay,
63 self.trapPixel, 'logistic', self.trapCoeffs)
64 trap.initialize(self.ny, self.nx, self.prescan)
66 signals = np.full((self.ny, self.nx + self.prescan), 100.0)
67 freeCharge = np.full((self.ny, self.nx + self.prescan), 100.0)
68 signals[:, 0:self.prescan] = 0.0
69 freeCharge[:, 0:self.prescan] = 0.0
71 # Captured charge is of size trapSize, stored in column
72 # trapPixel, and introduced to all rows.
73 capturedCharge = trap.trap_charge(signals)
74 self.assertEqual(capturedCharge.shape, (self.ny, self.nx + self.prescan))
75 self.assertAlmostEqual(np.sum(capturedCharge), 5.23732154 * (self.ny), 4)
77 # Released charge is the amount released after one decay.
78 releasedCharge = trap.release_charge()
79 self.assertEqual(releasedCharge.shape, (self.ny, self.nx + self.prescan))
80 self.assertAlmostEqual(np.sum(releasedCharge), 190.030934, 4)
83class DeferredChargeTestCase(lsst.utils.tests.TestCase):
84 def setUp(self):
85 self.nx = 64
86 self.ny = 128
87 self.prescan = 8
88 self.overscan = 16
89 self.trapSize = 10.0
90 self.trapDecay = 3.0
91 self.trapPixel = self.nx//2 + self.prescan
92 self.trapCoeffs = [5.0, 1e-3]
93 self.calib = DeferredChargeCalib()
95 self.calib.inputGain['amp0'] = 1.5
96 self.calib.inputGain['amp1'] = 1.5
97 self.calib.driftScale['amp0'] = 1.8e-4
98 self.calib.driftScale['amp1'] = 2.8e-4
99 self.calib.decayTime['amp0'] = 3.1
100 self.calib.decayTime['amp1'] = 3.2
101 self.calib.globalCti['amp0'] = 1.4e-7
102 self.calib.globalCti['amp1'] = 1.5e-7
103 self.calib.signals['amp0'] = np.linspace(1.0e2, 1.0e5, 10)
104 self.calib.signals['amp1'] = np.linspace(1.0e2, 1.0e5, 10)
105 self.calib.serialEper['amp0'] = np.full_like(self.calib.signals['amp0'], 2.0e-6)
106 self.calib.serialEper['amp1'] = np.full_like(self.calib.signals['amp1'], 2.0e-6)
107 self.calib.parallelEper['amp0'] = np.full_like(self.calib.signals['amp0'], 1.0e-6)
108 self.calib.parallelEper['amp1'] = np.full_like(self.calib.signals['amp1'], 1.0e-6)
109 self.calib.serialCtiTurnoff['amp0'] = 1.0e5
110 self.calib.serialCtiTurnoff['amp1'] = 1.0e5
111 self.calib.parallelCtiTurnoff['amp0'] = 1.0e5
112 self.calib.parallelCtiTurnoff['amp1'] = 1.0e5
113 self.calib.serialCtiTurnoffSamplingErr['amp0'] = 1.0e3
114 self.calib.serialCtiTurnoffSamplingErr['amp1'] = 1.0e3
115 self.calib.parallelCtiTurnoffSamplingErr['amp0'] = 1.0e3
116 self.calib.parallelCtiTurnoffSamplingErr['amp1'] = 1.0e3
118 self.calib.serialTraps['amp0'] = SerialTrap(self.trapSize, self.trapDecay,
119 self.trapPixel, 'linear', self.trapCoeffs)
120 self.calib.serialTraps['amp1'] = SerialTrap(self.trapSize, self.trapDecay,
121 self.trapPixel, 'logistic', self.trapCoeffs)
123 def testPartiallyPopulated(self):
124 # Do IO tests.
125 # First, try not populating 'amp1'
126 del self.calib.serialTraps['amp1']
128 outPath = tempfile.mktemp() + '.yaml'
129 self.calib.writeText(outPath)
130 newCalib = DeferredChargeCalib().readText(outPath)
131 self.assertEqual(self.calib, newCalib)
133 outPath = tempfile.mktemp() + '.fits'
134 self.calib.writeFits(outPath)
135 newCalib = DeferredChargeCalib().readFits(outPath)
136 self.assertEqual(self.calib, newCalib)
138 # Repopulate it
139 self.calib.serialTraps['amp1'] = SerialTrap(self.trapSize, self.trapDecay,
140 self.trapPixel, 'logistic', self.trapCoeffs)
142 def testFullyPopulated(self):
143 # Do IO tests.
144 outPath = tempfile.mktemp() + '.yaml'
145 self.calib.writeText(outPath)
146 newCalib = DeferredChargeCalib().readText(outPath)
147 self.assertEqual(self.calib, newCalib)
149 outPath = tempfile.mktemp() + '.fits'
150 self.calib.writeFits(outPath)
151 newCalib = DeferredChargeCalib().readFits(outPath)
152 self.assertEqual(self.calib, newCalib)
154 def testTaskMethods(self):
155 task = DeferredChargeTask()
157 image = np.full((self.ny, self.nx + self.prescan + self.overscan), 100.0)
158 image[:, 0:self.prescan] = 0.0
159 image[:, -self.overscan:] = 1.0
161 corrected = task.local_offset_inverse(image, self.calib.driftScale['amp0'],
162 self.calib.decayTime['amp0'],
163 num_previous_pixels=15)
164 # 64*128*100 + 16*64 * ~(1 - driftScale)
165 self.assertAlmostEqual(np.sum(corrected), 821094.5118501, 5)
167 corrected = task.local_trap_inverse(corrected, self.calib.serialTraps['amp0'],
168 self.calib.globalCti['amp0'],
169 num_previous_pixels=6)
170 # As above + ~320 deposited in prescan
171 self.assertAlmostEqual(np.sum(corrected), 821734.3965605417, 5)
174class MemoryTester(lsst.utils.tests.MemoryTestCase):
175 pass
178def setup_module(module):
179 lsst.utils.tests.init()
182if __name__ == "__main__": 182 ↛ 183line 182 didn't jump to line 183 because the condition on line 182 was never true
183 import sys
184 setup_module(sys.modules[__name__])
185 unittest.main()