Coverage for tests/test_cameras.py: 53%

89 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-30 08:38 +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 

15from typing import Any 

16 

17import pytest 

18 

19from lsst.images import YX 

20from lsst.images.cameras import AmplifierRawGeometry, Detector, ReadoutCorner 

21from lsst.images.describe import DescribableMixin, Report 

22from lsst.images.serialization import read_archive 

23from lsst.images.tests import DP2_VISIT_DETECTOR_DATA_ID, RoundtripFits, compare_detector_to_legacy 

24 

25LOCAL_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") 

26 

27EXTERNAL_DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None) 

28 

29 

30@pytest.fixture(scope="session") 

31def legacy_camera_data() -> dict[str, Any]: 

32 """Return a dict with legacy_camera and legacy_detector loaded from the 

33 test data directory. 

34 """ 

35 if EXTERNAL_DATA_DIR is None: 35 ↛ 37line 35 didn't jump to line 37 because the condition on line 35 was always true

36 pytest.skip("TESTDATA_IMAGES_DIR is not in the environment.") 

37 try: 

38 from lsst.afw.cameraGeom import Camera as LegacyCamera 

39 from lsst.afw.image import ExposureFitsReader 

40 except ImportError: 

41 pytest.skip("lsst.afw could not be imported.") 

42 legacy_camera = LegacyCamera.readFits(os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "camera.fits")) 

43 legacy_detector = ExposureFitsReader( 

44 os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "visit_image.fits") 

45 ).readDetector() 

46 return {"legacy_camera": legacy_camera, "legacy_detector": legacy_detector} 

47 

48 

49def test_visit_image_detector_legacy_conversions(legacy_camera_data: dict[str, Any]) -> None: 

50 """Test converting a visit-image detector from legacy and back with 

51 serialization in between. 

52 """ 

53 legacy_detector = legacy_camera_data["legacy_detector"] 

54 detector = Detector.from_legacy( 

55 legacy_detector, 

56 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"], 

57 visit=DP2_VISIT_DETECTOR_DATA_ID["visit"], 

58 ) 

59 compare_detector_to_legacy(detector, legacy_detector, is_raw_assembled=True) 

60 with RoundtripFits(detector) as roundtrip: 

61 pass 

62 compare_detector_to_legacy(roundtrip.result, legacy_detector, is_raw_assembled=True) 

63 compare_detector_to_legacy(detector, roundtrip.result.to_legacy(), is_raw_assembled=True) 

64 

65 

66def test_camera_detector_legacy_conversions(legacy_camera_data: dict[str, Any]) -> None: 

67 """Test converting one detector of each type from a legacy Camera and 

68 back. 

69 """ 

70 legacy_camera = legacy_camera_data["legacy_camera"] 

71 detector_types_seen = set() 

72 for legacy_detector_1 in legacy_camera: 

73 if legacy_detector_1.getType() in detector_types_seen: 

74 continue 

75 detector_types_seen.add(legacy_detector_1.getType()) 

76 detector = Detector.from_legacy( 

77 legacy_detector_1, 

78 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"], 

79 ) 

80 compare_detector_to_legacy(detector, legacy_detector_1, is_raw_assembled=False) 

81 with RoundtripFits(detector) as roundtrip: 

82 pass 

83 compare_detector_to_legacy(roundtrip.result, legacy_detector_1, is_raw_assembled=False) 

84 legacy_detector_2 = roundtrip.result.to_legacy() 

85 compare_detector_to_legacy(detector, legacy_detector_2, is_raw_assembled=False) 

86 

87 

88def test_expanded_detector_roundtrip(legacy_camera_data: dict[str, Any]) -> None: 

89 """Test round-tripping a detector that holds both assembled and 

90 unassembled raw amplifier geometry. 

91 """ 

92 legacy_camera = legacy_camera_data["legacy_camera"] 

93 legacy_detector = legacy_camera_data["legacy_detector"] 

94 legacy_camera_detector = legacy_camera[DP2_VISIT_DETECTOR_DATA_ID["detector"]] 

95 detector = Detector.from_legacy( 

96 legacy_camera_detector, 

97 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"], 

98 visit=DP2_VISIT_DETECTOR_DATA_ID["visit"], 

99 is_raw_assembled=False, 

100 ) 

101 for amplifier, legacy_assembled_amplifier in zip( 

102 detector.amplifiers, legacy_detector.getAmplifiers(), strict=True 

103 ): 

104 assert amplifier.unassembled_raw_geometry is not None 

105 assert amplifier.assembled_raw_geometry is None 

106 amplifier.assembled_raw_geometry = AmplifierRawGeometry.from_legacy_amplifier( 

107 legacy_assembled_amplifier 

108 ) 

109 with RoundtripFits(detector) as roundtrip: 

110 pass 

111 compare_detector_to_legacy(roundtrip.result, legacy_camera_detector, is_raw_assembled=False) 

112 compare_detector_to_legacy(roundtrip.result, legacy_detector, is_raw_assembled=True) 

113 

114 

115def test_as_flips() -> None: 

116 """Tes that ReadoutCorner.as_flips returns the correct YX flip flags for 

117 each corner. 

118 """ 

119 assert ReadoutCorner.LL.as_flips() == YX(y=False, x=False) 

120 assert ReadoutCorner.LR.as_flips() == YX(y=False, x=True) 

121 assert ReadoutCorner.UL.as_flips() == YX(y=True, x=False) 

122 assert ReadoutCorner.UR.as_flips() == YX(y=True, x=True) 

123 

124 

125def test_flips_roundtrip() -> None: 

126 """Test that ReadoutCorner.from_flips is the inverse of as_flips for all 

127 corners. 

128 """ 

129 for corner in ReadoutCorner: 

130 flips = corner.as_flips() 

131 assert ReadoutCorner.from_flips(y=flips.y, x=flips.x) is corner 

132 

133 

134def test_apply_flips() -> None: 

135 """Test that ReadoutCorner.apply_flips with identity returns the same 

136 corner, and a few specific flips are correct. 

137 """ 

138 for corner in ReadoutCorner: 

139 assert corner.apply_flips(y=False, x=False) is corner 

140 assert ReadoutCorner.LL.apply_flips(y=True, x=True) is ReadoutCorner.UR 

141 assert ReadoutCorner.LR.apply_flips(y=False, x=True) is ReadoutCorner.LL 

142 assert ReadoutCorner.UL.apply_flips(y=True, x=False) is ReadoutCorner.LL 

143 assert ReadoutCorner.UR.apply_flips(y=True, x=True) is ReadoutCorner.LL 

144 

145 

146def test_detector_describe() -> None: 

147 """Detector._describe returns a Report with the expected fields.""" 

148 detector = read_archive(os.path.join(LOCAL_DATA_DIR, "detector.json"), Detector) 

149 assert isinstance(detector, DescribableMixin) 

150 report = detector._describe() 

151 assert isinstance(report, Report) 

152 assert report.type_name == "Detector" 

153 labels = {f.label for f in report.fields} 

154 assert "instrument" in labels 

155 assert "name" in labels 

156 assert "id" in labels 

157 assert "type" in labels 

158 assert "serial" in labels 

159 assert "bbox" in labels 

160 name_field = next(f for f in report.fields if f.label == "name") 

161 assert name_field.value == detector.name