Coverage for tests/test_color_image.py: 94%

72 statements  

« prev     ^ index     » next       coverage.py v7.14.2, created at 2026-06-21 09:01 +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. 

11 

12from __future__ import annotations 

13 

14import unittest 

15 

16import numpy as np 

17 

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) 

27 

28try: 

29 import h5py 

30 

31 from lsst.images.ndf import _hds 

32 

33 HAVE_H5PY = True 

34except ImportError: 

35 HAVE_H5PY = False 

36 

37 

38class ColorImageTestCase(unittest.TestCase): 

39 """Tests for ColorImage.""" 

40 

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) 

49 

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 ) 

77 

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 ) 

95 

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) 

101 

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) 

108 

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) 

118 

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") 

140 

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) 

152 

153 

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) 

159 

160 

161if __name__ == "__main__": 

162 unittest.main()