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
« 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.
12from __future__ import annotations
14import os
15from typing import Any
17import pytest
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
25LOCAL_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
27EXTERNAL_DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None)
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}
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)
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)
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)
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)
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
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
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