Coverage for tests/test_cameras.py: 52%

67 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-01 09:14 +0000

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. 

11 

12from __future__ import annotations 

13 

14import os 

15import unittest 

16 

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 

20 

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 

29 

30DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None) 

31 

32 

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.""" 

37 

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() 

44 

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) 

59 

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) 

82 

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) 

106 

107 

108class ReadoutCornerTestCase(unittest.TestCase): 

109 """Tests for `ReadoutCorner` flip logic.""" 

110 

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)) 

116 

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) 

121 

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) 

129 

130 

131if __name__ == "__main__": 

132 unittest.main()