Coverage for tests/test_amplifier.py: 15%
111 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-12 02:30 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-12 02:30 -0800
1# This file is part of afw.
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/>.
22from types import SimpleNamespace
23import unittest
24import numpy as np
26import lsst.utils.tests
27import lsst.geom
28from lsst.afw.cameraGeom import Amplifier, AmplifierGeometryComparison, ReadoutCorner
29import lsst.afw.cameraGeom.testUtils # for assert methods injected into TestCase
32class AmplifierTestCase(lsst.utils.tests.TestCase):
34 def setUp(self):
35 self.data = SimpleNamespace(
36 name="Amp1",
37 gain=1.2345,
38 saturation=65535,
39 readNoise=-0.523,
40 linearityCoeffs=np.array([1.1, 2.2, 3.3, 4.4], dtype=float),
41 linearityType="Polynomial",
42 bbox=lsst.geom.Box2I(lsst.geom.Point2I(3, -2), lsst.geom.Extent2I(231, 320)),
43 rawFlipX=True,
44 rawFlipY=False,
45 readoutCorner=ReadoutCorner.UL,
46 rawBBox=lsst.geom.Box2I(lsst.geom.Point2I(-25, 2), lsst.geom.Extent2I(550, 629)),
47 rawXYOffset=lsst.geom.Extent2I(-97, 253),
48 rawDataBBox=lsst.geom.Box2I(lsst.geom.Point2I(-2, 29), lsst.geom.Extent2I(123, 307)),
49 rawHorizontalOverscanBBox=lsst.geom.Box2I(
50 lsst.geom.Point2I(150, 29),
51 lsst.geom.Extent2I(25, 307),
52 ),
53 rawVerticalOverscanBBox=lsst.geom.Box2I(
54 lsst.geom.Point2I(-2, 201),
55 lsst.geom.Extent2I(123, 6),
56 ),
57 rawPrescanBBox=lsst.geom.Box2I(
58 lsst.geom.Point2I(-20, 2),
59 lsst.geom.Extent2I(5, 307),
60 ),
61 )
62 builder = Amplifier.Builder()
63 builder.setBBox(self.data.bbox)
64 builder.setName(self.data.name)
65 builder.setGain(self.data.gain)
66 builder.setSaturation(self.data.saturation)
67 builder.setReadNoise(self.data.readNoise)
68 builder.setReadoutCorner(self.data.readoutCorner)
69 builder.setLinearityCoeffs(self.data.linearityCoeffs)
70 builder.setLinearityType(self.data.linearityType)
71 builder.setRawFlipX(self.data.rawFlipX)
72 builder.setRawFlipY(self.data.rawFlipY)
73 builder.setRawBBox(self.data.rawBBox)
74 builder.setRawXYOffset(self.data.rawXYOffset)
75 builder.setRawDataBBox(self.data.rawDataBBox)
76 builder.setRawHorizontalOverscanBBox(self.data.rawHorizontalOverscanBBox)
77 builder.setRawVerticalOverscanBBox(self.data.rawVerticalOverscanBBox)
78 builder.setRawPrescanBBox(self.data.rawPrescanBBox)
79 self.amplifier = builder.finish()
81 def testBasics(self):
82 self.assertEqual(self.data.name, self.amplifier.getName())
83 self.assertEqual(self.data.gain, self.amplifier.getGain())
84 self.assertEqual(self.data.saturation, self.amplifier.getSaturation())
85 self.assertEqual(self.data.readNoise, self.amplifier.getReadNoise())
86 self.assertEqual(self.data.readoutCorner, self.amplifier.getReadoutCorner())
87 self.assertEqual(list(self.data.linearityCoeffs), list(self.amplifier.getLinearityCoeffs()))
88 self.assertEqual(self.data.linearityType, self.amplifier.getLinearityType())
89 self.assertEqual(self.data.bbox, self.amplifier.getBBox())
90 self.assertEqual(self.data.rawBBox, self.amplifier.getRawBBox())
91 self.assertEqual(self.data.rawDataBBox, self.amplifier.getRawDataBBox())
92 self.assertEqual(self.data.rawHorizontalOverscanBBox, self.amplifier.getRawHorizontalOverscanBBox())
93 self.assertEqual(self.data.rawVerticalOverscanBBox, self.amplifier.getRawVerticalOverscanBBox())
94 self.assertEqual(self.data.rawPrescanBBox, self.amplifier.getRawPrescanBBox())
95 self.assertEqual(self.data.rawHorizontalOverscanBBox, self.amplifier.getRawSerialOverscanBBox())
96 self.assertEqual(self.data.rawVerticalOverscanBBox, self.amplifier.getRawParallelOverscanBBox())
97 self.assertEqual(self.data.rawPrescanBBox, self.amplifier.getRawSerialPrescanBBox())
98 self.assertEqual(self.data.rawPrescanBBox, self.amplifier.getRawHorizontalPrescanBBox())
99 self.assertEqual(self.data.rawFlipX, self.amplifier.getRawFlipX())
100 self.assertEqual(self.data.rawFlipY, self.amplifier.getRawFlipY())
101 self.assertEqual(self.data.rawXYOffset, self.amplifier.getRawXYOffset())
103 # Test get/set methods for overscan/prescan alias names.
104 # Change slightly, don't care about contiguity, make smaller.
105 newHorizontalOverscanBBox = lsst.geom.Box2I(
106 lsst.geom.Point2I(150, 29), lsst.geom.Extent2I(25, 306))
107 newVerticalOverscanBBox = lsst.geom.Box2I(
108 lsst.geom.Point2I(-2, 201), lsst.geom.Extent2I(123, 5))
109 newPrescanBBox = lsst.geom.Box2I(
110 lsst.geom.Point2I(-20, 2), lsst.geom.Extent2I(4, 306))
112 builder = self.amplifier.rebuild()
113 builder.setRawSerialOverscanBBox(newHorizontalOverscanBBox)
114 builder.setRawParallelOverscanBBox(newVerticalOverscanBBox)
115 builder.setRawSerialPrescanBBox(newPrescanBBox)
116 amplifier = builder.finish()
118 self.assertEqual(newHorizontalOverscanBBox,
119 amplifier.getRawHorizontalOverscanBBox())
120 self.assertEqual(newVerticalOverscanBBox,
121 amplifier.getRawVerticalOverscanBBox())
122 self.assertEqual(newPrescanBBox,
123 amplifier.getRawPrescanBBox())
125 newPrescanBBox2 = lsst.geom.Box2I(
126 lsst.geom.Point2I(-20, 2), lsst.geom.Extent2I(5, 306))
127 builder.setRawHorizontalPrescanBBox(newPrescanBBox2)
128 amplifier = builder.finish()
129 self.assertEqual(newPrescanBBox2,
130 amplifier.getRawPrescanBBox())
132 def test_compareGeometry(self):
133 """Test the `Amplifier.compareGeometry` method.
135 This doesn't handle the case where the amplifiers have the same
136 regions but different assembly state (and hence the regions look
137 different until they are transformed). That test is in test_transform.
138 """
139 Cmp = AmplifierGeometryComparison
141 def test_combos(rhs, expected):
142 """Test all combinations of flag kwargs to compareGeometry.
144 Parameters
145 ----------
146 rhs : `Amplifier`
147 RHS of comparison (LHS is always self.amplifier).
148 expected : `list` of `AmplifierGeometryComparison`
149 Expected results for comparison (just look at the code for
150 order).
151 """
152 self.assertIs(self.amplifier.compareGeometry(rhs), expected[0])
153 self.assertIs(self.amplifier.compareGeometry(rhs, assembly=False), expected[1])
154 self.assertIs(self.amplifier.compareGeometry(rhs, regions=False), expected[2])
155 self.assertIs(self.amplifier.compareGeometry(rhs, assembly=False, regions=False), Cmp.EQUAL)
157 def modified(**kwargs):
158 """Return an Amplifier by modifying a copy of ``self.amplifier``.
160 Keyword arguments encode the name of an `Amplifier.Builder` setter
161 method in the key and the value for that setter in the value.
162 """
163 builder = self.amplifier.rebuild()
164 for k, v in kwargs.items():
165 getattr(builder, k)(v)
166 return builder.rebuild()
168 # Comparing an amplifier to itself always returns EQUAL.
169 test_combos(self.amplifier, [Cmp.EQUAL]*4)
170 # If we modify a flip or shift parameter we notice iff assembly=true.
171 # If regions=True as well, then those compare as different because
172 # the method tries to transform to the same assembly state and that
173 # makes them different.
174 test_combos(modified(setRawFlipX=not self.data.rawFlipX),
175 [Cmp.FLIPPED_X | Cmp.REGIONS_DIFFER, Cmp.EQUAL, Cmp.FLIPPED_X])
176 test_combos(modified(setRawFlipY=not self.data.rawFlipY),
177 [Cmp.FLIPPED_Y | Cmp.REGIONS_DIFFER, Cmp.EQUAL, Cmp.FLIPPED_Y])
178 test_combos(modified(setRawXYOffset=self.data.rawXYOffset + lsst.geom.Extent2I(4, 5)),
179 [Cmp.SHIFTED_X | Cmp.SHIFTED_Y | Cmp.REGIONS_DIFFER, Cmp.EQUAL,
180 Cmp.SHIFTED_X | Cmp.SHIFTED_Y])
181 # If we modify or data box we notice iff regions=True. We modify them
182 # all by shifting by the same amount because the Amplifier constructor
183 # would be within its rights to raise if these didn't line up properly,
184 # and nontrivial modifications that are valid are a lot harder to
185 # write.
186 offset = lsst.geom.Extent2I(-2, 3)
187 test_combos(
188 modified(
189 setRawBBox=self.data.rawBBox.shiftedBy(offset),
190 setRawDataBBox=self.data.rawDataBBox.shiftedBy(offset),
191 setRawHorizontalOverscanBBox=self.data.rawHorizontalOverscanBBox.shiftedBy(offset),
192 setRawVerticalOverscanBBox=self.data.rawVerticalOverscanBBox.shiftedBy(offset),
193 setRawPrescanBBox=self.data.rawPrescanBBox.shiftedBy(offset),
194 ),
195 [Cmp.REGIONS_DIFFER, Cmp.REGIONS_DIFFER, Cmp.EQUAL, Cmp.EQUAL],
196 )
198 def test_transform(self):
199 """Test the `Amplifier.Builder.transform` method and
200 `Amplifier.compareGeometry` cases that involve post-transform
201 comparisons.
202 """
203 # Standard case: no kwargs to transform, so we apply flips and
204 # rawXYOffset shifts. This should leave no flip or offset in the
205 # returned amplifier.
206 assembled_amp = self.amplifier.rebuild().transform().finish()
207 self.assertFalse(assembled_amp.getRawFlipX())
208 self.assertFalse(assembled_amp.getRawFlipY())
209 self.assertEqual(assembled_amp.getRawXYOffset(), lsst.geom.Extent2I(0, 0))
210 self.assertEqual(assembled_amp.getReadoutCorner(), ReadoutCorner.UR)
211 comparison = self.amplifier.compareGeometry(assembled_amp)
212 self.assertEqual(bool(comparison & comparison.FLIPPED_X), self.data.rawFlipX)
213 self.assertEqual(bool(comparison & comparison.FLIPPED_Y), self.data.rawFlipY)
214 self.assertEqual(bool(comparison & comparison.SHIFTED),
215 self.data.rawXYOffset != lsst.geom.Extent2I(0, 0))
216 self.assertFalse(comparison & comparison.REGIONS_DIFFER)
217 # Use transform to round-trip the assembled amp back to the original.
218 roundtripped = assembled_amp.rebuild().transform(
219 outOffset=self.data.rawXYOffset,
220 outFlipX=self.data.rawFlipX,
221 outFlipY=self.data.rawFlipY,
222 ).finish()
223 self.assertAmplifiersEqual(self.amplifier, roundtripped)
224 self.assertIs(self.amplifier.compareGeometry(roundtripped), AmplifierGeometryComparison.EQUAL)
225 # Transform to completely different offsets and the inverse of the
226 # flips we tried before, just to make sure we didn't mix up X and Y
227 # somewhere.
228 different = self.amplifier.rebuild().transform(
229 outOffset=lsst.geom.Extent2I(7, 8),
230 outFlipX=not self.data.rawFlipX,
231 outFlipY=not self.data.rawFlipY,
232 ).finish()
233 self.assertEqual(different.getRawFlipX(), not self.data.rawFlipX)
234 self.assertEqual(different.getRawFlipY(), not self.data.rawFlipY)
235 self.assertEqual(different.getRawXYOffset(), lsst.geom.Extent2I(7, 8))
236 self.assertEqual(different.getReadoutCorner(), ReadoutCorner.LR)
237 comparison = self.amplifier.compareGeometry(different)
238 self.assertTrue(comparison & comparison.ASSEMBLY_DIFFERS)
239 self.assertFalse(comparison & comparison.REGIONS_DIFFER)
242class TestMemory(lsst.utils.tests.MemoryTestCase):
243 pass
246def setup_module(module):
247 lsst.utils.tests.init()
250if __name__ == "__main__": 250 ↛ 251line 250 didn't jump to line 251, because the condition on line 250 was never true
251 lsst.utils.tests.init()
252 unittest.main()