Coverage for tests/test_ptc.py: 8%
369 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-12 02:51 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-12 02:51 -0700
1#!/usr/bin/env python
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."""
27import unittest
28import numpy as np
29import copy
30import tempfile
31import logging
33import lsst.utils
34import lsst.utils.tests
36import lsst.cp.pipe as cpPipe
37import lsst.ip.isr.isrMock as isrMock
38from lsst.ip.isr import PhotonTransferCurveDataset
39from lsst.cp.pipe.utils import (funcPolynomial, makeMockFlats)
41from lsst.pipe.base import TaskMetadata
44class FakeCamera(list):
45 def getName(self):
46 return "FakeCam"
49class PretendRef():
50 "A class to act as a mock exposure reference"
51 def __init__(self, exposure):
52 self.exp = exposure
54 def get(self, component=None):
55 if component == 'visitInfo':
56 return self.exp.getVisitInfo()
57 elif component == 'detector':
58 return self.exp.getDetector()
59 elif component == 'metadata':
60 return self.exp.getMetadata()
61 else:
62 return self.exp
65class MeasurePhotonTransferCurveTaskTestCase(lsst.utils.tests.TestCase):
66 """A test case for the PTC tasks."""
68 def setUp(self):
69 self.defaultConfigExtract = cpPipe.ptc.PhotonTransferCurveExtractTask.ConfigClass()
70 self.defaultTaskExtract = cpPipe.ptc.PhotonTransferCurveExtractTask(config=self.defaultConfigExtract)
72 self.defaultConfigSolve = cpPipe.ptc.PhotonTransferCurveSolveTask.ConfigClass()
73 self.defaultTaskSolve = cpPipe.ptc.PhotonTransferCurveSolveTask(config=self.defaultConfigSolve)
75 self.flatMean = 2000
76 self.readNoiseAdu = 10
77 mockImageConfig = isrMock.IsrMock.ConfigClass()
79 # flatDrop is not really relevant as we replace the data
80 # but good to note it in case we change how this image is made
81 mockImageConfig.flatDrop = 0.99999
82 mockImageConfig.isTrimmed = True
84 self.flatExp1 = isrMock.FlatMock(config=mockImageConfig).run()
85 self.flatExp2 = self.flatExp1.clone()
86 (shapeY, shapeX) = self.flatExp1.getDimensions()
88 self.flatWidth = np.sqrt(self.flatMean) + self.readNoiseAdu
90 self.rng1 = np.random.RandomState(1984)
91 flatData1 = self.rng1.normal(self.flatMean, self.flatWidth, (shapeX, shapeY))
92 self.rng2 = np.random.RandomState(666)
93 flatData2 = self.rng2.normal(self.flatMean, self.flatWidth, (shapeX, shapeY))
95 self.flatExp1.image.array[:] = flatData1
96 self.flatExp2.image.array[:] = flatData2
98 # create fake PTC data to see if fit works, for one amp ('amp')
99 self.flux = 1000. # ADU/sec
100 self.timeVec = np.arange(1., 101., 5)
101 self.k2NonLinearity = -5e-6
102 # quadratic signal-chain non-linearity
103 muVec = self.flux*self.timeVec + self.k2NonLinearity*self.timeVec**2
104 self.gain = 0.75 # e-/ADU
105 self.c1 = 1./self.gain
106 self.noiseSq = 2*self.gain # 7.5 (e-)^2
107 self.a00 = -1.2e-6
108 self.c2 = -1.5e-6
109 self.c3 = -4.7e-12 # tuned so that it turns over for 200k mean
111 self.ampNames = [amp.getName() for amp in self.flatExp1.getDetector().getAmplifiers()]
112 self.dataset = PhotonTransferCurveDataset(self.ampNames, ptcFitType="PARTIAL")
113 self.covariancesSqrtWeights = {}
114 for ampName in self.ampNames: # just the expTimes and means here - vars vary per function
115 self.dataset.rawExpTimes[ampName] = self.timeVec
116 self.dataset.rawMeans[ampName] = muVec
117 self.dataset.covariancesSqrtWeights[ampName] = np.zeros((1,
118 self.dataset.covMatrixSide,
119 self.dataset.covMatrixSide))
121 # ISR metadata
122 self.metadataContents = TaskMetadata()
123 self.metadataContents["isr"] = {}
124 # Overscan readout noise [in ADU]
125 for amp in self.ampNames:
126 self.metadataContents["isr"][f"RESIDUAL STDEV {amp}"] = np.sqrt(self.noiseSq)/self.gain
128 def test_covAstier(self):
129 """Test to check getCovariancesAstier
131 We check that the gain is the same as the imput gain from the
132 mock data, that the covariances via FFT (as it is in
133 MeasurePhotonTransferCurveTask when doCovariancesAstier=True)
134 are the same as calculated in real space, and that Cov[0, 0]
135 (i.e., the variances) are similar to the variances calculated
136 with the standard method (when doCovariancesAstier=false),
138 """
139 extractConfig = self.defaultConfigExtract
140 extractConfig.minNumberGoodPixelsForCovariance = 5000
141 extractConfig.detectorMeasurementRegion = 'FULL'
142 # Cut off the low-flux point which is a bad fit, and this
143 # also exercises this functionality and makes the tests
144 # run a lot faster.
145 extractConfig.minMeanSignal["ALL_AMPS"] = 2000.0
146 extractTask = cpPipe.ptc.PhotonTransferCurveExtractTask(config=extractConfig)
148 solveConfig = self.defaultConfigSolve
149 solveConfig.ptcFitType = 'FULLCOVARIANCE'
150 solveTask = cpPipe.ptc.PhotonTransferCurveSolveTask(config=solveConfig)
152 inputGain = self.gain
154 muStandard, varStandard = {}, {}
155 expDict = {}
156 expIds = []
157 idCounter = 0
158 for expTime in self.timeVec:
159 mockExp1, mockExp2 = makeMockFlats(expTime, gain=inputGain,
160 readNoiseElectrons=3,
161 expId1=idCounter, expId2=idCounter+1)
162 mockExpRef1 = PretendRef(mockExp1)
163 mockExpRef2 = PretendRef(mockExp2)
164 expDict[expTime] = ((mockExpRef1, idCounter), (mockExpRef2, idCounter+1))
165 expIds.append(idCounter)
166 expIds.append(idCounter+1)
167 for ampNumber, ampName in enumerate(self.ampNames):
168 # cov has (i, j, var, cov, npix)
169 im1Area, im2Area, imStatsCtrl, mu1, mu2 = extractTask.getImageAreasMasksStats(mockExp1,
170 mockExp2)
171 muDiff, varDiff, covAstier = extractTask.measureMeanVarCov(im1Area, im2Area, imStatsCtrl,
172 mu1, mu2)
173 muStandard.setdefault(ampName, []).append(muDiff)
174 varStandard.setdefault(ampName, []).append(varDiff)
175 idCounter += 2
177 resultsExtract = extractTask.run(inputExp=expDict, inputDims=expIds,
178 taskMetadata=[self.metadataContents for x in expIds])
180 # Force the last PTC dataset to have a NaN, and ensure that the
181 # task runs (DM-38029). This is a minor perturbation and does not
182 # affect the output comparison.
183 resultsExtract.outputCovariances[-2].rawMeans['C:0,0'] = np.array([np.nan])
184 resultsExtract.outputCovariances[-2].rawVars['C:0,0'] = np.array([np.nan])
186 resultsSolve = solveTask.run(resultsExtract.outputCovariances,
187 camera=FakeCamera([self.flatExp1.getDetector()]))
189 ptc = resultsSolve.outputPtcDataset
191 for amp in self.ampNames:
192 self.assertAlmostEqual(ptc.gain[amp], inputGain, places=2)
193 for v1, v2 in zip(varStandard[amp], ptc.finalVars[amp]):
194 self.assertAlmostEqual(v1/v2, 1.0, places=1)
196 mask = ptc.getGoodPoints(amp)
198 values = ((ptc.covariancesModel[amp][mask, 0, 0] - ptc.covariances[amp][mask, 0, 0])
199 / ptc.covariancesModel[amp][mask, 0, 0])
200 np.testing.assert_array_less(np.abs(values), 2e-3)
202 values = ((ptc.covariancesModel[amp][mask, 1, 1] - ptc.covariances[amp][mask, 1, 1])
203 / ptc.covariancesModel[amp][mask, 1, 1])
204 np.testing.assert_array_less(np.abs(values), 0.2)
206 values = ((ptc.covariancesModel[amp][mask, 1, 2] - ptc.covariances[amp][mask, 1, 2])
207 / ptc.covariancesModel[amp][mask, 1, 2])
208 np.testing.assert_array_less(np.abs(values), 0.2)
210 expIdsUsed = ptc.getExpIdsUsed("C:0,0")
211 # Check that these are the same as the inputs, paired up, with the
212 # first two (low flux) and final two (nans) removed.
213 self.assertTrue(np.all(expIdsUsed == np.array(expIds).reshape(len(expIds) // 2, 2)[1:-1]))
215 goodAmps = ptc.getGoodAmps()
216 self.assertEqual(goodAmps, self.ampNames)
218 # Check that every possibly modified field has the same length.
219 covShape = None
220 covSqrtShape = None
221 covModelShape = None
222 covModelNoBShape = None
224 for ampName in self.ampNames:
225 if covShape is None:
226 covShape = ptc.covariances[ampName].shape
227 covSqrtShape = ptc.covariancesSqrtWeights[ampName].shape
228 covModelShape = ptc.covariancesModel[ampName].shape
229 covModelNoBShape = ptc.covariancesModelNoB[ampName].shape
230 else:
231 self.assertEqual(ptc.covariances[ampName].shape, covShape)
232 self.assertEqual(ptc.covariancesSqrtWeights[ampName].shape, covSqrtShape)
233 self.assertEqual(ptc.covariancesModel[ampName].shape, covModelShape)
234 self.assertEqual(ptc.covariancesModelNoB[ampName].shape, covModelNoBShape)
236 # And check that this is serializable
237 with tempfile.NamedTemporaryFile(suffix=".fits") as f:
238 usedFilename = ptc.writeFits(f.name)
239 fromFits = PhotonTransferCurveDataset.readFits(usedFilename)
240 self.assertEqual(fromFits, ptc)
242 def ptcFitAndCheckPtc(
243 self,
244 order=None,
245 fitType=None,
246 doTableArray=False,
247 doFitBootstrap=False,
248 doLegacy=False,
249 ):
250 localDataset = copy.deepcopy(self.dataset)
251 localDataset.ptcFitType = fitType
252 configSolve = copy.copy(self.defaultConfigSolve)
253 configLin = cpPipe.linearity.LinearitySolveTask.ConfigClass()
254 placesTests = 6
255 if doFitBootstrap:
256 configSolve.doFitBootstrap = True
257 # Bootstrap method in cp_pipe/utils.py does multiple fits
258 # in the precense of noise. Allow for more margin of
259 # error.
260 placesTests = 3
262 configSolve.doLegacyTurnoffAndOutlierSelection = doLegacy
264 if fitType == 'POLYNOMIAL':
265 if order not in [2, 3]:
266 RuntimeError("Enter a valid polynomial order for this test: 2 or 3")
267 if order == 2:
268 for ampName in self.ampNames:
269 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 for
270 mu in localDataset.rawMeans[ampName]]
271 configSolve.polynomialFitDegree = 2
272 if order == 3:
273 for ampName in self.ampNames:
274 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 + self.c3*mu**3
275 for mu in localDataset.rawMeans[ampName]]
276 configSolve.polynomialFitDegree = 3
277 elif fitType == 'EXPAPPROXIMATION':
278 g = self.gain
279 for ampName in self.ampNames:
280 localDataset.rawVars[ampName] = [(0.5/(self.a00*g**2)*(np.exp(2*self.a00*mu*g)-1)
281 + self.noiseSq/(g*g))
282 for mu in localDataset.rawMeans[ampName]]
283 else:
284 raise RuntimeError("Enter a fit function type: 'POLYNOMIAL' or 'EXPAPPROXIMATION'")
286 # Initialize mask and covariance weights that will be used in fits.
287 # Covariance weights values empirically determined from one of
288 # the cases in test_covAstier.
289 matrixSize = localDataset.covMatrixSide
290 maskLength = len(localDataset.rawMeans[ampName])
291 for ampName in self.ampNames:
292 localDataset.expIdMask[ampName] = np.repeat(True, maskLength)
293 localDataset.covariancesSqrtWeights[ampName] = np.repeat(np.ones((matrixSize, matrixSize)),
294 maskLength).reshape((maskLength,
295 matrixSize,
296 matrixSize))
297 localDataset.covariancesSqrtWeights[ampName][:, 0, 0] = [0.07980188, 0.01339653, 0.0073118,
298 0.00502802, 0.00383132, 0.00309475,
299 0.00259572, 0.00223528, 0.00196273,
300 0.00174943, 0.00157794, 0.00143707,
301 0.00131929, 0.00121935, 0.0011334,
302 0.00105893, 0.00099357, 0.0009358,
303 0.00088439, 0.00083833]
305 configLin.maxLookupTableAdu = 200000 # Max ADU in input mock flats
306 configLin.maxLinearAdu = 100000
307 configLin.minLinearAdu = 50000
308 if doTableArray:
309 configLin.linearityType = "LookupTable"
310 else:
311 configLin.linearityType = "Polynomial"
312 solveTask = cpPipe.ptc.PhotonTransferCurveSolveTask(config=configSolve)
313 linearityTask = cpPipe.linearity.LinearitySolveTask(config=configLin)
315 if doTableArray:
316 # Non-linearity
317 numberAmps = len(self.ampNames)
318 # localDataset: PTC dataset
319 # (`lsst.ip.isr.ptcDataset.PhotonTransferCurveDataset`)
320 localDataset = solveTask.fitMeasurementsToModel(localDataset)
321 # linDataset here is a lsst.pipe.base.Struct
322 linDataset = linearityTask.run(localDataset,
323 dummy=[1.0],
324 camera=FakeCamera([self.flatExp1.getDetector()]),
325 inputPhotodiodeData={},
326 inputDims={'detector': 0})
327 linDataset = linDataset.outputLinearizer
328 else:
329 localDataset = solveTask.fitMeasurementsToModel(localDataset)
330 linDataset = linearityTask.run(localDataset,
331 dummy=[1.0],
332 camera=FakeCamera([self.flatExp1.getDetector()]),
333 inputPhotodiodeData={},
334 inputDims={'detector': 0})
335 linDataset = linDataset.outputLinearizer
336 if doTableArray:
337 # check that the linearizer table has been filled out properly
338 for i in np.arange(numberAmps):
339 tMax = (configLin.maxLookupTableAdu)/self.flux
340 timeRange = np.linspace(0., tMax, configLin.maxLookupTableAdu)
341 signalIdeal = timeRange*self.flux
342 signalUncorrected = funcPolynomial(np.array([0.0, self.flux, self.k2NonLinearity]),
343 timeRange)
344 linearizerTableRow = signalIdeal - signalUncorrected
345 self.assertEqual(len(linearizerTableRow), len(linDataset.tableData[i, :]))
346 for j in np.arange(len(linearizerTableRow)):
347 self.assertAlmostEqual(linearizerTableRow[j], linDataset.tableData[i, :][j],
348 places=placesTests)
349 else:
350 # check entries in localDataset, which was modified by the function
351 for ampName in self.ampNames:
352 maskAmp = localDataset.expIdMask[ampName]
353 finalMuVec = localDataset.rawMeans[ampName][maskAmp]
354 finalTimeVec = localDataset.rawExpTimes[ampName][maskAmp]
355 linearPart = self.flux*finalTimeVec
356 inputFracNonLinearityResiduals = 100*(linearPart - finalMuVec)/linearPart
357 self.assertEqual(fitType, localDataset.ptcFitType)
358 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
359 if fitType == 'POLYNOMIAL':
360 self.assertAlmostEqual(self.c1, localDataset.ptcFitPars[ampName][1])
361 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName])
362 if fitType == 'EXPAPPROXIMATION':
363 self.assertAlmostEqual(self.a00, localDataset.ptcFitPars[ampName][0])
364 # noise already in electrons for 'EXPAPPROXIMATION' fit
365 self.assertAlmostEqual(np.sqrt(self.noiseSq), localDataset.noise[ampName])
367 # check entries in returned dataset (a dict of , for nonlinearity)
368 for ampName in self.ampNames:
369 maskAmp = localDataset.expIdMask[ampName]
370 finalMuVec = localDataset.rawMeans[ampName][maskAmp]
371 finalTimeVec = localDataset.rawExpTimes[ampName][maskAmp]
372 linearPart = self.flux*finalTimeVec
373 inputFracNonLinearityResiduals = 100*(linearPart - finalMuVec)/linearPart
375 # Nonlinearity fit parameters
376 # Polynomial fits are now normalized to unit flux scaling
377 self.assertAlmostEqual(0.0, linDataset.fitParams[ampName][0], places=1)
378 self.assertAlmostEqual(1.0, linDataset.fitParams[ampName][1],
379 places=5)
381 # Non-linearity coefficient for linearizer
382 squaredCoeff = self.k2NonLinearity/(self.flux**2)
383 self.assertAlmostEqual(squaredCoeff, linDataset.fitParams[ampName][2],
384 places=placesTests)
385 self.assertAlmostEqual(-squaredCoeff, linDataset.linearityCoeffs[ampName][2],
386 places=placesTests)
388 linearPartModel = linDataset.fitParams[ampName][1]*finalTimeVec*self.flux
389 outputFracNonLinearityResiduals = 100*(linearPartModel - finalMuVec)/linearPartModel
390 # Fractional nonlinearity residuals
391 self.assertEqual(len(outputFracNonLinearityResiduals), len(inputFracNonLinearityResiduals))
392 for calc, truth in zip(outputFracNonLinearityResiduals, inputFracNonLinearityResiduals):
393 self.assertAlmostEqual(calc, truth, places=3)
395 def test_ptcFit(self):
396 for createArray in [True, False]:
397 for doLegacy in [False, True]:
398 for (fitType, order) in [('POLYNOMIAL', 2), ('POLYNOMIAL', 3), ('EXPAPPROXIMATION', None)]:
399 self.ptcFitAndCheckPtc(
400 fitType=fitType,
401 order=order,
402 doTableArray=createArray,
403 doLegacy=doLegacy,
404 )
406 def test_meanVarMeasurement(self):
407 task = self.defaultTaskExtract
408 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(self.flatExp1,
409 self.flatExp2)
410 mu, varDiff, _ = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2)
412 self.assertLess(self.flatWidth - np.sqrt(varDiff), 1)
413 self.assertLess(self.flatMean - mu, 1)
415 def test_meanVarMeasurementWithNans(self):
416 task = self.defaultTaskExtract
418 flatExp1 = self.flatExp1.clone()
419 flatExp2 = self.flatExp2.clone()
421 flatExp1.image.array[20:30, :] = np.nan
422 flatExp2.image.array[20:30, :] = np.nan
424 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(flatExp1,
425 flatExp2)
426 mu, varDiff, _ = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2)
428 expectedMu1 = np.nanmean(flatExp1.image.array)
429 expectedMu2 = np.nanmean(flatExp2.image.array)
430 expectedMu = 0.5*(expectedMu1 + expectedMu2)
432 # Now the variance of the difference. First, create the diff image.
433 im1 = flatExp1.maskedImage
434 im2 = flatExp2.maskedImage
436 temp = im2.clone()
437 temp *= expectedMu1
438 diffIm = im1.clone()
439 diffIm *= expectedMu2
440 diffIm -= temp
441 diffIm /= expectedMu
443 # Divide by two as it is what measureMeanVarCov returns
444 # (variance of difference)
445 expectedVar = 0.5*np.nanvar(diffIm.image.array)
447 # Check that the standard deviations and the emans agree to
448 # less than 1 ADU
449 self.assertLess(np.sqrt(expectedVar) - np.sqrt(varDiff), 1)
450 self.assertLess(expectedMu - mu, 1)
452 def test_meanVarMeasurementAllNan(self):
453 task = self.defaultTaskExtract
454 flatExp1 = self.flatExp1.clone()
455 flatExp2 = self.flatExp2.clone()
457 flatExp1.image.array[:, :] = np.nan
458 flatExp2.image.array[:, :] = np.nan
460 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(flatExp1,
461 flatExp2)
462 mu, varDiff, covDiff = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2)
464 self.assertTrue(np.isnan(mu))
465 self.assertTrue(np.isnan(varDiff))
466 self.assertTrue(covDiff is None)
468 def test_meanVarMeasurementTooFewPixels(self):
469 task = self.defaultTaskExtract
470 flatExp1 = self.flatExp1.clone()
471 flatExp2 = self.flatExp2.clone()
473 flatExp1.image.array[0: 190, :] = np.nan
474 flatExp2.image.array[0: 190, :] = np.nan
476 bit = flatExp1.mask.getMaskPlaneDict()["NO_DATA"]
477 flatExp1.mask.array[0: 190, :] &= 2**bit
478 flatExp2.mask.array[0: 190, :] &= 2**bit
480 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(flatExp1,
481 flatExp2)
482 with self.assertLogs(level=logging.WARNING) as cm:
483 mu, varDiff, covDiff = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2)
484 self.assertIn("Number of good points", cm.output[0])
486 self.assertTrue(np.isnan(mu))
487 self.assertTrue(np.isnan(varDiff))
488 self.assertTrue(covDiff is None)
490 def test_meanVarMeasurementTooNarrowStrip(self):
491 # We need a new config to make sure the second covariance cut is
492 # triggered.
493 config = cpPipe.ptc.PhotonTransferCurveExtractTask.ConfigClass()
494 config.minNumberGoodPixelsForCovariance = 10
495 task = cpPipe.ptc.PhotonTransferCurveExtractTask(config=config)
496 flatExp1 = self.flatExp1.clone()
497 flatExp2 = self.flatExp2.clone()
499 flatExp1.image.array[0: 195, :] = np.nan
500 flatExp2.image.array[0: 195, :] = np.nan
501 flatExp1.image.array[:, 0: 195] = np.nan
502 flatExp2.image.array[:, 0: 195] = np.nan
504 bit = flatExp1.mask.getMaskPlaneDict()["NO_DATA"]
505 flatExp1.mask.array[0: 195, :] &= 2**bit
506 flatExp2.mask.array[0: 195, :] &= 2**bit
507 flatExp1.mask.array[:, 0: 195] &= 2**bit
508 flatExp2.mask.array[:, 0: 195] &= 2**bit
510 im1Area, im2Area, imStatsCtrl, mu1, mu2 = task.getImageAreasMasksStats(flatExp1,
511 flatExp2)
512 with self.assertLogs(level=logging.WARNING) as cm:
513 mu, varDiff, covDiff = task.measureMeanVarCov(im1Area, im2Area, imStatsCtrl, mu1, mu2)
514 self.assertIn("Not enough pixels", cm.output[0])
516 self.assertTrue(np.isnan(mu))
517 self.assertTrue(np.isnan(varDiff))
518 self.assertTrue(covDiff is None)
520 def test_makeZeroSafe(self):
521 noZerosArray = [1., 20, -35, 45578.98, 90.0, 897, 659.8]
522 someZerosArray = [1., 20, 0, 0, 90, 879, 0]
523 allZerosArray = [0., 0.0, 0, 0, 0.0, 0, 0]
525 substituteValue = 1e-10
527 expectedSomeZerosArray = [1., 20, substituteValue, substituteValue, 90, 879, substituteValue]
528 expectedAllZerosArray = np.repeat(substituteValue, len(allZerosArray))
530 measuredSomeZerosArray = self.defaultTaskSolve._makeZeroSafe(someZerosArray,
531 substituteValue=substituteValue)
532 measuredAllZerosArray = self.defaultTaskSolve._makeZeroSafe(allZerosArray,
533 substituteValue=substituteValue)
534 measuredNoZerosArray = self.defaultTaskSolve._makeZeroSafe(noZerosArray,
535 substituteValue=substituteValue)
537 for exp, meas in zip(expectedSomeZerosArray, measuredSomeZerosArray):
538 self.assertEqual(exp, meas)
539 for exp, meas in zip(expectedAllZerosArray, measuredAllZerosArray):
540 self.assertEqual(exp, meas)
541 for exp, meas in zip(noZerosArray, measuredNoZerosArray):
542 self.assertEqual(exp, meas)
544 def test_getInitialGoodPoints(self):
545 xs = [1, 2, 3, 4, 5, 6]
546 ys = [2*x for x in xs]
547 points = self.defaultTaskSolve._getInitialGoodPoints(xs, ys, minVarPivotSearch=0.,
548 consecutivePointsVarDecreases=2)
549 assert np.all(points) == np.all(np.array([True for x in xs]))
551 ys[4] = 7 # Variance decreases in two consecutive points after ys[3]=8
552 ys[5] = 6
553 points = self.defaultTaskSolve._getInitialGoodPoints(xs, ys, minVarPivotSearch=0.,
554 consecutivePointsVarDecreases=2)
555 assert np.all(points) == np.all(np.array([True, True, True, True, False]))
557 def runGetGainFromFlatPair(self, correctionType='NONE'):
558 extractConfig = self.defaultConfigExtract
559 extractConfig.gainCorrectionType = correctionType
560 extractConfig.minNumberGoodPixelsForCovariance = 5000
561 extractTask = cpPipe.ptc.PhotonTransferCurveExtractTask(config=extractConfig)
563 expDict = {}
564 expIds = []
565 idCounter = 0
566 inputGain = self.gain # 1.5 e/ADU
567 for expTime in self.timeVec:
568 # Approximation works better at low flux, e.g., < 10000 ADU
569 mockExp1, mockExp2 = makeMockFlats(expTime, gain=inputGain,
570 readNoiseElectrons=np.sqrt(self.noiseSq),
571 fluxElectrons=100,
572 expId1=idCounter, expId2=idCounter+1)
573 mockExpRef1 = PretendRef(mockExp1)
574 mockExpRef2 = PretendRef(mockExp2)
575 expDict[expTime] = ((mockExpRef1, idCounter), (mockExpRef2, idCounter+1))
576 expIds.append(idCounter)
577 expIds.append(idCounter+1)
578 idCounter += 2
580 resultsExtract = extractTask.run(inputExp=expDict, inputDims=expIds,
581 taskMetadata=[self.metadataContents for x in expIds])
582 for exposurePair in resultsExtract.outputCovariances:
583 for ampName in self.ampNames:
584 if exposurePair.gain[ampName] is np.nan:
585 continue
586 self.assertAlmostEqual(exposurePair.gain[ampName], inputGain, delta=0.04)
588 def test_getGainFromFlatPair(self):
589 for gainCorrectionType in ['NONE', 'SIMPLE', 'FULL', ]:
590 self.runGetGainFromFlatPair(gainCorrectionType)
593class MeasurePhotonTransferCurveDatasetTestCase(lsst.utils.tests.TestCase):
594 def setUp(self):
595 self.ptcData = PhotonTransferCurveDataset(['C00', 'C01'], " ")
596 self.ptcData.inputExpIdPairs = {'C00': [(123, 234), (345, 456), (567, 678)],
597 'C01': [(123, 234), (345, 456), (567, 678)]}
599 def test_generalBehaviour(self):
600 test = PhotonTransferCurveDataset(['C00', 'C01'], " ")
601 test.inputExpIdPairs = {'C00': [(123, 234), (345, 456), (567, 678)],
602 'C01': [(123, 234), (345, 456), (567, 678)]}
605class TestMemory(lsst.utils.tests.MemoryTestCase):
606 pass
609def setup_module(module):
610 lsst.utils.tests.init()
613if __name__ == "__main__": 613 ↛ 614line 613 didn't jump to line 614, because the condition on line 613 was never true
614 lsst.utils.tests.init()
615 unittest.main()