Coverage for tests / test_deferredCharge.py: 17%

112 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-25 08:29 +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 

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

117 

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) 

122 

123 def testPartiallyPopulated(self): 

124 # Do IO tests. 

125 # First, try not populating 'amp1' 

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

127 

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

129 self.calib.writeText(outPath) 

130 newCalib = DeferredChargeCalib().readText(outPath) 

131 self.assertEqual(self.calib, newCalib) 

132 

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

134 self.calib.writeFits(outPath) 

135 newCalib = DeferredChargeCalib().readFits(outPath) 

136 self.assertEqual(self.calib, newCalib) 

137 

138 # Repopulate it 

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

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

141 

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) 

148 

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

150 self.calib.writeFits(outPath) 

151 newCalib = DeferredChargeCalib().readFits(outPath) 

152 self.assertEqual(self.calib, newCalib) 

153 

154 def testTaskMethods(self): 

155 task = DeferredChargeTask() 

156 

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 

160 

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) 

166 

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) 

172 

173 

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

175 pass 

176 

177 

178def setup_module(module): 

179 lsst.utils.tests.init() 

180 

181 

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