Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3# 

4# LSST Data Management System 

5# 

6# Copyright 2008-2017 AURA/LSST. 

7# 

8# This product includes software developed by the 

9# LSST Project (http://www.lsst.org/). 

10# 

11# This program is free software: you can redistribute it and/or modify 

12# it under the terms of the GNU General Public License as published by 

13# the Free Software Foundation, either version 3 of the License, or 

14# (at your option) any later version. 

15# 

16# This program is distributed in the hope that it will be useful, 

17# but WITHOUT ANY WARRANTY; without even the implied warranty of 

18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19# GNU General Public License for more details. 

20# 

21# You should have received a copy of the LSST License Statement and 

22# the GNU General Public License along with this program. If not, 

23# see <https://www.lsstcorp.org/LegalNotices/>. 

24# 

25"""Test cases for cp_pipe.""" 

26 

27from __future__ import absolute_import, division, print_function 

28import unittest 

29import numpy as np 

30import copy 

31 

32import lsst.utils 

33import lsst.utils.tests 

34 

35import lsst.cp.pipe as cpPipe 

36import lsst.ip.isr.isrMock as isrMock 

37from lsst.cp.pipe.ptc import PhotonTransferCurveDataset 

38 

39 

40class MeasurePhotonTransferCurveTaskTestCase(lsst.utils.tests.TestCase): 

41 """A test case for the PTC task.""" 

42 

43 def setUp(self): 

44 self.defaultConfig = cpPipe.ptc.MeasurePhotonTransferCurveTask.ConfigClass() 

45 self.defaultConfig.isr.doFlat = False 

46 self.defaultConfig.isr.doFringe = False 

47 self.defaultConfig.isr.doCrosstalk = False 

48 self.defaultConfig.isr.doAddDistortionModel = False 

49 self.defaultConfig.isr.doUseOpticsTransmission = False 

50 self.defaultConfig.isr.doUseFilterTransmission = False 

51 self.defaultConfig.isr.doUseSensorTransmission = False 

52 self.defaultConfig.isr.doUseAtmosphereTransmission = False 

53 self.defaultConfig.isr.doAttachTransmissionCurve = False 

54 

55 self.defaultTask = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=self.defaultConfig) 

56 

57 self.flatMean = 2000 

58 self.readNoiseAdu = 10 

59 mockImageConfig = isrMock.IsrMock.ConfigClass() 

60 

61 # flatDrop is not really relevant as we replace the data 

62 # but good to note it in case we change how this image is made 

63 mockImageConfig.flatDrop = 0.99999 

64 mockImageConfig.isTrimmed = True 

65 

66 self.flatExp1 = isrMock.FlatMock(config=mockImageConfig).run() 

67 self.flatExp2 = self.flatExp1.clone() 

68 (shapeY, shapeX) = self.flatExp1.getDimensions() 

69 

70 self.flatWidth = np.sqrt(self.flatMean) + self.readNoiseAdu 

71 

72 self.rng1 = np.random.RandomState(1984) 

73 flatData1 = self.rng1.normal(self.flatMean, self.flatWidth, (shapeX, shapeY)) 

74 self.rng2 = np.random.RandomState(666) 

75 flatData2 = self.rng2.normal(self.flatMean, self.flatWidth, (shapeX, shapeY)) 

76 

77 self.flatExp1.image.array[:] = flatData1 

78 self.flatExp2.image.array[:] = flatData2 

79 

80 # create fake PTC data to see if fit works, for one amp ('amp') 

81 flux = 1000 # ADU/sec 

82 timeVec = np.arange(1., 201.) 

83 muVec = flux*timeVec # implies that signal-chain non-linearity is zero 

84 self.gain = 1.5 # e-/ADU 

85 self.c1 = 1./self.gain 

86 self.noiseSq = 5*self.gain # 7.5 (e-)^2 

87 self.a00 = -1.2e-6 

88 self.c2 = -1.5e-6 

89 self.c3 = -4.7e-12 # tuned so that it turns over for 200k mean 

90 

91 self.ampNames = [amp.getName() for amp in self.flatExp1.getDetector().getAmplifiers()] 

92 self.dataset = PhotonTransferCurveDataset(self.ampNames) # pack raw data for fitting 

93 

94 for ampName in self.ampNames: # just the expTimes and means here - vars vary per function 

95 self.dataset.rawExpTimes[ampName] = timeVec 

96 self.dataset.rawMeans[ampName] = muVec 

97 

98 def test_ptcFitQuad(self): 

99 localDataset = copy.copy(self.dataset) 

100 for ampName in self.ampNames: 

101 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 for 

102 mu in localDataset.rawMeans[ampName]] 

103 

104 config = copy.copy(self.defaultConfig) 

105 config.polynomialFitDegree = 2 

106 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config) 

107 

108 task.fitPtcAndNonLinearity(localDataset, ptcFitType='POLYNOMIAL') 

109 

110 for ampName in self.ampNames: 

111 self.assertAlmostEqual(self.gain, localDataset.gain[ampName]) 

112 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName]) 

113 # Linearity residual should be zero 

114 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0) 

115 

116 def test_ptcFitCubic(self): 

117 localDataset = copy.copy(self.dataset) 

118 for ampName in self.ampNames: 

119 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 + self.c3*mu**3 for 

120 mu in localDataset.rawMeans[ampName]] 

121 

122 config = copy.copy(self.defaultConfig) 

123 config.polynomialFitDegree = 3 

124 

125 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config) 

126 

127 task.fitPtcAndNonLinearity(localDataset, ptcFitType='POLYNOMIAL') 

128 

129 for ampName in self.ampNames: 

130 self.assertAlmostEqual(self.gain, localDataset.gain[ampName]) 

131 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName]) 

132 # Linearity residual should be zero 

133 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0) 

134 

135 def test_ptcFitAstier(self): 

136 localDataset = copy.copy(self.dataset) 

137 g = self.gain # next line is too long without this shorthand! 

138 for ampName in self.ampNames: 

139 localDataset.rawVars[ampName] = [(0.5/(self.a00*g**2)*(np.exp(2*self.a00*mu*g)-1) + 

140 self.noiseSq/(g*g)) for mu in localDataset.rawMeans[ampName]] 

141 

142 config = copy.copy(self.defaultConfig) 

143 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config) 

144 

145 task.fitPtcAndNonLinearity(localDataset, ptcFitType='ASTIERAPPROXIMATION') 

146 

147 for ampName in self.ampNames: 

148 self.assertAlmostEqual(self.gain, localDataset.gain[ampName]) 

149 # noise already comes out of the fit in electrons with Astier 

150 self.assertAlmostEqual(np.sqrt(self.noiseSq), localDataset.noise[ampName]) 

151 # Linearity residual should be zero 

152 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0) 

153 

154 def test_meanVarMeasurement(self): 

155 task = self.defaultTask 

156 mu, varDiff = task.measureMeanVarPair(self.flatExp1, self.flatExp2) 

157 

158 self.assertLess(self.flatWidth - np.sqrt(varDiff), 1) 

159 self.assertLess(self.flatMean - mu, 1) 

160 

161 def test_getInitialGoodPoints(self): 

162 xs = [1, 2, 3, 4, 5, 6] 

163 ys = [2*x for x in xs] 

164 points = self.defaultTask._getInitialGoodPoints(xs, ys, 0.1, 0.25) 

165 assert np.all(points) == np.all(np.array([True for x in xs])) 

166 

167 ys[-1] = 30 

168 points = self.defaultTask._getInitialGoodPoints(xs, ys, 0.1, 0.25) 

169 assert np.all(points) == np.all(np.array([True, True, True, True, False])) 

170 

171 ys = [2*x for x in xs] 

172 newYs = copy.copy(ys) 

173 results = [False, True, True, False, False] 

174 for i, factor in enumerate([-0.5, -0.1, 0, 0.1, 0.5]): 

175 newYs[-1] = ys[-1] + (factor*ys[-1]) 

176 points = self.defaultTask._getInitialGoodPoints(xs, newYs, 0.05, 0.25) 

177 assert (np.all(points[0:-1]) == True) # noqa: E712 - flake8 is wrong here because of numpy.bool 

178 assert points[-1] == results[i] 

179 

180 def test_getVisitsUsed(self): 

181 localDataset = copy.copy(self.dataset) 

182 

183 for pair in [(12, 34), (56, 78), (90, 10)]: 

184 localDataset.inputVisitPairs["C:0,0"].append(pair) 

185 localDataset.visitMask["C:0,0"] = np.array([True, False, True]) 

186 self.assertTrue(np.all(localDataset.getVisitsUsed("C:0,0") == [(12, 34), (90, 10)])) 

187 

188 localDataset.visitMask["C:0,0"] = np.array([True, False, True, True]) # wrong length now 

189 with self.assertRaises(AssertionError): 

190 localDataset.getVisitsUsed("C:0,0") 

191 

192 def test_getGoodAmps(self): 

193 dataset = self.dataset 

194 

195 self.assertTrue(dataset.ampNames == self.ampNames) 

196 dataset.badAmps.append("C:0,1") 

197 self.assertTrue(dataset.getGoodAmps() == [amp for amp in self.ampNames if amp != "C:0,1"]) 

198 

199 

200class MeasurePhotonTransferCurveDatasetTestCase(lsst.utils.tests.TestCase): 

201 def setUp(self): 

202 self.ptcData = PhotonTransferCurveDataset(['C00', 'C01']) 

203 self.ptcData.inputVisitPairs = {'C00': [(123, 234), (345, 456), (567, 678)], 

204 'C01': [(123, 234), (345, 456), (567, 678)]} 

205 

206 def test_generalBehaviour(self): 

207 test = PhotonTransferCurveDataset(['C00', 'C01']) 

208 test.inputVisitPairs = {'C00': [(123, 234), (345, 456), (567, 678)], 

209 'C01': [(123, 234), (345, 456), (567, 678)]} 

210 

211 with self.assertRaises(AttributeError): 

212 test.newItem = 1 

213 

214 

215class TestMemory(lsst.utils.tests.MemoryTestCase): 

216 pass 

217 

218 

219def setup_module(module): 

220 lsst.utils.tests.init() 

221 

222 

223if __name__ == "__main__": 223 ↛ 224line 223 didn't jump to line 224, because the condition on line 223 was never true

224 lsst.utils.tests.init() 

225 unittest.main()