Coverage for tests/test_cameras.py: 52%
67 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-02 02:02 -0700
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-02 02:02 -0700
1# This file is part of lsst-images.
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# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14import os
15import unittest
17from lsst.images._geom import YX
18from lsst.images.cameras import AmplifierRawGeometry, Detector, ReadoutCorner
19from lsst.images.tests import DP2_VISIT_DETECTOR_DATA_ID, RoundtripFits, compare_detector_to_legacy
21try:
22 from lsst.afw.cameraGeom import Camera as LegacyCamera
23 from lsst.afw.cameraGeom import DetectorType as LegacyDetectorType
24 from lsst.afw.image import ExposureFitsReader
25except ImportError: # pragma: no cover
26 HAVE_AFW = False
27else: # pragma: no cover
28 HAVE_AFW = True
30DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None)
33@unittest.skipUnless(DATA_DIR is not None, "TESTDATA_IMAGES_DIR is not in the environment.")
34@unittest.skipUnless(HAVE_AFW, "lsst.afw could not be imported.")
35class CamerasTestCase(unittest.TestCase):
36 """Tests for the 'cameras' module."""
38 @classmethod
39 def setUpClass(cls) -> None:
40 cls.legacy_camera = LegacyCamera.readFits(os.path.join(DATA_DIR, "dp2", "legacy", "camera.fits"))
41 cls.legacy_detector = ExposureFitsReader(
42 os.path.join(DATA_DIR, "dp2", "legacy", "visit_image.fits")
43 ).readDetector()
45 def test_visit_image_detector_legacy_conversions(self) -> None:
46 """Test converting a detector attached to a visit image from its
47 legacy type and back, with serialization in between.
48 """
49 detector = Detector.from_legacy(
50 self.legacy_detector,
51 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"],
52 visit=DP2_VISIT_DETECTOR_DATA_ID["visit"],
53 )
54 compare_detector_to_legacy(self, detector, self.legacy_detector, is_raw_assembled=True)
55 with RoundtripFits(self, detector) as roundtrip:
56 pass
57 compare_detector_to_legacy(self, roundtrip.result, self.legacy_detector, is_raw_assembled=True)
58 compare_detector_to_legacy(self, detector, roundtrip.result.to_legacy(), is_raw_assembled=True)
60 def test_camera_detector_legacy_conversions(self) -> None:
61 """Test converting a detector extracted from a camera from its
62 legacy type and back, with serialization in between.
63 """
64 # Test one detector of each type for a reasonable balance of
65 # completeness and test speed.
66 detector_types_seen: set[LegacyDetectorType] = set()
67 for legacy_detector_1 in self.legacy_camera:
68 if legacy_detector_1.getType() in detector_types_seen:
69 continue
70 detector_types_seen.add(legacy_detector_1.getType())
71 with self.subTest(detector_id=legacy_detector_1.getId()):
72 detector = Detector.from_legacy(
73 legacy_detector_1,
74 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"],
75 )
76 compare_detector_to_legacy(self, detector, legacy_detector_1, is_raw_assembled=False)
77 with RoundtripFits(self, detector) as roundtrip:
78 pass
79 compare_detector_to_legacy(self, roundtrip.result, legacy_detector_1, is_raw_assembled=False)
80 legacy_detector_2 = roundtrip.result.to_legacy()
81 compare_detector_to_legacy(self, detector, legacy_detector_2, is_raw_assembled=False)
83 def test_expanded_detector_roundtrip(self) -> None:
84 """Test roudntripping a detector that holds both assembled and
85 unassembled raw amplifier geometry.
86 """
87 legacy_camera_detector = self.legacy_camera[DP2_VISIT_DETECTOR_DATA_ID["detector"]]
88 detector = Detector.from_legacy(
89 legacy_camera_detector,
90 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"],
91 visit=DP2_VISIT_DETECTOR_DATA_ID["visit"],
92 is_raw_assembled=False,
93 )
94 for amplifier, legacy_assembled_amplifier in zip(
95 detector.amplifiers, self.legacy_detector.getAmplifiers(), strict=True
96 ):
97 assert amplifier.unassembled_raw_geometry is not None
98 assert amplifier.assembled_raw_geometry is None
99 amplifier.assembled_raw_geometry = AmplifierRawGeometry.from_legacy_amplifier(
100 legacy_assembled_amplifier
101 )
102 with RoundtripFits(self, detector) as roundtrip:
103 pass
104 compare_detector_to_legacy(self, roundtrip.result, legacy_camera_detector, is_raw_assembled=False)
105 compare_detector_to_legacy(self, roundtrip.result, self.legacy_detector, is_raw_assembled=True)
108class ReadoutCornerTestCase(unittest.TestCase):
109 """Tests for `ReadoutCorner` flip logic."""
111 def test_as_flips(self) -> None:
112 self.assertEqual(ReadoutCorner.LL.as_flips(), YX(y=False, x=False))
113 self.assertEqual(ReadoutCorner.LR.as_flips(), YX(y=False, x=True))
114 self.assertEqual(ReadoutCorner.UL.as_flips(), YX(y=True, x=False))
115 self.assertEqual(ReadoutCorner.UR.as_flips(), YX(y=True, x=True))
117 def test_flips_roundtrip(self) -> None:
118 for corner in ReadoutCorner:
119 flips = corner.as_flips()
120 self.assertIs(ReadoutCorner.from_flips(y=flips.y, x=flips.x), corner)
122 def test_apply_flips(self) -> None:
123 for corner in ReadoutCorner:
124 self.assertIs(corner.apply_flips(y=False, x=False), corner)
125 self.assertIs(ReadoutCorner.LL.apply_flips(y=True, x=True), ReadoutCorner.UR)
126 self.assertIs(ReadoutCorner.LR.apply_flips(y=False, x=True), ReadoutCorner.LL)
127 self.assertIs(ReadoutCorner.UL.apply_flips(y=True, x=False), ReadoutCorner.LL)
128 self.assertIs(ReadoutCorner.UR.apply_flips(y=True, x=True), ReadoutCorner.LL)
131if __name__ == "__main__":
132 unittest.main()