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 task.fitPtcAndNonLinearity(localDataset, ptcFitType='POLYNOMIAL')
110 for ampName in self.ampNames:
111 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
112 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName])
113 # Linearity residual should be zero
114 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0)
116 def test_ptcFitCubic(self):
117 localDataset = copy.copy(self.dataset)
118 for ampName in self.ampNames:
119 localDataset.rawVars[ampName] = [self.noiseSq + self.c1*mu + self.c2*mu**2 + self.c3*mu**3 for
120 mu in localDataset.rawMeans[ampName]]
122 config = copy.copy(self.defaultConfig)
123 config.polynomialFitDegree = 3
125 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config)
127 task.fitPtcAndNonLinearity(localDataset, ptcFitType='POLYNOMIAL')
129 for ampName in self.ampNames:
130 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
131 self.assertAlmostEqual(np.sqrt(self.noiseSq)*self.gain, localDataset.noise[ampName])
132 # Linearity residual should be zero
133 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0)
135 def test_ptcFitAstier(self):
136 localDataset = copy.copy(self.dataset)
137 g = self.gain # next line is too long without this shorthand!
138 for ampName in self.ampNames:
139 localDataset.rawVars[ampName] = [(0.5/(self.a00*g**2)*(np.exp(2*self.a00*mu*g)-1) +
140 self.noiseSq/(g*g)) for mu in localDataset.rawMeans[ampName]]
142 config = copy.copy(self.defaultConfig)
143 task = cpPipe.ptc.MeasurePhotonTransferCurveTask(config=config)
145 task.fitPtcAndNonLinearity(localDataset, ptcFitType='ASTIERAPPROXIMATION')
147 for ampName in self.ampNames:
148 self.assertAlmostEqual(self.gain, localDataset.gain[ampName])
149 # noise already comes out of the fit in electrons with Astier
150 self.assertAlmostEqual(np.sqrt(self.noiseSq), localDataset.noise[ampName])
151 # Linearity residual should be zero
152 self.assertTrue(localDataset.nonLinearityResiduals[ampName].all() == 0)
154 def test_meanVarMeasurement(self):
155 task = self.defaultTask
156 mu, varDiff = task.measureMeanVarPair(self.flatExp1, self.flatExp2)
158 self.assertLess(self.flatWidth - np.sqrt(varDiff), 1)
159 self.assertLess(self.flatMean - mu, 1)
161 def test_getInitialGoodPoints(self):
162 xs = [1, 2, 3, 4, 5, 6]
163 ys = [2*x for x in xs]
164 points = self.defaultTask._getInitialGoodPoints(xs, ys, 0.1, 0.25)
165 assert np.all(points) == np.all(np.array([True for x in xs]))
167 ys[-1] = 30
168 points = self.defaultTask._getInitialGoodPoints(xs, ys, 0.1, 0.25)
169 assert np.all(points) == np.all(np.array([True, True, True, True, False]))
171 ys = [2*x for x in xs]
172 newYs = copy.copy(ys)
173 results = [False, True, True, False, False]
174 for i, factor in enumerate([-0.5, -0.1, 0, 0.1, 0.5]):
175 newYs[-1] = ys[-1] + (factor*ys[-1])
176 points = self.defaultTask._getInitialGoodPoints(xs, newYs, 0.05, 0.25)
177 assert (np.all(points[0:-1]) == True) # noqa: E712 - flake8 is wrong here because of numpy.bool
178 assert points[-1] == results[i]
180 def test_getVisitsUsed(self):
181 localDataset = copy.copy(self.dataset)
183 for pair in [(12, 34), (56, 78), (90, 10)]:
184 localDataset.inputVisitPairs["C:0,0"].append(pair)
185 localDataset.visitMask["C:0,0"] = np.array([True, False, True])
186 self.assertTrue(np.all(localDataset.getVisitsUsed("C:0,0") == [(12, 34), (90, 10)]))
188 localDataset.visitMask["C:0,0"] = np.array([True, False, True, True]) # wrong length now
189 with self.assertRaises(AssertionError):
190 localDataset.getVisitsUsed("C:0,0")
192 def test_getGoodAmps(self):
193 dataset = self.dataset
195 self.assertTrue(dataset.ampNames == self.ampNames)
196 dataset.badAmps.append("C:0,1")
197 self.assertTrue(dataset.getGoodAmps() == [amp for amp in self.ampNames if amp != "C:0,1"])
200class MeasurePhotonTransferCurveDatasetTestCase(lsst.utils.tests.TestCase):
201 def setUp(self):
202 self.ptcData = PhotonTransferCurveDataset(['C00', 'C01'])
203 self.ptcData.inputVisitPairs = {'C00': [(123, 234), (345, 456), (567, 678)],
204 'C01': [(123, 234), (345, 456), (567, 678)]}
206 def test_generalBehaviour(self):
207 test = PhotonTransferCurveDataset(['C00', 'C01'])
208 test.inputVisitPairs = {'C00': [(123, 234), (345, 456), (567, 678)],
209 'C01': [(123, 234), (345, 456), (567, 678)]}
211 with self.assertRaises(AttributeError):
212 test.newItem = 1
215class TestMemory(lsst.utils.tests.MemoryTestCase):
216 pass
219def setup_module(module):
220 lsst.utils.tests.init()
223if __name__ == "__main__": 223 ↛ 224line 223 didn't jump to line 224, because the condition on line 223 was never true
224 lsst.utils.tests.init()
225 unittest.main()