Coverage for tests/test_from_legacy_lazy_coadd.py: 33%
91 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 01:34 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 01:34 +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 dataclasses
15import os
16import pickle
17from typing import Any
19import astropy.units as u
20import numpy as np
21import pytest
23from lsst.images import YX, Box, get_legacy_non_cell_coadd_mask_planes
24from lsst.images.cells import CellCoadd
25from lsst.images.tests import (
26 DP2_COADD_DATA_ID,
27 RoundtripFits,
28 assert_cell_coadds_equal,
29 assert_masked_images_equal,
30 compare_cell_coadd_to_legacy_lazy,
31 compare_field_to_legacy,
32 compare_masked_image_to_legacy,
33 compare_psf_to_legacy,
34 compare_sky_projection_to_legacy_wcs,
35 reset_afw_mask_planes, # noqa: F401
36)
38EXTERNAL_DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None)
41@dataclasses.dataclass
42class _LazyCoaddTestData:
43 """A struct holding test data loaded from EXTERNAL_DATA_DIR.
45 The legacy fixture is a DP2 ``template_coadd``: a patch-level coadd
46 ``lsst.afw.image.Exposure`` with a spatially varying PSF and aperture
47 corrections, but no cell structure.
48 """
50 filename: str
51 tract_info: Any
52 legacy_exposure: Any
53 cell_coadd: CellCoadd
55 @property
56 def tract_bbox(self) -> Box:
57 return Box.from_legacy(self.tract_info.getBBox())
60@pytest.fixture
61def legacy_test_data(reset_afw_mask_planes: None) -> _LazyCoaddTestData: # noqa: F811
62 """Return a struct with a legacy lazy coadd and its CellCoadd conversion.
64 The legacy mask must be loaded after the afw mask-plane registry is
65 reset (so its header's plane definitions, such as CLIPPED, get
66 registered in the reset registry), which means the fixture can not be
67 shared between tests via a module scope. The conversion itself is
68 cheap, since it only covers the cells overlapping the legacy coadd.
70 Skips if ``TESTDATA_IMAGES_DIR`` is not set or if ``lsst.afw`` cannot be
71 imported.
72 """
73 if EXTERNAL_DATA_DIR is None: 73 ↛ 75line 73 didn't jump to line 75 because the condition on line 73 was always true
74 pytest.skip("TESTDATA_IMAGES_DIR is not in the environment.")
75 filename = os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "template_coadd.fits")
76 try:
77 from lsst.afw.image import ExposureFitsReader
78 except ImportError:
79 pytest.skip("lsst.afw.image is not available; cannot read legacy exposures")
80 legacy_exposure = ExposureFitsReader(filename).read()
81 with open(os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "skyMap.pickle"), "rb") as stream:
82 skymap = pickle.load(stream)
83 tract_info = skymap[DP2_COADD_DATA_ID["tract"]]
84 cell_coadd = CellCoadd.from_legacy_lazy_coadd(
85 legacy_exposure,
86 instrument="LSSTCam",
87 tract_info=tract_info,
88 patch=DP2_COADD_DATA_ID["patch"],
89 )
90 return _LazyCoaddTestData(
91 filename=filename,
92 tract_info=tract_info,
93 legacy_exposure=legacy_exposure,
94 cell_coadd=cell_coadd,
95 )
98def test_from_legacy_lazy_coadd(legacy_test_data: _LazyCoaddTestData) -> None:
99 """Test constructing a CellCoadd by converting a legacy lazy
100 (non-cell) coadd.
101 """
102 compare_cell_coadd_to_legacy_lazy(
103 legacy_test_data.cell_coadd,
104 legacy_test_data.legacy_exposure,
105 tract_bbox=legacy_test_data.tract_bbox,
106 **DP2_COADD_DATA_ID,
107 )
108 assert legacy_test_data.cell_coadd.bbox == Box.from_legacy(legacy_test_data.legacy_exposure.getBBox())
109 assert legacy_test_data.cell_coadd.unit == u.nJy
110 assert legacy_test_data.cell_coadd.skymap == DP2_COADD_DATA_ID["skymap"]
111 assert legacy_test_data.cell_coadd.tract == DP2_COADD_DATA_ID["tract"]
112 assert legacy_test_data.cell_coadd.band == DP2_COADD_DATA_ID["band"]
113 patch_info = legacy_test_data.tract_info[DP2_COADD_DATA_ID["patch"]]
114 assert legacy_test_data.cell_coadd.patch.id == DP2_COADD_DATA_ID["patch"]
115 assert legacy_test_data.cell_coadd.patch.index == YX(y=patch_info.getIndex().y, x=patch_info.getIndex().x)
116 assert legacy_test_data.cell_coadd.patch.inner_bbox == Box.from_legacy(patch_info.getInnerBBox())
117 assert legacy_test_data.cell_coadd.mask_fractions == {}
118 assert list(legacy_test_data.cell_coadd.noise_realizations) == []
119 assert not legacy_test_data.cell_coadd.backgrounds.keys()
120 assert legacy_test_data.cell_coadd.backgrounds.subtracted is None
123def test_roundtrip(legacy_test_data: _LazyCoaddTestData) -> None:
124 """Test that a CellCoadd converted from a legacy lazy coadd round-trips
125 through FITS.
126 """
127 cell_coadd = legacy_test_data.cell_coadd
128 with RoundtripFits(cell_coadd, "CellCoadd") as roundtrip:
129 subbox = Box.factory[
130 cell_coadd.bbox.y.start + 3 : cell_coadd.bbox.y.stop - 4,
131 cell_coadd.bbox.x.start + 5 : cell_coadd.bbox.x.stop - 2,
132 ]
133 subimage = roundtrip.get(bbox=subbox)
134 assert_masked_images_equal(subimage, cell_coadd[subbox], expect_view=False)
135 alternates = {
136 k: roundtrip.get(k) for k in ["sky_projection", "psf", "aperture_corrections", "provenance"]
137 }
138 compare_cell_coadd_to_legacy_lazy(
139 roundtrip.result,
140 legacy_test_data.legacy_exposure,
141 tract_bbox=legacy_test_data.tract_bbox,
142 alternates=alternates,
143 **DP2_COADD_DATA_ID,
144 )
145 assert_cell_coadds_equal(roundtrip.result, cell_coadd, expect_view=False)
148def test_to_legacy(legacy_test_data: _LazyCoaddTestData) -> None:
149 """Test converting a coadd that was itself converted from a legacy lazy
150 coadd back into a legacy Exposure.
151 """
152 cell_coadd = legacy_test_data.cell_coadd
153 plane_map = get_legacy_non_cell_coadd_mask_planes()
154 legacy_exposure = cell_coadd.to_legacy(plane_map=plane_map)
155 assert legacy_exposure.getFilter().bandLabel == cell_coadd.band
156 assert Box.from_legacy(legacy_exposure.getBBox()) == cell_coadd.bbox
157 compare_masked_image_to_legacy(
158 cell_coadd, legacy_exposure.maskedImage, plane_map=plane_map, expect_view=True
159 )
160 # The legacy PSF here is a StitchedPsf made from the very same per-cell
161 # stamps, so this comparison is exact at any point again.
162 compare_psf_to_legacy(cell_coadd.psf, legacy_exposure.getPsf())
163 compare_sky_projection_to_legacy_wcs(
164 cell_coadd.sky_projection,
165 legacy_exposure.getWcs(),
166 cell_coadd.sky_projection.pixel_frame,
167 subimage_bbox=cell_coadd.bbox,
168 is_fits=True,
169 )
172def test_compare_lazy_coadd_image_atol(legacy_test_data: _LazyCoaddTestData) -> None:
173 """Test that image_atol lets compare_cell_coadd_to_legacy_lazy accept a
174 small offset in the image pixels (while image_atol=0.0 rejects it).
175 """
176 perturbed = legacy_test_data.legacy_exposure.clone()
177 perturbed.getMaskedImage().getImage().array += 0.5
178 with pytest.raises(AssertionError):
179 compare_cell_coadd_to_legacy_lazy(
180 legacy_test_data.cell_coadd,
181 perturbed,
182 tract_bbox=legacy_test_data.tract_bbox,
183 **DP2_COADD_DATA_ID,
184 )
185 compare_cell_coadd_to_legacy_lazy(
186 legacy_test_data.cell_coadd,
187 perturbed,
188 tract_bbox=legacy_test_data.tract_bbox,
189 image_atol=1.0,
190 **DP2_COADD_DATA_ID,
191 )
194def test_compare_lazy_coadd_variance_atol(legacy_test_data: _LazyCoaddTestData) -> None:
195 """Test that variance_atol lets compare_cell_coadd_to_legacy_lazy accept
196 a small offset in the variance pixels (while variance_atol=0.0 rejects
197 it), with the image still compared exactly.
198 """
199 perturbed = legacy_test_data.legacy_exposure.clone()
200 perturbed.getMaskedImage().getVariance().array += 0.5
201 with pytest.raises(AssertionError):
202 compare_cell_coadd_to_legacy_lazy(
203 legacy_test_data.cell_coadd,
204 perturbed,
205 tract_bbox=legacy_test_data.tract_bbox,
206 **DP2_COADD_DATA_ID,
207 )
208 compare_cell_coadd_to_legacy_lazy(
209 legacy_test_data.cell_coadd,
210 perturbed,
211 tract_bbox=legacy_test_data.tract_bbox,
212 variance_atol=1.0,
213 **DP2_COADD_DATA_ID,
214 )
217def test_compare_field_to_legacy_rtol(legacy_test_data: _LazyCoaddTestData) -> None:
218 """Test that compare_field_to_legacy's default rtol catches a
219 1e-4-scale eval-point mismatch, while a looser rtol accepts it.
220 """
221 ap_corr_map = legacy_test_data.legacy_exposure.info.getApCorrMap()
222 name = sorted(ap_corr_map.keys())[0]
223 field = legacy_test_data.cell_coadd.aperture_corrections[name]
224 legacy_field = ap_corr_map[name]
225 perturbed = field * (1.0 + 1e-4)
226 eval_points = field.bounds.bbox.meshgrid(n=3).map(np.ravel)
227 with pytest.raises(AssertionError):
228 compare_field_to_legacy(
229 perturbed,
230 legacy_field,
231 field.bounds.bbox,
232 eval_points=eval_points,
233 expect_subset_bounds=True,
234 )
235 compare_field_to_legacy(
236 perturbed,
237 legacy_field,
238 field.bounds.bbox,
239 eval_points=eval_points,
240 expect_subset_bounds=True,
241 rtol=1e-2,
242 )