Coverage for tests/test_ptc.py : 15%

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
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."""
27from __future__ import absolute_import, division, print_function
28import unittest
29import numpy as np
30import copy
32import lsst.utils
33import lsst.utils.tests
35import lsst.cp.pipe as cpPipe
36import lsst.ip.isr.isrMock as isrMock
37from lsst.cp.pipe.ptc import PhotonTransferCurveDataset
40class MeasurePhotonTransferCurveTaskTestCase(lsst.utils.tests.TestCase):
41 """A test case for the PTC task."""
43 def setUp(self):
44 self.defaultConfig = cpPipe.ptc.MeasurePhotonTransferCurveTask.ConfigClass()
45 self.defaultConfig.isr.doFlat = False
46 self.defaultConfig.isr.doFringe = False
47 self.defaultConfig.isr.doCrosstalk = False
48 self.defaultConfig.isr.doAddDistortionModel = False
49 self.defaultConfig.isr.doUseOpticsTransmission = False
50 self.defaultConfig.isr.doUseFilterTransmission = False
51 self.defaultConfig.isr.doUseSensorTransmission = False
52 self.defaultConfig.isr.doUseAtmosphereTransmission = False
53 self.defaultConfig.isr.doAttachTransmissionCurve = False
55 self.defaultTask = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=self.defaultConfig)
57 self.flatMean = 2000
58 self.readNoiseAdu = 10
59 mockImageConfig = isrMock.IsrMock.ConfigClass()
61 # flatDrop is not really relevant as we replace the data
62 # but good to note it in case we change how this image is made
63 mockImageConfig.flatDrop = 0.99999
64 mockImageConfig.isTrimmed = True
66 self.flatExp1 = isrMock.FlatMock(config=mockImageConfig).run()
67 self.flatExp2 = self.flatExp1.clone()
68 (shapeY, shapeX) = self.flatExp1.getDimensions()
70 self.flatWidth = np.sqrt(self.flatMean) + self.readNoiseAdu
72 self.rng1 = np.random.RandomState(1984)
73 flatData1 = self.rng1.normal(self.flatMean, self.flatWidth, (shapeX, shapeY))
74 self.rng2 = np.random.RandomState(666)
75 flatData2 = self.rng2.normal(self.flatMean, self.flatWidth, (shapeX, shapeY))
77 self.flatExp1.image.array[:] = flatData1
78 self.flatExp2.image.array[:] = flatData2
80 # create fake PTC data to see if fit works, for one amp ('amp')
81 flux = 1000 # ADU/sec
82 timeVec = np.arange(1., 201.)
83 muVec = flux*timeVec # implies that signal-chain non-linearity is zero
84 self.gain = 1.5 # e-/ADU
85 self.c1 = 1./self.gain
86 self.noiseSq = 5*self.gain # 7.5 (e-)^2
87 self.a00 = -1.2e-6
88 self.c2 = -1.5e-6
89 self.c3 = -4.7e-12 # tuned so that it turns over for 200k mean
91 self.ampNames = [amp.getName() for amp in self.flatExp1.getDetector().getAmplifiers()]
92 self.dataset = PhotonTransferCurveDataset(self.ampNames) # pack raw data for fitting
94 for ampName in self.ampNames: # just the expTimes and means here - vars vary per function
95 self.dataset.rawExpTimes[ampName] = timeVec
96 self.dataset.rawMeans[ampName] = muVec
98 def test_ptcFitQuad(self):
99 localDataset = copy.copy(self.dataset)
100 for ampName in self.ampNames:
101 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 for
102 mu in localDataset.rawMeans[ampName]]
104 config = copy.copy(self.defaultConfig)
105 config.polynomialFitDegree = 2
106 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config)
108 numberAmps = len(self.ampNames)
109 numberAduValues = config.maxAduForLookupTableLinearizer
110 lookupTableArray = np.zeros((numberAmps, numberAduValues), dtype=np.float32)
112 task.fitPtcAndNonLinearity(localDataset, lookupTableArray, ptcFitType='POLYNOMIAL')
114 for ampName in self.ampNames:
115 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
116 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName])
117 # Linearity residual should be zero
118 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0)
120 def test_ptcFitCubic(self):
121 localDataset = copy.copy(self.dataset)
122 for ampName in self.ampNames:
123 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 + self.c3*mu**3 for
124 mu in localDataset.rawMeans[ampName]]
126 config = copy.copy(self.defaultConfig)
127 config.polynomialFitDegree = 3
129 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config)
131 numberAmps = len(self.ampNames)
132 numberAduValues = config.maxAduForLookupTableLinearizer
133 lookupTableArray = np.zeros((numberAmps, numberAduValues), dtype=np.float32)
135 task.fitPtcAndNonLinearity(localDataset, lookupTableArray, ptcFitType='POLYNOMIAL')
137 for ampName in self.ampNames:
138 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
139 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName])
140 # Linearity residual should be zero
141 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0)
143 def test_ptcFitAstier(self):
144 localDataset = copy.copy(self.dataset)
145 g = self.gain # next line is too long without this shorthand!
146 for ampName in self.ampNames:
147 localDataset.rawVars[ampName] = [(0.5/(self.a00*g**2)*(np.exp(2*self.a00*mu*g)-1) +
148 self.noiseSq/(g*g)) for mu in localDataset.rawMeans[ampName]]
150 config = copy.copy(self.defaultConfig)
151 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config)
153 numberAmps = len(self.ampNames)
154 numberAduValues = config.maxAduForLookupTableLinearizer
155 lookupTableArray = np.zeros((numberAmps, numberAduValues), dtype=np.float32)
157 task.fitPtcAndNonLinearity(localDataset, lookupTableArray, ptcFitType='ASTIERAPPROXIMATION')
159 for ampName in self.ampNames:
160 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
161 # noise already comes out of the fit in electrons with Astier
162 self.assertAlmostEqual(np.sqrt(self.noiseSq), localDataset.noise[ampName])
163 # Linearity residual should be zero
164 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0)
166 def test_meanVarMeasurement(self):
167 task = self.defaultTask
168 mu, varDiff = task.measureMeanVarPair(self.flatExp1, self.flatExp2)
170 self.assertLess(self.flatWidth - np.sqrt(varDiff), 1)
171 self.assertLess(self.flatMean - mu, 1)
173 def test_getInitialGoodPoints(self):
174 xs = [1, 2, 3, 4, 5, 6]
175 ys = [2*x for x in xs]
176 points = self.defaultTask._getInitialGoodPoints(xs, ys, 0.1, 0.25)
177 assert np.all(points) == np.all(np.array([True for x in xs]))
179 ys[-1] = 30
180 points = self.defaultTask._getInitialGoodPoints(xs, ys, 0.1, 0.25)
181 assert np.all(points) == np.all(np.array([True, True, True, True, False]))
183 ys = [2*x for x in xs]
184 newYs = copy.copy(ys)
185 results = [False, True, True, False, False]
186 for i, factor in enumerate([-0.5, -0.1, 0, 0.1, 0.5]):
187 newYs[-1] = ys[-1] + (factor*ys[-1])
188 points = self.defaultTask._getInitialGoodPoints(xs, newYs, 0.05, 0.25)
189 assert (np.all(points[0:-1]) == True) # noqa: E712 - flake8 is wrong here because of numpy.bool
190 assert points[-1] == results[i]
192 def test_getVisitsUsed(self):
193 localDataset = copy.copy(self.dataset)
195 for pair in [(12, 34), (56, 78), (90, 10)]:
196 localDataset.inputVisitPairs["C:0,0"].append(pair)
197 localDataset.visitMask["C:0,0"] = np.array([True, False, True])
198 self.assertTrue(np.all(localDataset.getVisitsUsed("C:0,0") == [(12, 34), (90, 10)]))
200 localDataset.visitMask["C:0,0"] = np.array([True, False, True, True]) # wrong length now
201 with self.assertRaises(AssertionError):
202 localDataset.getVisitsUsed("C:0,0")
204 def test_getGoodAmps(self):
205 dataset = self.dataset
207 self.assertTrue(dataset.ampNames == self.ampNames)
208 dataset.badAmps.append("C:0,1")
209 self.assertTrue(dataset.getGoodAmps() == [amp for amp in self.ampNames if amp != "C:0,1"])
212class MeasurePhotonTransferCurveDatasetTestCase(lsst.utils.tests.TestCase):
213 def setUp(self):
214 self.ptcData = PhotonTransferCurveDataset(['C00', 'C01'])
215 self.ptcData.inputVisitPairs = {'C00': [(123, 234), (345, 456), (567, 678)],
216 'C01': [(123, 234), (345, 456), (567, 678)]}
218 def test_generalBehaviour(self):
219 test = PhotonTransferCurveDataset(['C00', 'C01'])
220 test.inputVisitPairs = {'C00': [(123, 234), (345, 456), (567, 678)],
221 'C01': [(123, 234), (345, 456), (567, 678)]}
223 with self.assertRaises(AttributeError):
224 test.newItem = 1
227class TestMemory(lsst.utils.tests.MemoryTestCase):
228 pass
231def setup_module(module):
232 lsst.utils.tests.init()
235if __name__ == "__main__": 235 ↛ 236line 235 didn't jump to line 236, because the condition on line 235 was never true
236 lsst.utils.tests.init()
237 unittest.main()