Coverage for tests/test_deferredCharge.py: 17%
110 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-14 02:03 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-14 02:03 -0700
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.driftScale['amp0'] = 1.8e-4
96 self.calib.driftScale['amp1'] = 2.8e-4
97 self.calib.decayTime['amp0'] = 3.1
98 self.calib.decayTime['amp1'] = 3.2
99 self.calib.globalCti['amp0'] = 1.4e-7
100 self.calib.globalCti['amp1'] = 1.5e-7
101 self.calib.signals['amp0'] = np.linspace(1.0e2, 1.0e5, 10)
102 self.calib.signals['amp1'] = np.linspace(1.0e2, 1.0e5, 10)
103 self.calib.serialEper['amp0'] = np.full_like(self.calib.signals['amp0'], 2.0e-6)
104 self.calib.serialEper['amp1'] = np.full_like(self.calib.signals['amp1'], 2.0e-6)
105 self.calib.parallelEper['amp0'] = np.full_like(self.calib.signals['amp0'], 1.0e-6)
106 self.calib.parallelEper['amp1'] = np.full_like(self.calib.signals['amp1'], 1.0e-6)
107 self.calib.serialCtiTurnoff['amp0'] = 1.0e5
108 self.calib.serialCtiTurnoff['amp1'] = 1.0e5
109 self.calib.parallelCtiTurnoff['amp0'] = 1.0e5
110 self.calib.parallelCtiTurnoff['amp1'] = 1.0e5
111 self.calib.serialCtiTurnoffSamplingErr['amp0'] = 1.0e3
112 self.calib.serialCtiTurnoffSamplingErr['amp1'] = 1.0e3
113 self.calib.parallelCtiTurnoffSamplingErr['amp0'] = 1.0e3
114 self.calib.parallelCtiTurnoffSamplingErr['amp1'] = 1.0e3
116 self.calib.serialTraps['amp0'] = SerialTrap(self.trapSize, self.trapDecay,
117 self.trapPixel, 'linear', self.trapCoeffs)
118 self.calib.serialTraps['amp1'] = SerialTrap(self.trapSize, self.trapDecay,
119 self.trapPixel, 'logistic', self.trapCoeffs)
121 def testPartiallyPopulated(self):
122 # Do IO tests.
123 # First, try not populating 'amp1'
124 del self.calib.serialTraps['amp1']
126 outPath = tempfile.mktemp() + '.yaml'
127 self.calib.writeText(outPath)
128 newCalib = DeferredChargeCalib().readText(outPath)
129 self.assertEqual(self.calib, newCalib)
131 outPath = tempfile.mktemp() + '.fits'
132 self.calib.writeFits(outPath)
133 newCalib = DeferredChargeCalib().readFits(outPath)
134 self.assertEqual(self.calib, newCalib)
136 # Repopulate it
137 self.calib.serialTraps['amp1'] = SerialTrap(self.trapSize, self.trapDecay,
138 self.trapPixel, 'logistic', self.trapCoeffs)
140 def testFullyPopulated(self):
141 # Do IO tests.
142 outPath = tempfile.mktemp() + '.yaml'
143 self.calib.writeText(outPath)
144 newCalib = DeferredChargeCalib().readText(outPath)
145 self.assertEqual(self.calib, newCalib)
147 outPath = tempfile.mktemp() + '.fits'
148 self.calib.writeFits(outPath)
149 newCalib = DeferredChargeCalib().readFits(outPath)
150 self.assertEqual(self.calib, newCalib)
152 def testTaskMethods(self):
153 task = DeferredChargeTask()
155 image = np.full((self.ny, self.nx + self.prescan + self.overscan), 100.0)
156 image[:, 0:self.prescan] = 0.0
157 image[:, -self.overscan:] = 1.0
159 corrected = task.local_offset_inverse(image, self.calib.driftScale['amp0'],
160 self.calib.decayTime['amp0'],
161 num_previous_pixels=15)
162 # 64*128*100 + 16*64 * ~(1 - driftScale)
163 self.assertAlmostEqual(np.sum(corrected), 821094.5118501, 5)
165 corrected = task.local_trap_inverse(corrected, self.calib.serialTraps['amp0'],
166 self.calib.globalCti['amp0'],
167 num_previous_pixels=6)
168 # As above + ~320 deposited in prescan
169 self.assertAlmostEqual(np.sum(corrected), 821734.3965605417, 5)
172class MemoryTester(lsst.utils.tests.MemoryTestCase):
173 pass
176def setup_module(module):
177 lsst.utils.tests.init()
180if __name__ == "__main__": 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true
181 import sys
182 setup_module(sys.modules[__name__])
183 unittest.main()