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.ip.isr import PhotonTransferCurveDataset 

38from lsst.cp.pipe.utils import (funcPolynomial, makeMockFlats) 

39 

40 

41class FakeCamera(list): 

42 def getName(self): 

43 return "FakeCam" 

44 

45 

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

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

48 

49 def setUp(self): 

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

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

52 

53 self.defaultConfigExtract = cpPipe.ptc.PhotonTransferCurveExtractTask.ConfigClass() 

54 self.defaultTaskExtract = cpPipe.ptc.PhotonTransferCurveExtractTask(config=self.defaultConfigExtract) 

55 

56 self.defaultConfigSolve = cpPipe.ptc.PhotonTransferCurveSolveTask.ConfigClass() 

57 self.defaultTaskSolve = cpPipe.ptc.PhotonTransferCurveSolveTask(config=self.defaultConfigSolve) 

58 

59 self.flatMean = 2000 

60 self.readNoiseAdu = 10 

61 mockImageConfig = isrMock.IsrMock.ConfigClass() 

62 

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

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

65 mockImageConfig.flatDrop = 0.99999 

66 mockImageConfig.isTrimmed = True 

67 

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

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

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

71 

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

73 

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

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

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

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

78 

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

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

81 

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

83 self.flux = 1000. # ADU/sec 

84 self.timeVec = np.arange(1., 101.) 

85 self.k2NonLinearity = -5e-6 

86 # quadratic signal-chain non-linearity 

87 muVec = self.flux*self.timeVec + self.k2NonLinearity*self.timeVec**2 

88 self.gain = 1.5 # e-/ADU 

89 self.c1 = 1./self.gain 

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

91 self.a00 = -1.2e-6 

92 self.c2 = -1.5e-6 

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

94 

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

96 self.dataset = PhotonTransferCurveDataset(self.ampNames, " ") # pack raw data for fitting 

97 

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

99 self.dataset.rawExpTimes[ampName] = self.timeVec 

100 self.dataset.rawMeans[ampName] = muVec 

101 

102 def test_covAstier(self): 

103 """Test to check getCovariancesAstier 

104 

105 We check that the gain is the same as the imput gain from the mock data, that 

106 the covariances via FFT (as it is in MeasurePhotonTransferCurveTask when 

107 doCovariancesAstier=True) are the same as calculated in real space, and that 

108 Cov[0, 0] (i.e., the variances) are similar to the variances calculated with the standard 

109 method (when doCovariancesAstier=false), 

110 """ 

111 task = self.defaultTask 

112 extractConfig = self.defaultConfigExtract 

113 extractConfig.minNumberGoodPixelsForCovariance = 5000 

114 extractConfig.detectorMeasurementRegion = 'FULL' 

115 extractTask = cpPipe.ptc.PhotonTransferCurveExtractTask(config=extractConfig) 

116 

117 solveConfig = self.defaultConfigSolve 

118 solveConfig.ptcFitType = 'FULLCOVARIANCE' 

119 solveTask = cpPipe.ptc.PhotonTransferCurveSolveTask(config=solveConfig) 

120 

121 inputGain = 0.75 

122 

123 muStandard, varStandard = {}, {} 

124 expDict = {} 

125 expIds = [] 

126 idCounter = 0 

127 for expTime in self.timeVec: 

128 mockExp1, mockExp2 = makeMockFlats(expTime, gain=inputGain, 

129 readNoiseElectrons=3, expId1=idCounter, 

130 expId2=idCounter+1) 

131 expDict[expTime] = (mockExp1, mockExp2) 

132 expIds.append(idCounter) 

133 expIds.append(idCounter+1) 

134 for ampNumber, ampName in enumerate(self.ampNames): 

135 # cov has (i, j, var, cov, npix) 

136 muDiff, varDiff, covAstier = task.extract.measureMeanVarCov(mockExp1, mockExp2) 

137 muStandard.setdefault(ampName, []).append(muDiff) 

138 varStandard.setdefault(ampName, []).append(varDiff) 

139 # Calculate covariances in an independent way: direct space 

140 _, _, covsDirect = task.extract.measureMeanVarCov(mockExp1, mockExp2, covAstierRealSpace=True) 

141 

142 # Test that the arrays "covs" (FFT) and "covDirect" (direct space) are the same 

143 for row1, row2 in zip(covAstier, covsDirect): 

144 for a, b in zip(row1, row2): 

145 self.assertAlmostEqual(a, b) 

146 idCounter += 2 

147 resultsExtract = extractTask.run(inputExp=expDict, inputDims=expIds) 

148 resultsSolve = solveTask.run(resultsExtract.outputCovariances) 

149 

150 for amp in self.ampNames: 

151 self.assertAlmostEqual(resultsSolve.outputPtcDataset.gain[amp], inputGain, places=2) 

152 for v1, v2 in zip(varStandard[amp], resultsSolve.outputPtcDataset.finalVars[amp]): 

153 self.assertAlmostEqual(v1/v2, 1.0, places=1) 

154 

155 def ptcFitAndCheckPtc(self, order=None, fitType=None, doTableArray=False, doFitBootstrap=False): 

156 localDataset = copy.copy(self.dataset) 

157 localDataset.ptcFitType = fitType 

158 configSolve = copy.copy(self.defaultConfigSolve) 

159 configLin = cpPipe.linearity.LinearitySolveTask.ConfigClass() 

160 placesTests = 6 

161 if doFitBootstrap: 

162 configSolve.doFitBootstrap = True 

163 # Bootstrap method in cp_pipe/utils.py does multiple fits in the precense of noise. 

164 # Allow for more margin of error. 

165 placesTests = 3 

166 

167 if fitType == 'POLYNOMIAL': 

168 if order not in [2, 3]: 

169 RuntimeError("Enter a valid polynomial order for this test: 2 or 3") 

170 if order == 2: 

171 for ampName in self.ampNames: 

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

173 mu in localDataset.rawMeans[ampName]] 

174 configSolve.polynomialFitDegree = 2 

175 if order == 3: 

176 for ampName in self.ampNames: 

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

178 for mu in localDataset.rawMeans[ampName]] 

179 configSolve.polynomialFitDegree = 3 

180 elif fitType == 'EXPAPPROXIMATION': 

181 g = self.gain 

182 for ampName in self.ampNames: 

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

184 + self.noiseSq/(g*g)) 

185 for mu in localDataset.rawMeans[ampName]] 

186 else: 

187 RuntimeError("Enter a fit function type: 'POLYNOMIAL' or 'EXPAPPROXIMATION'") 

188 

189 configLin.maxLookupTableAdu = 200000 # Max ADU in input mock flats 

190 configLin.maxLinearAdu = 100000 

191 configLin.minLinearAdu = 50000 

192 if doTableArray: 

193 configLin.linearityType = "LookupTable" 

194 else: 

195 configLin.linearityType = "Polynomial" 

196 solveTask = cpPipe.ptc.PhotonTransferCurveSolveTask(config=configSolve) 

197 linearityTask = cpPipe.linearity.LinearitySolveTask(config=configLin) 

198 

199 if doTableArray: 

200 # Non-linearity 

201 numberAmps = len(self.ampNames) 

202 # localDataset: PTC dataset (`lsst.ip.isr.ptcDataset.PhotonTransferCurveDataset`) 

203 localDataset = solveTask.fitPtc(localDataset) 

204 # linDataset here is a lsst.pipe.base.Struct 

205 linDataset = linearityTask.run(localDataset, 

206 camera=FakeCamera([self.flatExp1.getDetector()]), 

207 inputDims={'detector': 0}) 

208 linDataset = linDataset.outputLinearizer 

209 else: 

210 localDataset = solveTask.fitPtc(localDataset) 

211 linDataset = linearityTask.run(localDataset, 

212 camera=FakeCamera([self.flatExp1.getDetector()]), 

213 inputDims={'detector': 0}) 

214 linDataset = linDataset.outputLinearizer 

215 if doTableArray: 

216 # check that the linearizer table has been filled out properly 

217 for i in np.arange(numberAmps): 

218 tMax = (configLin.maxLookupTableAdu)/self.flux 

219 timeRange = np.linspace(0., tMax, configLin.maxLookupTableAdu) 

220 signalIdeal = timeRange*self.flux 

221 signalUncorrected = funcPolynomial(np.array([0.0, self.flux, self.k2NonLinearity]), 

222 timeRange) 

223 linearizerTableRow = signalIdeal - signalUncorrected 

224 self.assertEqual(len(linearizerTableRow), len(linDataset.tableData[i, :])) 

225 for j in np.arange(len(linearizerTableRow)): 

226 self.assertAlmostEqual(linearizerTableRow[j], linDataset.tableData[i, :][j], 

227 places=placesTests) 

228 else: 

229 # check entries in localDataset, which was modified by the function 

230 for ampName in self.ampNames: 

231 maskAmp = localDataset.expIdMask[ampName] 

232 finalMuVec = localDataset.rawMeans[ampName][maskAmp] 

233 finalTimeVec = localDataset.rawExpTimes[ampName][maskAmp] 

234 linearPart = self.flux*finalTimeVec 

235 inputFracNonLinearityResiduals = 100*(linearPart - finalMuVec)/linearPart 

236 self.assertEqual(fitType, localDataset.ptcFitType) 

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

238 if fitType == 'POLYNOMIAL': 

239 self.assertAlmostEqual(self.c1, localDataset.ptcFitPars[ampName][1]) 

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

241 if fitType == 'EXPAPPROXIMATION': 

242 self.assertAlmostEqual(self.a00, localDataset.ptcFitPars[ampName][0]) 

243 # noise already in electrons for 'EXPAPPROXIMATION' fit 

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

245 

246 # check entries in returned dataset (a dict of , for nonlinearity) 

247 for ampName in self.ampNames: 

248 maskAmp = localDataset.expIdMask[ampName] 

249 finalMuVec = localDataset.rawMeans[ampName][maskAmp] 

250 finalTimeVec = localDataset.rawExpTimes[ampName][maskAmp] 

251 linearPart = self.flux*finalTimeVec 

252 inputFracNonLinearityResiduals = 100*(linearPart - finalMuVec)/linearPart 

253 

254 # Nonlinearity fit parameters 

255 # Polynomial fits are now normalized to unit flux scaling 

256 self.assertAlmostEqual(0.0, linDataset.fitParams[ampName][0], places=1) 

257 self.assertAlmostEqual(1.0, linDataset.fitParams[ampName][1], 

258 places=5) 

259 

260 # Non-linearity coefficient for linearizer 

261 squaredCoeff = self.k2NonLinearity/(self.flux**2) 

262 self.assertAlmostEqual(squaredCoeff, linDataset.fitParams[ampName][2], 

263 places=placesTests) 

264 self.assertAlmostEqual(-squaredCoeff, linDataset.linearityCoeffs[ampName][2], 

265 places=placesTests) 

266 

267 linearPartModel = linDataset.fitParams[ampName][1]*finalTimeVec*self.flux 

268 outputFracNonLinearityResiduals = 100*(linearPartModel - finalMuVec)/linearPartModel 

269 # Fractional nonlinearity residuals 

270 self.assertEqual(len(outputFracNonLinearityResiduals), len(inputFracNonLinearityResiduals)) 

271 for calc, truth in zip(outputFracNonLinearityResiduals, inputFracNonLinearityResiduals): 

272 self.assertAlmostEqual(calc, truth, places=3) 

273 

274 def test_ptcFit(self): 

275 for createArray in [True, False]: 

276 for (fitType, order) in [('POLYNOMIAL', 2), ('POLYNOMIAL', 3), ('EXPAPPROXIMATION', None)]: 

277 self.ptcFitAndCheckPtc(fitType=fitType, order=order, doTableArray=createArray) 

278 

279 def test_meanVarMeasurement(self): 

280 task = self.defaultTaskExtract 

281 mu, varDiff, _ = task.measureMeanVarCov(self.flatExp1, self.flatExp2) 

282 

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

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

285 

286 def test_meanVarMeasurementWithNans(self): 

287 task = self.defaultTaskExtract 

288 self.flatExp1.image.array[20:30, :] = np.nan 

289 self.flatExp2.image.array[20:30, :] = np.nan 

290 

291 mu, varDiff, _ = task.measureMeanVarCov(self.flatExp1, self.flatExp2) 

292 

293 expectedMu1 = np.nanmean(self.flatExp1.image.array) 

294 expectedMu2 = np.nanmean(self.flatExp2.image.array) 

295 expectedMu = 0.5*(expectedMu1 + expectedMu2) 

296 

297 # Now the variance of the difference. First, create the diff image. 

298 im1 = self.flatExp1.maskedImage 

299 im2 = self.flatExp2.maskedImage 

300 

301 temp = im2.clone() 

302 temp *= expectedMu1 

303 diffIm = im1.clone() 

304 diffIm *= expectedMu2 

305 diffIm -= temp 

306 diffIm /= expectedMu 

307 

308 # Dive by two as it is what measureMeanVarCov returns (variance of difference) 

309 expectedVar = 0.5*np.nanvar(diffIm.image.array) 

310 

311 # Check that the standard deviations and the emans agree to less than 1 ADU 

312 self.assertLess(np.sqrt(expectedVar) - np.sqrt(varDiff), 1) 

313 self.assertLess(expectedMu - mu, 1) 

314 

315 def test_meanVarMeasurementAllNan(self): 

316 task = self.defaultTaskExtract 

317 self.flatExp1.image.array[:, :] = np.nan 

318 self.flatExp2.image.array[:, :] = np.nan 

319 

320 mu, varDiff, covDiff = task.measureMeanVarCov(self.flatExp1, self.flatExp2) 

321 

322 self.assertTrue(np.isnan(mu)) 

323 self.assertTrue(np.isnan(varDiff)) 

324 self.assertTrue(covDiff is None) 

325 

326 def test_makeZeroSafe(self): 

327 noZerosArray = [1., 20, -35, 45578.98, 90.0, 897, 659.8] 

328 someZerosArray = [1., 20, 0, 0, 90, 879, 0] 

329 allZerosArray = [0., 0.0, 0, 0, 0.0, 0, 0] 

330 

331 substituteValue = 1e-10 

332 

333 expectedSomeZerosArray = [1., 20, substituteValue, substituteValue, 90, 879, substituteValue] 

334 expectedAllZerosArray = np.repeat(substituteValue, len(allZerosArray)) 

335 

336 measuredSomeZerosArray = self.defaultTaskSolve._makeZeroSafe(someZerosArray, 

337 substituteValue=substituteValue) 

338 measuredAllZerosArray = self.defaultTaskSolve._makeZeroSafe(allZerosArray, 

339 substituteValue=substituteValue) 

340 measuredNoZerosArray = self.defaultTaskSolve._makeZeroSafe(noZerosArray, 

341 substituteValue=substituteValue) 

342 

343 for exp, meas in zip(expectedSomeZerosArray, measuredSomeZerosArray): 

344 self.assertEqual(exp, meas) 

345 for exp, meas in zip(expectedAllZerosArray, measuredAllZerosArray): 

346 self.assertEqual(exp, meas) 

347 for exp, meas in zip(noZerosArray, measuredNoZerosArray): 

348 self.assertEqual(exp, meas) 

349 

350 def test_getInitialGoodPoints(self): 

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

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

353 points = self.defaultTaskSolve._getInitialGoodPoints(xs, ys, maxDeviationPositive=0.1, 

354 maxDeviationNegative=0.25, 

355 minMeanRatioTest=0., 

356 minVarPivotSearch=0.) 

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

358 

359 ys[-1] = 30 

360 points = self.defaultTaskSolve._getInitialGoodPoints(xs, ys, 

361 maxDeviationPositive=0.1, 

362 maxDeviationNegative=0.25, 

363 minMeanRatioTest=0., 

364 minVarPivotSearch=0.) 

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

366 

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

368 newYs = copy.copy(ys) 

369 results = [False, True, True, False, False] 

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

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

372 points = self.defaultTaskSolve._getInitialGoodPoints(xs, newYs, maxDeviationPositive=0.05, 

373 maxDeviationNegative=0.25, 

374 minMeanRatioTest=0.0, 

375 minVarPivotSearch=0.0) 

376 assert (np.all(points[0:-2])) # noqa: E712 - flake8 is wrong here because of numpy.bool 

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

378 

379 def test_getExpIdsUsed(self): 

380 localDataset = copy.copy(self.dataset) 

381 

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

383 localDataset.inputExpIdPairs["C:0,0"].append(pair) 

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

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

386 

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

388 with self.assertRaises(AssertionError): 

389 localDataset.getExpIdsUsed("C:0,0") 

390 

391 def test_getGoodAmps(self): 

392 dataset = self.dataset 

393 

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

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

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

397 

398 

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

400 def setUp(self): 

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

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

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

404 

405 def test_generalBehaviour(self): 

406 test = PhotonTransferCurveDataset(['C00', 'C01'], " ") 

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

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

409 

410 

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

412 pass 

413 

414 

415def setup_module(module): 

416 lsst.utils.tests.init() 

417 

418 

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

420 lsst.utils.tests.init() 

421 unittest.main()