Coverage for tests/test_ptc.py: 10%

303 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-09 03:54 -0800

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 

40from lsst.pipe.base import TaskMetadata 

41 

42 

43class FakeCamera(list): 

44 def getName(self): 

45 return "FakeCam" 

46 

47 

48class PretendRef(): 

49 "A class to act as a mock exposure reference" 

50 def __init__(self, exposure): 

51 self.exp = exposure 

52 

53 def get(self, component=None): 

54 if component == 'visitInfo': 

55 return self.exp.getVisitInfo() 

56 elif component == 'detector': 

57 return self.exp.getDetector() 

58 else: 

59 return self.exp 

60 

61 

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

63 """A test case for the PTC tasks.""" 

64 

65 def setUp(self): 

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

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

68 

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

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

71 

72 self.flatMean = 2000 

73 self.readNoiseAdu = 10 

74 mockImageConfig = isrMock.IsrMock.ConfigClass() 

75 

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

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

78 mockImageConfig.flatDrop = 0.99999 

79 mockImageConfig.isTrimmed = True 

80 

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

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

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

84 

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

86 

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

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

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

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

91 

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

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

94 

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

96 self.flux = 1000. # ADU/sec 

97 self.timeVec = np.arange(1., 101., 5) 

98 self.k2NonLinearity = -5e-6 

99 # quadratic signal-chain non-linearity 

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

101 self.gain = 0.75 # e-/ADU 

102 self.c1 = 1./self.gain 

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

104 self.a00 = -1.2e-6 

105 self.c2 = -1.5e-6 

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

107 

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

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

110 self.covariancesSqrtWeights = {} 

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

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

113 self.dataset.rawMeans[ampName] = muVec 

114 self.covariancesSqrtWeights[ampName] = [] 

115 

116 # ISR metadata 

117 self.metadataContents = TaskMetadata() 

118 self.metadataContents["isr"] = {} 

119 # Overscan readout noise [in ADU] 

120 for amp in self.ampNames: 

121 self.metadataContents["isr"][f"RESIDUAL STDEV {amp}"] = np.sqrt(self.noiseSq)/self.gain 

122 

123 def test_covAstier(self): 

124 """Test to check getCovariancesAstier 

125 

126 We check that the gain is the same as the imput gain from the 

127 mock data, that the covariances via FFT (as it is in 

128 MeasurePhotonTransferCurveTask when doCovariancesAstier=True) 

129 are the same as calculated in real space, and that Cov[0, 0] 

130 (i.e., the variances) are similar to the variances calculated 

131 with the standard method (when doCovariancesAstier=false), 

132 

133 """ 

134 extractConfig = self.defaultConfigExtract 

135 extractConfig.minNumberGoodPixelsForCovariance = 5000 

136 extractConfig.detectorMeasurementRegion = 'FULL' 

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

138 

139 solveConfig = self.defaultConfigSolve 

140 solveConfig.ptcFitType = 'FULLCOVARIANCE' 

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

142 

143 inputGain = self.gain 

144 

145 muStandard, varStandard = {}, {} 

146 expDict = {} 

147 expIds = [] 

148 idCounter = 0 

149 for expTime in self.timeVec: 

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

151 readNoiseElectrons=3, 

152 expId1=idCounter, expId2=idCounter+1) 

153 mockExpRef1 = PretendRef(mockExp1) 

154 mockExpRef2 = PretendRef(mockExp2) 

155 expDict[expTime] = ((mockExpRef1, idCounter), (mockExpRef2, idCounter+1)) 

156 expIds.append(idCounter) 

157 expIds.append(idCounter+1) 

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

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

160 im1Area, im2Area, imStatsCtrl, mu1, mu2 = extractTask.getImageAreasMasksStats(mockExp1, 

161 mockExp2) 

162 muDiff, varDiff, covAstier = extractTask.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, 

163 mu1, mu2) 

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

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

166 idCounter += 2 

167 

168 resultsExtract = extractTask.run(inputExp=expDict, inputDims=expIds, 

169 taskMetadata=[self.metadataContents]) 

170 resultsSolve = solveTask.run(resultsExtract.outputCovariances, 

171 camera=FakeCamera([self.flatExp1.getDetector()])) 

172 

173 for amp in self.ampNames: 

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

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

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

177 

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

179 localDataset = copy.deepcopy(self.dataset) 

180 localDataset.ptcFitType = fitType 

181 configSolve = copy.copy(self.defaultConfigSolve) 

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

183 placesTests = 6 

184 if doFitBootstrap: 

185 configSolve.doFitBootstrap = True 

186 # Bootstrap method in cp_pipe/utils.py does multiple fits 

187 # in the precense of noise. Allow for more margin of 

188 # error. 

189 placesTests = 3 

190 

191 if fitType == 'POLYNOMIAL': 

192 if order not in [2, 3]: 

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

194 if order == 2: 

195 for ampName in self.ampNames: 

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

197 mu in localDataset.rawMeans[ampName]] 

198 configSolve.polynomialFitDegree = 2 

199 if order == 3: 

200 for ampName in self.ampNames: 

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

202 for mu in localDataset.rawMeans[ampName]] 

203 configSolve.polynomialFitDegree = 3 

204 elif fitType == 'EXPAPPROXIMATION': 

205 g = self.gain 

206 for ampName in self.ampNames: 

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

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

209 for mu in localDataset.rawMeans[ampName]] 

210 else: 

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

212 

213 # Initialize mask and covariance weights that will be used in fits. 

214 # Covariance weights values empirically determined from one of 

215 # the cases in test_covAstier. 

216 matrixSize = localDataset.covMatrixSide 

217 maskLength = len(localDataset.rawMeans[ampName]) 

218 for ampName in self.ampNames: 

219 localDataset.expIdMask[ampName] = np.repeat(True, maskLength) 

220 localDataset.covariancesSqrtWeights[ampName] = np.repeat(np.ones((matrixSize, matrixSize)), 

221 maskLength).reshape((maskLength, 

222 matrixSize, 

223 matrixSize)) 

224 localDataset.covariancesSqrtWeights[ampName][:, 0, 0] = [0.07980188, 0.01339653, 0.0073118, 

225 0.00502802, 0.00383132, 0.00309475, 

226 0.00259572, 0.00223528, 0.00196273, 

227 0.00174943, 0.00157794, 0.00143707, 

228 0.00131929, 0.00121935, 0.0011334, 

229 0.00105893, 0.00099357, 0.0009358, 

230 0.00088439, 0.00083833] 

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

232 configLin.maxLinearAdu = 100000 

233 configLin.minLinearAdu = 50000 

234 if doTableArray: 

235 configLin.linearityType = "LookupTable" 

236 else: 

237 configLin.linearityType = "Polynomial" 

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

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

240 

241 if doTableArray: 

242 # Non-linearity 

243 numberAmps = len(self.ampNames) 

244 # localDataset: PTC dataset 

245 # (`lsst.ip.isr.ptcDataset.PhotonTransferCurveDataset`) 

246 localDataset = solveTask.fitMeasurementsToModel(localDataset) 

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

248 linDataset = linearityTask.run(localDataset, 

249 dummy=[1.0], 

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

251 inputDims={'detector': 0}) 

252 linDataset = linDataset.outputLinearizer 

253 else: 

254 localDataset = solveTask.fitMeasurementsToModel(localDataset) 

255 linDataset = linearityTask.run(localDataset, 

256 dummy=[1.0], 

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

258 inputDims={'detector': 0}) 

259 linDataset = linDataset.outputLinearizer 

260 if doTableArray: 

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

262 for i in np.arange(numberAmps): 

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

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

265 signalIdeal = timeRange*self.flux 

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

267 timeRange) 

268 linearizerTableRow = signalIdeal - signalUncorrected 

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

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

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

272 places=placesTests) 

273 else: 

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

275 for ampName in self.ampNames: 

276 maskAmp = localDataset.expIdMask[ampName] 

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

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

279 linearPart = self.flux*finalTimeVec 

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

281 self.assertEqual(fitType, localDataset.ptcFitType) 

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

283 if fitType == 'POLYNOMIAL': 

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

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

286 if fitType == 'EXPAPPROXIMATION': 

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

288 # noise already in electrons for 'EXPAPPROXIMATION' fit 

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

290 

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

292 for ampName in self.ampNames: 

293 maskAmp = localDataset.expIdMask[ampName] 

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

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

296 linearPart = self.flux*finalTimeVec 

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

298 

299 # Nonlinearity fit parameters 

300 # Polynomial fits are now normalized to unit flux scaling 

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

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

303 places=5) 

304 

305 # Non-linearity coefficient for linearizer 

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

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

308 places=placesTests) 

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

310 places=placesTests) 

311 

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

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

314 # Fractional nonlinearity residuals 

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

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

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

318 

319 def test_ptcFit(self): 

320 for createArray in [True, False]: 

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

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

323 

324 def test_meanVarMeasurement(self): 

325 task = self.defaultTaskExtract 

326 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(self.flatExp1, 

327 self.flatExp2) 

328 mu, varDiff, _ = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2) 

329 

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

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

332 

333 def test_meanVarMeasurementWithNans(self): 

334 task = self.defaultTaskExtract 

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

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

337 

338 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(self.flatExp1, 

339 self.flatExp2) 

340 mu, varDiff, _ = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2) 

341 

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

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

344 expectedMu = 0.5*(expectedMu1 + expectedMu2) 

345 

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

347 im1 = self.flatExp1.maskedImage 

348 im2 = self.flatExp2.maskedImage 

349 

350 temp = im2.clone() 

351 temp *= expectedMu1 

352 diffIm = im1.clone() 

353 diffIm *= expectedMu2 

354 diffIm -= temp 

355 diffIm /= expectedMu 

356 

357 # Divide by two as it is what measureMeanVarCov returns 

358 # (variance of difference) 

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

360 

361 # Check that the standard deviations and the emans agree to 

362 # less than 1 ADU 

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

364 self.assertLess(expectedMu - mu, 1) 

365 

366 def test_meanVarMeasurementAllNan(self): 

367 task = self.defaultTaskExtract 

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

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

370 

371 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(self.flatExp1, 

372 self.flatExp2) 

373 mu, varDiff, covDiff = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2) 

374 

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

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

377 self.assertTrue(covDiff is None) 

378 

379 def test_makeZeroSafe(self): 

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

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

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

383 

384 substituteValue = 1e-10 

385 

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

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

388 

389 measuredSomeZerosArray = self.defaultTaskSolve._makeZeroSafe(someZerosArray, 

390 substituteValue=substituteValue) 

391 measuredAllZerosArray = self.defaultTaskSolve._makeZeroSafe(allZerosArray, 

392 substituteValue=substituteValue) 

393 measuredNoZerosArray = self.defaultTaskSolve._makeZeroSafe(noZerosArray, 

394 substituteValue=substituteValue) 

395 

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

397 self.assertEqual(exp, meas) 

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

399 self.assertEqual(exp, meas) 

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

401 self.assertEqual(exp, meas) 

402 

403 def test_getInitialGoodPoints(self): 

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

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

406 points = self.defaultTaskSolve._getInitialGoodPoints(xs, ys, minVarPivotSearch=0., 

407 consecutivePointsVarDecreases=2) 

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

409 

410 ys[4] = 7 # Variance decreases in two consecutive points after ys[3]=8 

411 ys[5] = 6 

412 points = self.defaultTaskSolve._getInitialGoodPoints(xs, ys, minVarPivotSearch=0., 

413 consecutivePointsVarDecreases=2) 

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

415 

416 def test_getExpIdsUsed(self): 

417 localDataset = copy.copy(self.dataset) 

418 

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

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

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

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

423 

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

425 with self.assertRaises(AssertionError): 

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

427 

428 def test_getGoodAmps(self): 

429 dataset = self.dataset 

430 

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

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

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

434 

435 def runGetGainFromFlatPair(self, correctionType='NONE'): 

436 extractConfig = self.defaultConfigExtract 

437 extractConfig.gainCorrectionType = correctionType 

438 extractConfig.minNumberGoodPixelsForCovariance = 5000 

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

440 

441 expDict = {} 

442 expIds = [] 

443 idCounter = 0 

444 inputGain = self.gain # 1.5 e/ADU 

445 for expTime in self.timeVec: 

446 # Approximation works better at low flux, e.g., < 10000 ADU 

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

448 readNoiseElectrons=np.sqrt(self.noiseSq), 

449 fluxElectrons=100, 

450 expId1=idCounter, expId2=idCounter+1) 

451 mockExpRef1 = PretendRef(mockExp1) 

452 mockExpRef2 = PretendRef(mockExp2) 

453 expDict[expTime] = ((mockExpRef1, idCounter), (mockExpRef2, idCounter+1)) 

454 expIds.append(idCounter) 

455 expIds.append(idCounter+1) 

456 idCounter += 2 

457 

458 resultsExtract = extractTask.run(inputExp=expDict, inputDims=expIds, 

459 taskMetadata=[self.metadataContents]) 

460 for exposurePair in resultsExtract.outputCovariances: 

461 for ampName in self.ampNames: 

462 if exposurePair.gain[ampName] is np.nan: 

463 continue 

464 self.assertAlmostEqual(exposurePair.gain[ampName], inputGain, delta=0.04) 

465 

466 def test_getGainFromFlatPair(self): 

467 for gainCorrectionType in ['NONE', 'SIMPLE', 'FULL', ]: 

468 self.runGetGainFromFlatPair(gainCorrectionType) 

469 

470 

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

472 def setUp(self): 

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

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

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

476 

477 def test_generalBehaviour(self): 

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

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

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

481 

482 

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

484 pass 

485 

486 

487def setup_module(module): 

488 lsst.utils.tests.init() 

489 

490 

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

492 lsst.utils.tests.init() 

493 unittest.main()