Coverage for tests/test_ptc.py: 9%

324 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-08 01:45 -0700

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 

27import unittest 

28import numpy as np 

29import copy 

30import tempfile 

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 elif component == 'metadata': 

59 return self.exp.getMetadata() 

60 else: 

61 return self.exp 

62 

63 

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

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

66 

67 def setUp(self): 

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

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

70 

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

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

73 

74 self.flatMean = 2000 

75 self.readNoiseAdu = 10 

76 mockImageConfig = isrMock.IsrMock.ConfigClass() 

77 

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

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

80 mockImageConfig.flatDrop = 0.99999 

81 mockImageConfig.isTrimmed = True 

82 

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

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

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

86 

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

88 

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

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

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

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

93 

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

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

96 

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

98 self.flux = 1000. # ADU/sec 

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

100 self.k2NonLinearity = -5e-6 

101 # quadratic signal-chain non-linearity 

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

103 self.gain = 0.75 # e-/ADU 

104 self.c1 = 1./self.gain 

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

106 self.a00 = -1.2e-6 

107 self.c2 = -1.5e-6 

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

109 

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

111 self.dataset = PhotonTransferCurveDataset(self.ampNames, ptcFitType="PARTIAL") 

112 self.covariancesSqrtWeights = {} 

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

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

115 self.dataset.rawMeans[ampName] = muVec 

116 self.dataset.covariancesSqrtWeights[ampName] = np.zeros((1, 

117 self.dataset.covMatrixSide, 

118 self.dataset.covMatrixSide)) 

119 

120 # ISR metadata 

121 self.metadataContents = TaskMetadata() 

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

123 # Overscan readout noise [in ADU] 

124 for amp in self.ampNames: 

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

126 

127 def test_covAstier(self): 

128 """Test to check getCovariancesAstier 

129 

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

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

132 MeasurePhotonTransferCurveTask when doCovariancesAstier=True) 

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

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

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

136 

137 """ 

138 extractConfig = self.defaultConfigExtract 

139 extractConfig.minNumberGoodPixelsForCovariance = 5000 

140 extractConfig.detectorMeasurementRegion = 'FULL' 

141 # Cut off the low-flux point which is a bad fit, and this 

142 # also exercises this functionality and makes the tests 

143 # run a lot faster. 

144 extractConfig.minMeanSignal["ALL_AMPS"] = 2000.0 

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

146 

147 solveConfig = self.defaultConfigSolve 

148 solveConfig.ptcFitType = 'FULLCOVARIANCE' 

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

150 

151 inputGain = self.gain 

152 

153 muStandard, varStandard = {}, {} 

154 expDict = {} 

155 expIds = [] 

156 idCounter = 0 

157 for expTime in self.timeVec: 

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

159 readNoiseElectrons=3, 

160 expId1=idCounter, expId2=idCounter+1) 

161 mockExpRef1 = PretendRef(mockExp1) 

162 mockExpRef2 = PretendRef(mockExp2) 

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

164 expIds.append(idCounter) 

165 expIds.append(idCounter+1) 

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

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

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

169 mockExp2) 

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

171 mu1, mu2) 

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

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

174 idCounter += 2 

175 

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

177 taskMetadata=[self.metadataContents for x in expIds]) 

178 

179 # Force the last PTC dataset to have a NaN, and ensure that the 

180 # task runs (DM-38029). This is a minor perturbation and does not 

181 # affect the output comparison. 

182 resultsExtract.outputCovariances[-2].rawMeans['C:0,0'] = np.array([np.nan]) 

183 resultsExtract.outputCovariances[-2].rawVars['C:0,0'] = np.array([np.nan]) 

184 

185 resultsSolve = solveTask.run(resultsExtract.outputCovariances, 

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

187 

188 ptc = resultsSolve.outputPtcDataset 

189 

190 for amp in self.ampNames: 

191 self.assertAlmostEqual(ptc.gain[amp], inputGain, places=2) 

192 for v1, v2 in zip(varStandard[amp], ptc.finalVars[amp]): 

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

194 

195 mask = ptc.getGoodPoints(amp) 

196 

197 values = ((ptc.covariancesModel[amp][mask, 0, 0] - ptc.covariances[amp][mask, 0, 0]) 

198 / ptc.covariancesModel[amp][mask, 0, 0]) 

199 np.testing.assert_array_less(np.abs(values), 2e-3) 

200 

201 values = ((ptc.covariancesModel[amp][mask, 1, 1] - ptc.covariances[amp][mask, 1, 1]) 

202 / ptc.covariancesModel[amp][mask, 1, 1]) 

203 np.testing.assert_array_less(np.abs(values), 0.2) 

204 

205 values = ((ptc.covariancesModel[amp][mask, 1, 2] - ptc.covariances[amp][mask, 1, 2]) 

206 / ptc.covariancesModel[amp][mask, 1, 2]) 

207 np.testing.assert_array_less(np.abs(values), 0.2) 

208 

209 expIdsUsed = ptc.getExpIdsUsed("C:0,0") 

210 # Check that these are the same as the inputs, paired up, with the 

211 # first two (low flux) and final two (nans) removed. 

212 self.assertTrue(np.all(expIdsUsed == np.array(expIds).reshape(len(expIds) // 2, 2)[1:-1])) 

213 

214 goodAmps = ptc.getGoodAmps() 

215 self.assertEqual(goodAmps, self.ampNames) 

216 

217 # Check that every possibly modified field has the same length. 

218 covShape = None 

219 covSqrtShape = None 

220 covModelShape = None 

221 covModelNoBShape = None 

222 

223 for ampName in self.ampNames: 

224 if covShape is None: 

225 covShape = ptc.covariances[ampName].shape 

226 covSqrtShape = ptc.covariancesSqrtWeights[ampName].shape 

227 covModelShape = ptc.covariancesModel[ampName].shape 

228 covModelNoBShape = ptc.covariancesModelNoB[ampName].shape 

229 else: 

230 self.assertEqual(ptc.covariances[ampName].shape, covShape) 

231 self.assertEqual(ptc.covariancesSqrtWeights[ampName].shape, covSqrtShape) 

232 self.assertEqual(ptc.covariancesModel[ampName].shape, covModelShape) 

233 self.assertEqual(ptc.covariancesModelNoB[ampName].shape, covModelNoBShape) 

234 

235 # And check that this is serializable 

236 with tempfile.NamedTemporaryFile(suffix=".fits") as f: 

237 usedFilename = ptc.writeFits(f.name) 

238 fromFits = PhotonTransferCurveDataset.readFits(usedFilename) 

239 self.assertEqual(fromFits, ptc) 

240 

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

242 localDataset = copy.deepcopy(self.dataset) 

243 localDataset.ptcFitType = fitType 

244 configSolve = copy.copy(self.defaultConfigSolve) 

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

246 placesTests = 6 

247 if doFitBootstrap: 

248 configSolve.doFitBootstrap = True 

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

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

251 # error. 

252 placesTests = 3 

253 

254 if fitType == 'POLYNOMIAL': 

255 if order not in [2, 3]: 

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

257 if order == 2: 

258 for ampName in self.ampNames: 

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

260 mu in localDataset.rawMeans[ampName]] 

261 configSolve.polynomialFitDegree = 2 

262 if order == 3: 

263 for ampName in self.ampNames: 

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

265 for mu in localDataset.rawMeans[ampName]] 

266 configSolve.polynomialFitDegree = 3 

267 elif fitType == 'EXPAPPROXIMATION': 

268 g = self.gain 

269 for ampName in self.ampNames: 

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

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

272 for mu in localDataset.rawMeans[ampName]] 

273 else: 

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

275 

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

277 # Covariance weights values empirically determined from one of 

278 # the cases in test_covAstier. 

279 matrixSize = localDataset.covMatrixSide 

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

281 for ampName in self.ampNames: 

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

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

284 maskLength).reshape((maskLength, 

285 matrixSize, 

286 matrixSize)) 

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

288 0.00502802, 0.00383132, 0.00309475, 

289 0.00259572, 0.00223528, 0.00196273, 

290 0.00174943, 0.00157794, 0.00143707, 

291 0.00131929, 0.00121935, 0.0011334, 

292 0.00105893, 0.00099357, 0.0009358, 

293 0.00088439, 0.00083833] 

294 

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

296 configLin.maxLinearAdu = 100000 

297 configLin.minLinearAdu = 50000 

298 if doTableArray: 

299 configLin.linearityType = "LookupTable" 

300 else: 

301 configLin.linearityType = "Polynomial" 

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

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

304 

305 if doTableArray: 

306 # Non-linearity 

307 numberAmps = len(self.ampNames) 

308 # localDataset: PTC dataset 

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

310 localDataset = solveTask.fitMeasurementsToModel(localDataset) 

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

312 linDataset = linearityTask.run(localDataset, 

313 dummy=[1.0], 

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

315 inputPhotodiodeData={}, 

316 inputDims={'detector': 0}) 

317 linDataset = linDataset.outputLinearizer 

318 else: 

319 localDataset = solveTask.fitMeasurementsToModel(localDataset) 

320 linDataset = linearityTask.run(localDataset, 

321 dummy=[1.0], 

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

323 inputPhotodiodeData={}, 

324 inputDims={'detector': 0}) 

325 linDataset = linDataset.outputLinearizer 

326 if doTableArray: 

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

328 for i in np.arange(numberAmps): 

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

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

331 signalIdeal = timeRange*self.flux 

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

333 timeRange) 

334 linearizerTableRow = signalIdeal - signalUncorrected 

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

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

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

338 places=placesTests) 

339 else: 

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

341 for ampName in self.ampNames: 

342 maskAmp = localDataset.expIdMask[ampName] 

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

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

345 linearPart = self.flux*finalTimeVec 

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

347 self.assertEqual(fitType, localDataset.ptcFitType) 

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

349 if fitType == 'POLYNOMIAL': 

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

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

352 if fitType == 'EXPAPPROXIMATION': 

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

354 # noise already in electrons for 'EXPAPPROXIMATION' fit 

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

356 

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

358 for ampName in self.ampNames: 

359 maskAmp = localDataset.expIdMask[ampName] 

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

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

362 linearPart = self.flux*finalTimeVec 

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

364 

365 # Nonlinearity fit parameters 

366 # Polynomial fits are now normalized to unit flux scaling 

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

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

369 places=5) 

370 

371 # Non-linearity coefficient for linearizer 

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

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

374 places=placesTests) 

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

376 places=placesTests) 

377 

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

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

380 # Fractional nonlinearity residuals 

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

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

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

384 

385 def test_ptcFit(self): 

386 for createArray in [True, False]: 

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

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

389 

390 def test_meanVarMeasurement(self): 

391 task = self.defaultTaskExtract 

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

393 self.flatExp2) 

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

395 

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

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

398 

399 def test_meanVarMeasurementWithNans(self): 

400 task = self.defaultTaskExtract 

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

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

403 

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

405 self.flatExp2) 

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

407 

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

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

410 expectedMu = 0.5*(expectedMu1 + expectedMu2) 

411 

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

413 im1 = self.flatExp1.maskedImage 

414 im2 = self.flatExp2.maskedImage 

415 

416 temp = im2.clone() 

417 temp *= expectedMu1 

418 diffIm = im1.clone() 

419 diffIm *= expectedMu2 

420 diffIm -= temp 

421 diffIm /= expectedMu 

422 

423 # Divide by two as it is what measureMeanVarCov returns 

424 # (variance of difference) 

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

426 

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

428 # less than 1 ADU 

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

430 self.assertLess(expectedMu - mu, 1) 

431 

432 def test_meanVarMeasurementAllNan(self): 

433 task = self.defaultTaskExtract 

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

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

436 

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

438 self.flatExp2) 

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

440 

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

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

443 self.assertTrue(covDiff is None) 

444 

445 def test_makeZeroSafe(self): 

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

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

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

449 

450 substituteValue = 1e-10 

451 

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

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

454 

455 measuredSomeZerosArray = self.defaultTaskSolve._makeZeroSafe(someZerosArray, 

456 substituteValue=substituteValue) 

457 measuredAllZerosArray = self.defaultTaskSolve._makeZeroSafe(allZerosArray, 

458 substituteValue=substituteValue) 

459 measuredNoZerosArray = self.defaultTaskSolve._makeZeroSafe(noZerosArray, 

460 substituteValue=substituteValue) 

461 

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

463 self.assertEqual(exp, meas) 

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

465 self.assertEqual(exp, meas) 

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

467 self.assertEqual(exp, meas) 

468 

469 def test_getInitialGoodPoints(self): 

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

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

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

473 consecutivePointsVarDecreases=2) 

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

475 

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

477 ys[5] = 6 

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

479 consecutivePointsVarDecreases=2) 

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

481 

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

483 extractConfig = self.defaultConfigExtract 

484 extractConfig.gainCorrectionType = correctionType 

485 extractConfig.minNumberGoodPixelsForCovariance = 5000 

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

487 

488 expDict = {} 

489 expIds = [] 

490 idCounter = 0 

491 inputGain = self.gain # 1.5 e/ADU 

492 for expTime in self.timeVec: 

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

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

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

496 fluxElectrons=100, 

497 expId1=idCounter, expId2=idCounter+1) 

498 mockExpRef1 = PretendRef(mockExp1) 

499 mockExpRef2 = PretendRef(mockExp2) 

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

501 expIds.append(idCounter) 

502 expIds.append(idCounter+1) 

503 idCounter += 2 

504 

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

506 taskMetadata=[self.metadataContents for x in expIds]) 

507 for exposurePair in resultsExtract.outputCovariances: 

508 for ampName in self.ampNames: 

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

510 continue 

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

512 

513 def test_getGainFromFlatPair(self): 

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

515 self.runGetGainFromFlatPair(gainCorrectionType) 

516 

517 

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

519 def setUp(self): 

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

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

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

523 

524 def test_generalBehaviour(self): 

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

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

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

528 

529 

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

531 pass 

532 

533 

534def setup_module(module): 

535 lsst.utils.tests.init() 

536 

537 

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

539 lsst.utils.tests.init() 

540 unittest.main()