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

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 numpy as np 

15import pytest 

16 

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) 

26 

27try: 

28 import h5py 

29 

30 from lsst.images.ndf import _hds 

31 

32 HAVE_H5PY = True 

33except ImportError: 

34 HAVE_H5PY = False 

35 

36skip_no_h5py = pytest.mark.skipif(not HAVE_H5PY, reason="h5py is not installed") 

37 

38 

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

42 

43 

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 

52 

53 

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) 

63 

64 

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 ) 

86 

87 

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 ) 

108 

109 

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) 

116 

117 

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) 

125 

126 

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) 

133 

134 

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" 

158 

159 

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)