Coverage for tests/test_deferredCharge.py: 17%

110 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-13 01:55 -0800

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 

25 

26from lsst.ip.isr import (DeferredChargeCalib, DeferredChargeTask, SerialTrap) 

27 

28 

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] 

38 

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) 

43 

44 signals = np.full((self.ny, self.nx + self.prescan), 100.0) 

45 signals[:, 0:self.prescan] = 0.0 

46 

47 freeCharge = np.full((self.ny, self.nx + self.prescan), 100.0) 

48 freeCharge[:, 0:self.prescan] = 0.0 

49 

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)) 

55 

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) 

60 

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) 

65 

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 

70 

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) 

76 

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) 

81 

82 

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() 

94 

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 

115 

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) 

120 

121 def testPartiallyPopulated(self): 

122 # Do IO tests. 

123 # First, try not populating 'amp1' 

124 del self.calib.serialTraps['amp1'] 

125 

126 outPath = tempfile.mktemp() + '.yaml' 

127 self.calib.writeText(outPath) 

128 newCalib = DeferredChargeCalib().readText(outPath) 

129 self.assertEqual(self.calib, newCalib) 

130 

131 outPath = tempfile.mktemp() + '.fits' 

132 self.calib.writeFits(outPath) 

133 newCalib = DeferredChargeCalib().readFits(outPath) 

134 self.assertEqual(self.calib, newCalib) 

135 

136 # Repopulate it 

137 self.calib.serialTraps['amp1'] = SerialTrap(self.trapSize, self.trapDecay, 

138 self.trapPixel, 'logistic', self.trapCoeffs) 

139 

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) 

146 

147 outPath = tempfile.mktemp() + '.fits' 

148 self.calib.writeFits(outPath) 

149 newCalib = DeferredChargeCalib().readFits(outPath) 

150 self.assertEqual(self.calib, newCalib) 

151 

152 def testTaskMethods(self): 

153 task = DeferredChargeTask() 

154 

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 

158 

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) 

164 

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) 

170 

171 

172class MemoryTester(lsst.utils.tests.MemoryTestCase): 

173 pass 

174 

175 

176def setup_module(module): 

177 lsst.utils.tests.init() 

178 

179 

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()