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.minNumberGoodPixelsForFft = 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)) for mu in localDataset.rawMeans[ampName]] 

185 else: 

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

187 

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

189 configLin.maxLinearAdu = 100000 

190 configLin.minLinearAdu = 50000 

191 if doTableArray: 

192 configLin.linearityType = "LookupTable" 

193 else: 

194 configLin.linearityType = "Polynomial" 

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

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

197 

198 if doTableArray: 

199 # Non-linearity 

200 numberAmps = len(self.ampNames) 

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

202 localDataset = solveTask.fitPtc(localDataset) 

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

204 linDataset = linearityTask.run(localDataset, 

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

206 inputDims={'detector': 0}) 

207 linDataset = linDataset.outputLinearizer 

208 else: 

209 localDataset = solveTask.fitPtc(localDataset) 

210 linDataset = linearityTask.run(localDataset, 

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

212 inputDims={'detector': 0}) 

213 linDataset = linDataset.outputLinearizer 

214 if doTableArray: 

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

216 for i in np.arange(numberAmps): 

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

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

219 signalIdeal = timeRange*self.flux 

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

221 timeRange) 

222 linearizerTableRow = signalIdeal - signalUncorrected 

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

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

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

226 places=placesTests) 

227 else: 

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

229 for ampName in self.ampNames: 

230 maskAmp = localDataset.expIdMask[ampName] 

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

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

233 linearPart = self.flux*finalTimeVec 

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

235 self.assertEqual(fitType, localDataset.ptcFitType) 

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

237 if fitType == 'POLYNOMIAL': 

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

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

240 if fitType == 'EXPAPPROXIMATION': 

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

242 # noise already in electrons for 'EXPAPPROXIMATION' fit 

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

244 

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

246 for ampName in self.ampNames: 

247 maskAmp = localDataset.expIdMask[ampName] 

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

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

250 linearPart = self.flux*finalTimeVec 

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

252 

253 # Nonlinearity fit parameters 

254 # Polynomial fits are now normalized to unit flux scaling 

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

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

257 places=5) 

258 

259 # Non-linearity coefficient for linearizer 

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

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

262 places=placesTests) 

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

264 places=placesTests) 

265 

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

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

268 # Fractional nonlinearity residuals 

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

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

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

272 

273 def test_ptcFit(self): 

274 for createArray in [True, False]: 

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

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

277 

278 def test_meanVarMeasurement(self): 

279 task = self.defaultTaskExtract 

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

281 

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

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

284 

285 def test_meanVarMeasurementWithNans(self): 

286 task = self.defaultTaskExtract 

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

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

289 

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

291 

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

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

294 expectedMu = 0.5*(expectedMu1 + expectedMu2) 

295 

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

297 im1 = self.flatExp1.maskedImage 

298 im2 = self.flatExp2.maskedImage 

299 

300 temp = im2.clone() 

301 temp *= expectedMu1 

302 diffIm = im1.clone() 

303 diffIm *= expectedMu2 

304 diffIm -= temp 

305 diffIm /= expectedMu 

306 

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

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

309 

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

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

312 self.assertLess(expectedMu - mu, 1) 

313 

314 def test_meanVarMeasurementAllNan(self): 

315 task = self.defaultTaskExtract 

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

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

318 

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

320 

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

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

323 self.assertTrue(covDiff is None) 

324 

325 def test_makeZeroSafe(self): 

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

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

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

329 

330 substituteValue = 1e-10 

331 

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

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

334 

335 measuredSomeZerosArray = self.defaultTaskSolve._makeZeroSafe(someZerosArray, 

336 substituteValue=substituteValue) 

337 measuredAllZerosArray = self.defaultTaskSolve._makeZeroSafe(allZerosArray, 

338 substituteValue=substituteValue) 

339 measuredNoZerosArray = self.defaultTaskSolve._makeZeroSafe(noZerosArray, 

340 substituteValue=substituteValue) 

341 

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

343 self.assertEqual(exp, meas) 

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

345 self.assertEqual(exp, meas) 

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

347 self.assertEqual(exp, meas) 

348 

349 def test_getInitialGoodPoints(self): 

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

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

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

353 maxDeviationNegative=0.25, 

354 minMeanRatioTest=0., 

355 minVarPivotSearch=0.) 

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

357 

358 ys[-1] = 30 

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

360 maxDeviationPositive=0.1, 

361 maxDeviationNegative=0.25, 

362 minMeanRatioTest=0., 

363 minVarPivotSearch=0.) 

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

365 

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

367 newYs = copy.copy(ys) 

368 results = [False, True, True, False, False] 

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

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

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

372 maxDeviationNegative=0.25, 

373 minMeanRatioTest=0.0, 

374 minVarPivotSearch=0.0) 

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

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

377 

378 def test_getExpIdsUsed(self): 

379 localDataset = copy.copy(self.dataset) 

380 

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

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

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

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

385 

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

387 with self.assertRaises(AssertionError): 

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

389 

390 def test_getGoodAmps(self): 

391 dataset = self.dataset 

392 

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

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

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

396 

397 

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

399 def setUp(self): 

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

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

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

403 

404 def test_generalBehaviour(self): 

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

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

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

408 

409 

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

411 pass 

412 

413 

414def setup_module(module): 

415 lsst.utils.tests.init() 

416 

417 

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

419 lsst.utils.tests.init() 

420 unittest.main()