Coverage for tests/test_cameras.py: 55%
86 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 06:11 -0300
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 06:11 -0300
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 (
24 DP2_VISIT_DETECTOR_DATA_ID,
25 RoundtripFits,
26 compare_detector_to_legacy,
27 reset_afw_mask_planes, # noqa: F401
28)
30LOCAL_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
32EXTERNAL_DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None)
35@pytest.fixture
36def legacy_camera_data(reset_afw_mask_planes: None) -> dict[str, Any]: # noqa: F811
37 """Return a dict with legacy_camera and legacy_detector loaded from the
38 test data directory.
39 """
40 if EXTERNAL_DATA_DIR is None: 40 ↛ 43line 40 didn't jump to line 43 because the condition on line 40 was always true
41 pytest.skip("TESTDATA_IMAGES_DIR is not in the environment.")
42 # reset_afw_mask_planes will have already skipped if afw is not available.
43 from lsst.afw.cameraGeom import Camera as LegacyCamera
44 from lsst.afw.image import ExposureFitsReader
46 legacy_camera = LegacyCamera.readFits(os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "camera.fits"))
47 legacy_detector = ExposureFitsReader(
48 os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "visit_image.fits")
49 ).readDetector()
50 return {"legacy_camera": legacy_camera, "legacy_detector": legacy_detector}
53def test_visit_image_detector_legacy_conversions(legacy_camera_data: dict[str, Any]) -> None:
54 """Test converting a visit-image detector from legacy and back with
55 serialization in between.
56 """
57 legacy_detector = legacy_camera_data["legacy_detector"]
58 detector = Detector.from_legacy(
59 legacy_detector,
60 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"],
61 visit=DP2_VISIT_DETECTOR_DATA_ID["visit"],
62 )
63 compare_detector_to_legacy(detector, legacy_detector, is_raw_assembled=True)
64 with RoundtripFits(detector) as roundtrip:
65 pass
66 compare_detector_to_legacy(roundtrip.result, legacy_detector, is_raw_assembled=True)
67 compare_detector_to_legacy(detector, roundtrip.result.to_legacy(), is_raw_assembled=True)
70def test_camera_detector_legacy_conversions(legacy_camera_data: dict[str, Any]) -> None:
71 """Test converting one detector of each type from a legacy Camera and
72 back.
73 """
74 legacy_camera = legacy_camera_data["legacy_camera"]
75 detector_types_seen = set()
76 for legacy_detector_1 in legacy_camera:
77 if legacy_detector_1.getType() in detector_types_seen:
78 continue
79 detector_types_seen.add(legacy_detector_1.getType())
80 detector = Detector.from_legacy(
81 legacy_detector_1,
82 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"],
83 )
84 compare_detector_to_legacy(detector, legacy_detector_1, is_raw_assembled=False)
85 with RoundtripFits(detector) as roundtrip:
86 pass
87 compare_detector_to_legacy(roundtrip.result, legacy_detector_1, is_raw_assembled=False)
88 legacy_detector_2 = roundtrip.result.to_legacy()
89 compare_detector_to_legacy(detector, legacy_detector_2, is_raw_assembled=False)
92def test_expanded_detector_roundtrip(legacy_camera_data: dict[str, Any]) -> None:
93 """Test round-tripping a detector that holds both assembled and
94 unassembled raw amplifier geometry.
95 """
96 legacy_camera = legacy_camera_data["legacy_camera"]
97 legacy_detector = legacy_camera_data["legacy_detector"]
98 legacy_camera_detector = legacy_camera[DP2_VISIT_DETECTOR_DATA_ID["detector"]]
99 detector = Detector.from_legacy(
100 legacy_camera_detector,
101 instrument=DP2_VISIT_DETECTOR_DATA_ID["instrument"],
102 visit=DP2_VISIT_DETECTOR_DATA_ID["visit"],
103 is_raw_assembled=False,
104 )
105 for amplifier, legacy_assembled_amplifier in zip(
106 detector.amplifiers, legacy_detector.getAmplifiers(), strict=True
107 ):
108 assert amplifier.unassembled_raw_geometry is not None
109 assert amplifier.assembled_raw_geometry is None
110 amplifier.assembled_raw_geometry = AmplifierRawGeometry.from_legacy_amplifier(
111 legacy_assembled_amplifier
112 )
113 with RoundtripFits(detector) as roundtrip:
114 pass
115 compare_detector_to_legacy(roundtrip.result, legacy_camera_detector, is_raw_assembled=False)
116 compare_detector_to_legacy(roundtrip.result, legacy_detector, is_raw_assembled=True)
119def test_as_flips() -> None:
120 """Tes that ReadoutCorner.as_flips returns the correct YX flip flags for
121 each corner.
122 """
123 assert ReadoutCorner.LL.as_flips() == YX(y=False, x=False)
124 assert ReadoutCorner.LR.as_flips() == YX(y=False, x=True)
125 assert ReadoutCorner.UL.as_flips() == YX(y=True, x=False)
126 assert ReadoutCorner.UR.as_flips() == YX(y=True, x=True)
129def test_flips_roundtrip() -> None:
130 """Test that ReadoutCorner.from_flips is the inverse of as_flips for all
131 corners.
132 """
133 for corner in ReadoutCorner:
134 flips = corner.as_flips()
135 assert ReadoutCorner.from_flips(y=flips.y, x=flips.x) is corner
138def test_apply_flips() -> None:
139 """Test that ReadoutCorner.apply_flips with identity returns the same
140 corner, and a few specific flips are correct.
141 """
142 for corner in ReadoutCorner:
143 assert corner.apply_flips(y=False, x=False) is corner
144 assert ReadoutCorner.LL.apply_flips(y=True, x=True) is ReadoutCorner.UR
145 assert ReadoutCorner.LR.apply_flips(y=False, x=True) is ReadoutCorner.LL
146 assert ReadoutCorner.UL.apply_flips(y=True, x=False) is ReadoutCorner.LL
147 assert ReadoutCorner.UR.apply_flips(y=True, x=True) is ReadoutCorner.LL
150def test_detector_describe() -> None:
151 """Detector._describe returns a Report with the expected fields."""
152 detector = read_archive(os.path.join(LOCAL_DATA_DIR, "detector.json"), Detector)
153 assert isinstance(detector, DescribableMixin)
154 report = detector._describe()
155 assert isinstance(report, Report)
156 assert report.type_name == "Detector"
157 labels = {f.label for f in report.fields}
158 assert "instrument" in labels
159 assert "name" in labels
160 assert "id" in labels
161 assert "type" in labels
162 assert "serial" in labels
163 assert "bbox" in labels
164 name_field = next(f for f in report.fields if f.label == "name")
165 assert name_field.value == detector.name