Coverage for tests/test_color_image.py: 94%
72 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-08 02:03 -0700
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-08 02:03 -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 unittest
16import numpy as np
18from lsst.images import Box, ColorImage, Image, TractFrame
19from lsst.images.tests import (
20 RoundtripFits,
21 RoundtripJson,
22 RoundtripNdf,
23 assert_images_equal,
24 assert_sky_projections_equal,
25 make_random_sky_projection,
26)
28try:
29 import h5py
31 from lsst.images.ndf import _hds
33 HAVE_H5PY = True
34except ImportError:
35 HAVE_H5PY = False
38class ColorImageTestCase(unittest.TestCase):
39 """Tests for ColorImage."""
41 def setUp(self) -> None:
42 self.maxDiff = None
43 self.rng = np.random.default_rng(500)
44 self.pixel_frame = TractFrame(skymap="test_skymap", tract=33, bbox=Box.factory[:50, :64])
45 self.bbox = Box.factory[20:25, 40:48]
46 self.sky_projection = make_random_sky_projection(self.rng, self.pixel_frame, self.pixel_frame.bbox)
47 self.array = self.rng.integers(low=0, high=255, size=self.bbox.shape + (3,), dtype=np.uint8)
48 self.color_image = ColorImage(self.array, bbox=self.bbox, sky_projection=self.sky_projection)
50 def test_properties(self) -> None:
51 """Test the properties of the nominal ColorImage constructed in
52 setUp.
53 """
54 self.assertEqual(self.color_image.bbox, self.bbox)
55 self.assertTrue(np.may_share_memory(self.color_image.array, self.array))
56 assert_images_equal(
57 self,
58 self.color_image.red,
59 Image(self.array[:, :, 0], bbox=self.bbox, sky_projection=self.sky_projection),
60 expect_view="array",
61 )
62 assert_images_equal(
63 self,
64 self.color_image.green,
65 Image(self.array[:, :, 1], bbox=self.bbox, sky_projection=self.sky_projection),
66 expect_view="array",
67 )
68 assert_images_equal(
69 self,
70 self.color_image.blue,
71 Image(self.array[:, :, 2], bbox=self.bbox, sky_projection=self.sky_projection),
72 expect_view="array",
73 )
74 assert_sky_projections_equal(
75 self, self.color_image.sky_projection, self.sky_projection, expect_identity=True
76 )
78 def test_constructor(self) -> None:
79 """Test alternate constructor arguments."""
80 self.assert_color_images_equal(
81 ColorImage(self.array, yx0=self.bbox.start, sky_projection=self.sky_projection),
82 self.color_image,
83 expect_view=True,
84 )
85 self.assert_color_images_equal(
86 ColorImage.from_channels(
87 self.color_image.red,
88 self.color_image.green,
89 self.color_image.blue,
90 sky_projection=self.sky_projection,
91 ),
92 self.color_image,
93 expect_view=False,
94 )
96 def test_fits_roundtrip(self) -> None:
97 """Test round-tripping through FITS, via the butler if available."""
98 with RoundtripFits(self, self.color_image, "ColorImage") as roundtrip:
99 pass
100 self.assert_color_images_equal(roundtrip.result, self.color_image, expect_view=False)
102 @unittest.skipUnless(HAVE_H5PY, "h5py is not installed")
103 def test_ndf_roundtrip(self) -> None:
104 """Test round-tripping through NDF."""
105 with RoundtripNdf(self, self.color_image, "ColorImage") as roundtrip:
106 pass
107 self.assert_color_images_equal(roundtrip.result, self.color_image, expect_view=False)
109 def test_fits_json_consistency(self) -> None:
110 """FITS and JSON backends produce equal ColorImages on round-trip."""
111 with (
112 RoundtripFits(self, self.color_image) as fits_rt,
113 RoundtripJson(self, self.color_image) as json_rt,
114 ):
115 self.assert_color_images_equal(fits_rt.result, self.color_image, expect_view=False)
116 self.assert_color_images_equal(json_rt.result, self.color_image, expect_view=False)
117 self.assert_color_images_equal(fits_rt.result, json_rt.result, expect_view=False)
119 @unittest.skipUnless(HAVE_H5PY, "h5py is not installed")
120 def test_ndf_layout(self) -> None:
121 """ColorImage writes a top-level container with RGB child NDFs."""
122 with RoundtripNdf(self, self.color_image, "ColorImage") as roundtrip:
123 f = roundtrip.inspect()
124 self.assertEqual(_cls(f["/"]), "EXT")
125 self.assertIn("LSST", f)
126 self.assertIn("JSON", f["/LSST"])
127 self.assertNotIn("MORE", f)
128 for channel, index in (("RED", 0), ("GREEN", 1), ("BLUE", 2)):
129 with self.subTest(channel=channel):
130 self.assertIn(channel, f)
131 self.assertEqual(_cls(f[channel]), "NDF")
132 self.assertEqual(_cls(f[f"{channel}/DATA_ARRAY"]), "ARRAY")
133 np.testing.assert_array_equal(
134 f[f"{channel}/DATA_ARRAY/DATA"][()],
135 self.array[:, :, index],
136 )
137 self.assertEqual(list(f[f"{channel}/DATA_ARRAY/ORIGIN"][()]), [40, 20])
138 self.assertIn("WCS", f[channel])
139 self.assertEqual(_cls(f[f"{channel}/WCS"]), "WCS")
141 def assert_color_images_equal(
142 self, a: ColorImage, b: ColorImage, expect_view: bool | None = None
143 ) -> None:
144 """Check that the given ColorImage matches the nominal one constructed
145 in setUp.
146 """
147 assert_sky_projections_equal(self, a.sky_projection, b.sky_projection)
148 if expect_view is not None: 148 ↛ 150line 148 didn't jump to line 150 because the condition on line 148 was always true
149 self.assertEqual(np.may_share_memory(a.array, b.array), expect_view)
150 if not expect_view:
151 np.testing.assert_array_equal(a.array, b.array)
154def _cls(node: h5py.Group) -> str:
155 val = node.attrs.get(_hds.ATTR_CLASS)
156 if isinstance(val, bytes): 156 ↛ 158line 156 didn't jump to line 158 because the condition on line 156 was always true
157 return val.decode("ascii")
158 return str(val)
161if __name__ == "__main__":
162 unittest.main()