Coverage for tests/test_verify_rewrite.py: 70%
94 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 10:30 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 10:30 +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 pathlib import Path
17import numpy as np
18import pytest
19from click.testing import CliRunner
21from lsst.images import Background, BackgroundMap, Box
22from lsst.images.cli._main import main
23from lsst.images.fields import ChebyshevField
24from lsst.images.tests import (
25 DP2_VISIT_DETECTOR_DATA_ID,
26 reset_afw_mask_planes, # noqa: F401
27)
28from lsst.images.tests.verify_rewrite import (
29 RewriteVerifier,
30 _check_backgrounds,
31 _check_kernel,
32 _check_templates,
33)
36def _bbox() -> Box:
37 return Box.factory[0:10, 0:10]
40def _constant_background(name: str = "subtracted") -> Background:
41 field = ChebyshevField(_bbox(), np.array([[5.0]]))
42 return Background(name=name, field=field, description="test")
45def test_check_backgrounds_present_and_finite() -> None:
46 """Check that present and finite backgrounds pass."""
47 backgrounds = BackgroundMap(
48 [_constant_background("subtracted"), _constant_background("skyCorr")],
49 subtracted="subtracted",
50 )
51 _check_backgrounds(backgrounds, _bbox(), expected=("subtracted", "skyCorr"))
54def test_check_backgrounds_missing_expected_fails() -> None:
55 """Check that a missing expected background name fails."""
56 backgrounds = BackgroundMap([_constant_background("subtracted")], subtracted="subtracted")
57 with pytest.raises(AssertionError, match="expected background 'skyCorr' not attached"):
58 _check_backgrounds(backgrounds, _bbox(), expected=("subtracted", "skyCorr"))
61def test_check_backgrounds_non_finite_fails() -> None:
62 """Check that a non-finite background field fails."""
63 field = ChebyshevField(_bbox(), np.array([[np.nan]]))
64 backgrounds = BackgroundMap([Background(name="subtracted", field=field)], subtracted="subtracted")
65 with pytest.raises(AssertionError, match="non-finite"):
66 _check_backgrounds(backgrounds, _bbox())
69def test_check_backgrounds_no_background_ok() -> None:
70 """Check that an empty background map with no expectations passes."""
71 _check_backgrounds(BackgroundMap(), _bbox(), expected=())
74def test_check_kernel_none_fails() -> None:
75 """Check that a None kernel fails."""
76 with pytest.raises(AssertionError):
77 _check_kernel(None)
80def test_check_templates_none_fails() -> None:
81 """Check that a None templates list fails."""
82 with pytest.raises(AssertionError):
83 _check_templates(None, _bbox())
86def test_check_templates_empty_fails() -> None:
87 """Check that an empty templates list fails."""
88 with pytest.raises(AssertionError):
89 _check_templates([], _bbox())
92def test_rewrite_verifier_reports_problems() -> None:
93 """Assert that print_error increments the problem count."""
94 verifier = RewriteVerifier(None, "difference_image", old_prefix="legacy_", new_prefix="")
95 assert verifier.n_problems == 0
96 verifier.print_error("data_id", AssertionError("boom"))
97 assert verifier.n_problems == 1
100def test_print_error_no_note(capsys: pytest.CaptureFixture[str]) -> None:
101 """An error without a component note is printed on one line."""
102 verifier = RewriteVerifier(None, "difference_image", old_prefix="legacy_", new_prefix="")
103 verifier.print_error("data_id", AssertionError("boom"))
104 assert capsys.readouterr().out == "data_id: boom (AssertionError)\n"
107def test_print_error_component_note(capsys: pytest.CaptureFixture[str]) -> None:
108 """A component note is printed as a prefix on its own indented line."""
109 verifier = RewriteVerifier(None, "difference_image", old_prefix="legacy_", new_prefix="")
110 err = AssertionError("81/6642 values differ; max abs diff 1e-9 at index (np.int64(0),)")
111 err.add_note("sky_projection")
112 verifier.print_error("data_id", err)
113 assert capsys.readouterr().out == (
114 "data_id\n sky_projection: 81/6642 values differ; max abs diff 1e-9 at index"
115 " (np.int64(0),) (AssertionError)\n"
116 )
119def test_print_error_nested_notes(capsys: pytest.CaptureFixture[str]) -> None:
120 """Nested component notes are rendered as a path, not repeated messages."""
121 verifier = RewriteVerifier(None, "difference_image", old_prefix="legacy_", new_prefix="")
122 err = AssertionError("boom")
123 err.add_note("visit_image")
124 err.add_note("sky_projection")
125 verifier.print_error("data_id", err)
126 assert capsys.readouterr().out == "data_id\n visit_image -> sky_projection: boom (AssertionError)\n"
129@pytest.fixture(scope="module")
130def testdata_dir() -> str:
131 """Return the external test-data directory, skipping if unset."""
132 if (result := os.environ.get("TESTDATA_IMAGES_DIR")) is None: 132 ↛ 134line 132 didn't jump to line 134 because the condition on line 132 was always true
133 pytest.skip("TESTDATA_IMAGES_DIR is not set.")
134 return result
137def test_verify_rewrite_end_to_end(tmp_path: Path, testdata_dir: str, reset_afw_mask_planes) -> None: # noqa: F811
138 """Run convert then verify-rewrite on a real difference image.
140 Happy path: asserts the whole flow exits successfully.
141 """
142 try:
143 from lsst.daf.butler import Butler, DataCoordinate, DatasetRef, DatasetType, FileDataset
144 except ImportError:
145 pytest.skip("lsst.daf.butler could not be imported.")
147 src = os.path.join(testdata_dir, "dp2", "legacy", "difference_image.fits")
148 converted = str(tmp_path / "difference_image.fits")
149 result = CliRunner().invoke(main, ["convert", src, converted])
150 assert result.exit_code == 0, result.output
152 repo = str(tmp_path / "repo")
153 Butler.makeRepo(repo)
154 butler = Butler.from_config(repo, run="run1")
155 reg = butler.registry
156 reg.insertDimensionData(
157 "instrument",
158 {
159 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
160 "name": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
161 },
162 )
163 reg.insertDimensionData(
164 "day_obs",
165 {
166 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
167 "day_obs": DP2_VISIT_DETECTOR_DATA_ID["day_obs"],
168 },
169 )
170 reg.insertDimensionData(
171 "physical_filter",
172 {
173 "physical_filter": DP2_VISIT_DETECTOR_DATA_ID["physical_filter"],
174 "band": DP2_VISIT_DETECTOR_DATA_ID["band"],
175 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
176 },
177 )
178 reg.insertDimensionData(
179 "detector",
180 {
181 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
182 "id": DP2_VISIT_DETECTOR_DATA_ID["detector"],
183 "full_name": "R21_S11",
184 },
185 )
186 reg.insertDimensionData(
187 "visit",
188 {
189 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
190 "id": DP2_VISIT_DETECTOR_DATA_ID["visit"],
191 "physical_filter": DP2_VISIT_DETECTOR_DATA_ID["physical_filter"],
192 "name": str(DP2_VISIT_DETECTOR_DATA_ID["visit"]),
193 "day_obs": DP2_VISIT_DETECTOR_DATA_ID["day_obs"],
194 },
195 )
196 reg.insertDimensionData(
197 "visit_detector_region",
198 {
199 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
200 "visit": DP2_VISIT_DETECTOR_DATA_ID["visit"],
201 "detector": DP2_VISIT_DETECTOR_DATA_ID["detector"],
202 "region": None,
203 },
204 )
206 dims = ("instrument", "visit", "detector")
207 data_id = DataCoordinate.standardize(
208 {
209 "instrument": DP2_VISIT_DETECTOR_DATA_ID["instrument"],
210 "visit": DP2_VISIT_DETECTOR_DATA_ID["visit"],
211 "detector": DP2_VISIT_DETECTOR_DATA_ID["detector"],
212 },
213 universe=butler.dimensions,
214 )
215 legacy_dt = DatasetType("legacy_difference_image", dims, "ExposureF", universe=butler.dimensions)
216 new_dt = DatasetType("difference_image", dims, "DifferenceImage", universe=butler.dimensions)
217 reg.registerDatasetType(legacy_dt)
218 reg.registerDatasetType(new_dt)
220 butler.ingest(
221 FileDataset(src, DatasetRef(legacy_dt, data_id, "run1")),
222 FileDataset(converted, DatasetRef(new_dt, data_id, "run1")),
223 )
225 result = CliRunner().invoke(
226 main,
227 [
228 "verify-rewrite",
229 repo,
230 "difference_image",
231 "run1",
232 "--no-check-kernel",
233 "--no-check-templates",
234 "--no-require-compressed",
235 ],
236 )
237 assert result.exit_code == 0, result.output