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 numberAmps = len(self.ampNames) 

109 numberAduValues = config.maxAduForLookupTableLinearizer 

110 lookupTableArray = np.zeros((numberAmps, numberAduValues), dtype=np.float32) 

111 

112 task.fitPtcAndNonLinearity(localDataset, lookupTableArray, ptcFitType='POLYNOMIAL') 

113 

114 for ampName in self.ampNames: 

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

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

117 # Linearity residual should be zero 

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

119 

120 def test_ptcFitCubic(self): 

121 localDataset = copy.copy(self.dataset) 

122 for ampName in self.ampNames: 

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

124 mu in localDataset.rawMeans[ampName]] 

125 

126 config = copy.copy(self.defaultConfig) 

127 config.polynomialFitDegree = 3 

128 

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

130 

131 numberAmps = len(self.ampNames) 

132 numberAduValues = config.maxAduForLookupTableLinearizer 

133 lookupTableArray = np.zeros((numberAmps, numberAduValues), dtype=np.float32) 

134 

135 task.fitPtcAndNonLinearity(localDataset, lookupTableArray, ptcFitType='POLYNOMIAL') 

136 

137 for ampName in self.ampNames: 

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

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

140 # Linearity residual should be zero 

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

142 

143 def test_ptcFitAstier(self): 

144 localDataset = copy.copy(self.dataset) 

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

146 for ampName in self.ampNames: 

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

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

149 

150 config = copy.copy(self.defaultConfig) 

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

152 

153 numberAmps = len(self.ampNames) 

154 numberAduValues = config.maxAduForLookupTableLinearizer 

155 lookupTableArray = np.zeros((numberAmps, numberAduValues), dtype=np.float32) 

156 

157 task.fitPtcAndNonLinearity(localDataset, lookupTableArray, ptcFitType='ASTIERAPPROXIMATION') 

158 

159 for ampName in self.ampNames: 

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

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

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

163 # Linearity residual should be zero 

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

165 

166 def test_meanVarMeasurement(self): 

167 task = self.defaultTask 

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

169 

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

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

172 

173 def test_getInitialGoodPoints(self): 

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

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

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

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

178 

179 ys[-1] = 30 

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

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

182 

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

184 newYs = copy.copy(ys) 

185 results = [False, True, True, False, False] 

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

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

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

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

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

191 

192 def test_getVisitsUsed(self): 

193 localDataset = copy.copy(self.dataset) 

194 

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

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

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

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

199 

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

201 with self.assertRaises(AssertionError): 

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

203 

204 def test_getGoodAmps(self): 

205 dataset = self.dataset 

206 

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

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

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

210 

211 

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

213 def setUp(self): 

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

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

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

217 

218 def test_generalBehaviour(self): 

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

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

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

222 

223 with self.assertRaises(AttributeError): 

224 test.newItem = 1 

225 

226 

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

228 pass 

229 

230 

231def setup_module(module): 

232 lsst.utils.tests.init() 

233 

234 

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

236 lsst.utils.tests.init() 

237 unittest.main()