Coverage for tests/test_crosstalk.py : 18%

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# This file is part of ip_isr.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import unittest
23import itertools
24import tempfile
26import numpy as np
28import lsst.geom
29import lsst.utils.tests
30import lsst.afw.image
31import lsst.afw.table
32import lsst.afw.cameraGeom as cameraGeom
34from lsst.pipe.base import Struct
35from lsst.ip.isr import (IsrTask, subtractCrosstalk,
36 MeasureCrosstalkTask, writeCrosstalkCoeffs, CrosstalkTask, NullCrosstalkTask)
38try:
39 display
40except NameError:
41 display = False
42else:
43 import lsst.afw.display as afwDisplay
44 afwDisplay.setDefaultMaskTransparency(75)
47outputName = None # specify a name (as a string) to save the output crosstalk coeffs.
50class CrosstalkTestCase(lsst.utils.tests.TestCase):
51 def setUp(self):
52 width, height = 250, 500
53 self.numAmps = 4
54 numPixelsPerAmp = 1000
55 # crosstalk[i][j] is the fraction of the j-th amp present on the i-th amp.
56 self.crosstalk = [[0.0, 1e-4, 2e-4, 3e-4],
57 [3e-4, 0.0, 2e-4, 1e-4],
58 [4e-4, 5e-4, 0.0, 6e-4],
59 [7e-4, 8e-4, 9e-4, 0.0]]
60 self.value = 12345
61 self.crosstalkStr = "XTLK"
63 # A bit of noise is important, because otherwise the pixel distributions are razor-thin
64 # and then rejection doesn't work
65 rng = np.random.RandomState(12345)
66 self.noise = rng.normal(0.0, 0.1, (2*height, 2*width))
68 # Create amp images
69 withoutCrosstalk = [lsst.afw.image.ImageF(width, height) for _ in range(self.numAmps)]
70 for image in withoutCrosstalk:
71 image.set(0)
72 xx = rng.randint(0, width, numPixelsPerAmp)
73 yy = rng.randint(0, height, numPixelsPerAmp)
74 image.getArray()[yy, xx] = self.value
76 # Add in crosstalk
77 withCrosstalk = [image.Factory(image, True) for image in withoutCrosstalk]
78 for ii, iImage in enumerate(withCrosstalk):
79 for jj, jImage in enumerate(withoutCrosstalk):
80 value = self.crosstalk[ii][jj]
81 iImage.scaledPlus(value, jImage)
83 # Put amp images together
84 def construct(imageList):
85 image = lsst.afw.image.ImageF(2*width, 2*height)
86 image.getArray()[:height, :width] = imageList[0].getArray()
87 image.getArray()[:height, width:] = imageList[1].getArray()[:, ::-1] # flip in x
88 image.getArray()[height:, :width] = imageList[2].getArray()[::-1, :] # flip in y
89 image.getArray()[height:, width:] = imageList[3].getArray()[::-1, ::-1] # flip in x and y
90 image.getArray()[:] += self.noise
91 return image
93 # Construct detector
94 detName = 'detector'
95 detId = 1
96 detSerial = 'serial'
97 orientation = cameraGeom.Orientation()
98 pixelSize = lsst.geom.Extent2D(1, 1)
99 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
100 lsst.geom.Extent2I(2*width, 2*height))
101 crosstalk = np.array(self.crosstalk, dtype=np.float32)
103 camBuilder = cameraGeom.Camera.Builder("fakeCam")
104 detBuilder = camBuilder.add(detName, detId)
105 detBuilder.setSerial(detSerial)
106 detBuilder.setBBox(bbox)
107 detBuilder.setOrientation(orientation)
108 detBuilder.setPixelSize(pixelSize)
109 detBuilder.setCrosstalk(crosstalk)
111 # Create amp info
112 for ii, (xx, yy, corner) in enumerate([(0, 0, lsst.afw.cameraGeom.ReadoutCorner.LL),
113 (width, 0, lsst.afw.cameraGeom.ReadoutCorner.LR),
114 (0, height, lsst.afw.cameraGeom.ReadoutCorner.UL),
115 (width, height, lsst.afw.cameraGeom.ReadoutCorner.UR)]):
117 amp = cameraGeom.Amplifier.Builder()
118 amp.setName("amp %d" % ii)
119 amp.setBBox(lsst.geom.Box2I(lsst.geom.Point2I(xx, yy),
120 lsst.geom.Extent2I(width, height)))
121 amp.setRawDataBBox(lsst.geom.Box2I(lsst.geom.Point2I(xx, yy),
122 lsst.geom.Extent2I(width, height)))
123 amp.setReadoutCorner(corner)
124 detBuilder.append(amp)
126 cam = camBuilder.finish()
127 ccd = cam.get('detector')
129 self.exposure = lsst.afw.image.makeExposure(lsst.afw.image.makeMaskedImage(construct(withCrosstalk)))
130 self.exposure.setDetector(ccd)
132 self.corrected = construct(withoutCrosstalk)
134 if display:
135 disp = lsst.afw.display.Display(frame=1)
136 disp.mtv(self.exposure, title="exposure")
137 disp = lsst.afw.display.Display(frame=0)
138 disp.mtv(self.corrected, title="corrected exposure")
140 def tearDown(self):
141 del self.exposure
142 del self.corrected
144 def checkCoefficients(self, coeff, coeffErr, coeffNum):
145 """Check that coefficients are as expected
147 Parameters
148 ----------
149 coeff : `numpy.ndarray`
150 Crosstalk coefficients.
151 coeffErr : `numpy.ndarray`
152 Crosstalk coefficient errors.
153 coeffNum : `numpy.ndarray`
154 Number of pixels to produce each coefficient.
155 """
156 for matrix in (coeff, coeffErr, coeffNum):
157 self.assertEqual(matrix.shape, (self.numAmps, self.numAmps))
158 self.assertFloatsAlmostEqual(coeff, np.array(self.crosstalk), atol=1.0e-6)
160 for ii in range(self.numAmps):
161 self.assertEqual(coeff[ii, ii], 0.0)
162 self.assertTrue(np.isnan(coeffErr[ii, ii]))
163 self.assertEqual(coeffNum[ii, ii], 1)
165 self.assertTrue(np.all(coeffErr[ii, jj] > 0 for ii, jj in
166 itertools.product(range(self.numAmps), range(self.numAmps)) if ii != jj))
167 self.assertTrue(np.all(coeffNum[ii, jj] > 0 for ii, jj in
168 itertools.product(range(self.numAmps), range(self.numAmps)) if ii != jj))
170 def checkSubtracted(self, exposure):
171 """Check that the subtracted image is as expected
173 Parameters
174 ----------
175 exposure : `lsst.afw.image.Exposure`
176 Crosstalk-subtracted exposure.
177 """
178 image = exposure.getMaskedImage().getImage()
179 mask = exposure.getMaskedImage().getMask()
180 self.assertFloatsAlmostEqual(image.getArray(), self.corrected.getArray(), atol=2.0e-2)
181 self.assertIn(self.crosstalkStr, mask.getMaskPlaneDict())
182 self.assertGreater((mask.getArray() & mask.getPlaneBitMask(self.crosstalkStr) > 0).sum(), 0)
184 def testDirectAPI(self):
185 """Test that individual function calls work"""
186 config = MeasureCrosstalkTask.ConfigClass()
187 measure = MeasureCrosstalkTask(config=config)
188 ratios = measure.extractCrosstalkRatios(self.exposure, threshold=self.value - 1)
189 coeff, coeffErr, coeffNum = measure.measureCrosstalkCoefficients(ratios)
190 self.checkCoefficients(coeff, coeffErr, coeffNum)
191 subtractCrosstalk(self.exposure, minPixelToMask=self.value - 1,
192 crosstalkStr=self.crosstalkStr)
193 self.checkSubtracted(self.exposure)
195 outPath = tempfile.mktemp() if outputName is None else "{}-isrCrosstalk".format(outputName)
196 writeCrosstalkCoeffs(outPath, coeff, det=None, crosstalkName="testDirectAPI", indent=2)
198 def testTaskAPI(self):
199 """Test that the Tasks work
201 Checks both MeasureCrosstalkTask and the CrosstalkTask.
202 """
203 # make exposure available to NullIsrTask
204 # without NullIsrTask's `self` hiding this test class's `self`
205 exposure = self.exposure
207 class NullIsrTask(IsrTask):
208 def runDataRef(self, dataRef):
209 return Struct(exposure=exposure)
211 config = MeasureCrosstalkTask.ConfigClass()
212 config.isr.retarget(NullIsrTask)
213 config.threshold = self.value - 1
214 measure = MeasureCrosstalkTask(config=config)
215 fakeDataRef = Struct(dataId={'fake': 1})
216 coeff, coeffErr, coeffNum = measure.reduce([measure.runDataRef(fakeDataRef)])
217 self.checkCoefficients(coeff, coeffErr, coeffNum)
219 config = IsrTask.ConfigClass()
220 config.crosstalk.minPixelToMask = self.value - 1
221 config.crosstalk.crosstalkMaskPlane = self.crosstalkStr
222 isr = IsrTask(config=config)
223 isr.crosstalk.run(self.exposure)
224 self.checkSubtracted(self.exposure)
226 def test_prepCrosstalk(self):
227 """Test that prep crosstalk does not error when given a dataRef with no
228 crosstalkSources to find.
229 """
230 dataRef = Struct(dataId={'fake': 1})
231 task = CrosstalkTask()
232 result = task.prepCrosstalk(dataRef)
233 self.assertIsNone(result)
235 def test_nullCrosstalkTask(self):
236 """Test that the null crosstalk task does not create an error.
237 """
238 exposure = self.exposure
239 task = NullCrosstalkTask()
240 result = task.run(exposure, crosstalkSources=None)
241 self.assertIsNone(result)
244class MemoryTester(lsst.utils.tests.MemoryTestCase):
245 pass
248def setup_module(module):
249 lsst.utils.tests.init()
252if __name__ == "__main__": 252 ↛ 253line 252 didn't jump to line 253, because the condition on line 252 was never true
253 import sys
254 setup_module(sys.modules[__name__])
255 unittest.main()