Coverage for tests/test_color_image.py: 94%
77 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-16 15:24 -0700
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-16 15:24 -0700
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 numpy as np
15import pytest
17from lsst.images import Box, ColorImage, Image, TractFrame
18from lsst.images.tests import (
19 RoundtripFits,
20 RoundtripJson,
21 RoundtripNdf,
22 assert_images_equal,
23 assert_sky_projections_equal,
24 make_random_sky_projection,
25)
27try:
28 import h5py
30 from lsst.images.ndf import _hds
32 HAVE_H5PY = True
33except ImportError:
34 HAVE_H5PY = False
36skip_no_h5py = pytest.mark.skipif(not HAVE_H5PY, reason="h5py is not installed")
39def _make_pixel_frame() -> TractFrame:
40 """Return a TractFrame for use in ColorImage tests."""
41 return TractFrame(skymap="test_skymap", tract=33, bbox=Box.factory[:50, :64])
44def _make_color_image() -> tuple[ColorImage, np.ndarray]:
45 """Return a ColorImage and its backing uint8 array."""
46 rng = np.random.default_rng(500)
47 pixel_frame = _make_pixel_frame()
48 bbox = Box.factory[20:25, 40:48]
49 sky_projection = make_random_sky_projection(rng, pixel_frame, pixel_frame.bbox)
50 array = rng.integers(low=0, high=255, size=bbox.shape + (3,), dtype=np.uint8)
51 return ColorImage(array, bbox=bbox, sky_projection=sky_projection), array
54def assert_color_images_equal(a: ColorImage, b: ColorImage, expect_view: bool | None = None) -> None:
55 """Assert that two ColorImages have equal sky projections and
56 pixel data.
57 """
58 assert_sky_projections_equal(a.sky_projection, b.sky_projection)
59 if expect_view is not None: 59 ↛ 61line 59 didn't jump to line 61 because the condition on line 59 was always true
60 assert np.may_share_memory(a.array, b.array) == expect_view
61 if not expect_view:
62 np.testing.assert_array_equal(a.array, b.array)
65def test_properties() -> None:
66 """Test that ColorImage properties match the construction arguments."""
67 color_image, array = _make_color_image()
68 bbox = Box.factory[20:25, 40:48]
69 assert color_image.bbox == bbox
70 assert np.may_share_memory(color_image.array, array)
71 assert_images_equal(
72 color_image.red,
73 Image(array[:, :, 0], bbox=bbox, sky_projection=color_image.sky_projection),
74 expect_view="array",
75 )
76 assert_images_equal(
77 color_image.green,
78 Image(array[:, :, 1], bbox=bbox, sky_projection=color_image.sky_projection),
79 expect_view="array",
80 )
81 assert_images_equal(
82 color_image.blue,
83 Image(array[:, :, 2], bbox=bbox, sky_projection=color_image.sky_projection),
84 expect_view="array",
85 )
88def test_constructor() -> None:
89 """Test that alternate ColorImage constructors produce equivalent
90 objects.
91 """
92 color_image, array = _make_color_image()
93 assert_color_images_equal(
94 ColorImage(array, yx0=color_image.bbox.start, sky_projection=color_image.sky_projection),
95 color_image,
96 expect_view=True,
97 )
98 assert_color_images_equal(
99 ColorImage.from_channels(
100 color_image.red,
101 color_image.green,
102 color_image.blue,
103 sky_projection=color_image.sky_projection,
104 ),
105 color_image,
106 expect_view=False,
107 )
110def test_fits_roundtrip() -> None:
111 """Test that ColorImage round-trips correctly through FITS."""
112 color_image, _ = _make_color_image()
113 with RoundtripFits(color_image, "ColorImage") as roundtrip:
114 pass
115 assert_color_images_equal(roundtrip.result, color_image, expect_view=False)
118@skip_no_h5py
119def test_ndf_roundtrip() -> None:
120 """Test that ColorImage round-trips correctly through NDF."""
121 color_image, _ = _make_color_image()
122 with RoundtripNdf(color_image, "ColorImage") as roundtrip:
123 pass
124 assert_color_images_equal(roundtrip.result, color_image, expect_view=False)
127def test_json_roundtrip() -> None:
128 """Test that ColorImage round-trips correctly through JSON."""
129 color_image, _ = _make_color_image()
130 with RoundtripJson(color_image) as roundtrip:
131 pass
132 assert_color_images_equal(roundtrip.result, color_image, expect_view=False)
135@skip_no_h5py
136def test_ndf_layout() -> None:
137 """Test that NDF output has the expected top-level structure with RGB
138 child NDFs.
139 """
140 color_image, array = _make_color_image()
141 with RoundtripNdf(color_image, "ColorImage") as roundtrip:
142 f = roundtrip.inspect()
143 assert _cls(f["/"]) == "EXT"
144 assert "LSST" in f
145 assert "JSON" in f["/LSST"]
146 assert "MORE" not in f
147 for channel, index in (("RED", 0), ("GREEN", 1), ("BLUE", 2)):
148 assert channel in f
149 assert _cls(f[channel]) == "NDF"
150 assert _cls(f[f"{channel}/DATA_ARRAY"]) == "ARRAY"
151 np.testing.assert_array_equal(
152 f[f"{channel}/DATA_ARRAY/DATA"][()],
153 array[:, :, index],
154 )
155 assert list(f[f"{channel}/DATA_ARRAY/ORIGIN"][()]) == [40, 20]
156 assert "WCS" in f[channel]
157 assert _cls(f[f"{channel}/WCS"]) == "WCS"
160def _cls(node: h5py.Group) -> str:
161 """Return the HDS CLASS attribute of an h5py group as a string."""
162 val = node.attrs.get(_hds.ATTR_CLASS)
163 if isinstance(val, bytes): 163 ↛ 165line 163 didn't jump to line 165 because the condition on line 163 was always true
164 return val.decode("ascii")
165 return str(val)