Coverage for tests / test_grid_container.py: 15%

133 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-23 08:47 +0000

1# This file is part of cell_coadds. 

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# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22from __future__ import annotations 

23 

24import copy 

25import pickle 

26import unittest 

27 

28import lsst.utils.tests 

29from lsst.cell_coadds import GridContainer, UniformGrid 

30from lsst.geom import Box2I, Extent2I, Point2I 

31from lsst.skymap import Index2D 

32from lsst.utils.tests import methodParameters 

33 

34 

35class GridContainerTestCase(unittest.TestCase): 

36 """Tests for GridContainer and GridIndex/Index2D's C++/Python 

37 translation. 

38 """ 

39 

40 def _fill(self, container: GridContainer[dict[str, int]]) -> None: 

41 """Populate a GridContainer with dicts that map "x" or "y" to 

42 the cell index in that dimension. 

43 """ 

44 for y in range(container.offset.y, container.offset.y + container.shape.y): 

45 for x in range(container.offset.x, container.offset.x + container.shape.x): 

46 container[Index2D(x, y)] = {"x": x, "y": y} 

47 

48 def _check(self, container: GridContainer[dict[str, int]]) -> None: 

49 """Perform a complete battery of tests on a GridContainer instance.""" 

50 for value in container.values(): 

51 self.assertEqual(container[Index2D(x=value["x"], y=value["y"])], value) 

52 self.assertEqual(container.get(Index2D(x=value["x"], y=value["y"])), value) 

53 self.assertEqual(container[Index2D(**value)], value) 

54 

55 self.assertEqual(container.first, {"x": container.offset.x, "y": container.offset.y}) 

56 self.assertEqual( 

57 container.last, 

58 { 

59 "x": container.offset.x + container.shape.x - 1, 

60 "y": container.offset.y + container.shape.y - 1, 

61 }, 

62 ) 

63 transformed: GridContainer[list[int]] = container.rebuild_transformed( 

64 lambda cell: list(cell.values()) # type: ignore 

65 ) 

66 self.assertEqual(transformed.shape, container.shape) 

67 self.assertEqual(transformed.offset, container.offset) 

68 self.assertEqual(list(transformed.keys()), list(container.keys())) 

69 self.assertEqual(list(transformed.values()), [list(v.values()) for v in container.values()]) 

70 emptied = GridContainer[dict[str, int]](container.shape, container.offset) 

71 self._fill(emptied) 

72 self.assertEqual(emptied.shape, container.shape) 

73 self.assertEqual(emptied.offset, container.offset) 

74 self.assertEqual(list(emptied), list(container)) 

75 copied = copy.copy(container) 

76 self.assertEqual(copied.shape, container.shape) 

77 self.assertEqual(copied.offset, container.offset) 

78 self.assertEqual(list(copied), list(container)) 

79 self.assertTrue( 

80 all( 

81 copied_cell is original_cell 

82 for copied_cell, original_cell in zip(copied, container, strict=True) 

83 ) 

84 ) 

85 deep_copied = copy.deepcopy(container) 

86 self.assertEqual(deep_copied.shape, container.shape) 

87 self.assertEqual(deep_copied.offset, container.offset) 

88 self.assertEqual(list(deep_copied), list(container)) 

89 self.assertTrue( 

90 all( 

91 deep_copied_cell is not original_cell 

92 for deep_copied_cell, original_cell in zip(deep_copied, container, strict=True) 

93 ) 

94 ) 

95 

96 def test_simple_ctor(self) -> None: 

97 """Test a GridContainer built with the shape-only GridContainer 

98 constructor. 

99 """ 

100 shape = Index2D(x=3, y=2) 

101 gc = GridContainer(shape) 

102 self.assertEqual(gc.shape, shape) 

103 self.assertIsInstance(gc.shape, Index2D) 

104 self.assertEqual(gc.offset, Index2D(x=0, y=0)) 

105 self.assertIsInstance(gc.offset, Index2D) 

106 self.assertEqual(gc.size, shape[0] * shape[1]) 

107 self.assertEqual(len(gc), 0) # unfilled container has length 0. 

108 self._fill(gc) 

109 self.assertEqual(len(gc), shape[0] * shape[1]) 

110 self._check(gc) 

111 

112 def test_complex_ctor(self) -> None: 

113 """Test a GridContainer built with the shape-and-offset GridContainer 

114 constructor. 

115 """ 

116 shape = Index2D(x=3, y=2) 

117 offset = Index2D(x=1, y=2) 

118 gc = GridContainer(shape, offset) 

119 self.assertEqual(gc.shape, shape) 

120 self.assertIsInstance(gc.shape, Index2D) 

121 self.assertEqual(gc.offset, offset) 

122 self.assertIsInstance(gc.offset, Index2D) 

123 self.assertEqual(len(gc), 0) 

124 self.assertEqual(gc.size, shape[0] * shape[1]) 

125 self._fill(gc) 

126 self.assertEqual(len(gc), shape[0] * shape[1]) 

127 self._check(gc) 

128 

129 def test_subset_overlapping(self) -> None: 

130 """Test various inputs to GridContainer.subset_overlapping.""" 

131 cell_size = Extent2I(x=3, y=2) 

132 full_bbox = Box2I(Point2I(x=1, y=2), Extent2I(x=15, y=12)) 

133 full_shape = Index2D(x=5, y=6) 

134 full_container = GridContainer(shape=full_shape) 

135 self._fill(full_container) 

136 grid = UniformGrid.from_bbox_cell_size(full_bbox, cell_size) 

137 self.assertEqual(grid.shape, full_shape) 

138 

139 # Subset with the orignal bounding box; should behave like a deepcopy. 

140 subset_container_full = full_container.subset_overlapping(grid, full_bbox) 

141 self.assertEqual(subset_container_full.shape, full_container.shape) 

142 self.assertEqual(subset_container_full.offset, full_container.offset) 

143 self.assertEqual(list(subset_container_full), list(full_container)) 

144 subset_container_full[Index2D(x=2, y=2)] = {"x": -1, "y": -1} 

145 self.assertEqual(list(subset_container_full.keys()), list(full_container.keys())) 

146 self.assertNotEqual(list(subset_container_full.values()), list(full_container.values())) 

147 

148 # Subset the full container with a nontrivial bbox. 

149 bbox_1 = Box2I(Point2I(x=6, y=4), Point2I(x=10, y=7)) 

150 subset_container_1 = full_container.subset_overlapping(grid, bbox_1) 

151 self.assertEqual(subset_container_1.offset, Index2D(x=1, y=1)) 

152 self.assertEqual(subset_container_1.shape, Index2D(x=3, y=2)) 

153 union_bbox_1 = Box2I() 

154 for v in subset_container_1.keys(): # noqa: SIM118 

155 cell_bbox = grid.bbox_of(v) 

156 self.assertTrue(cell_bbox.overlaps(bbox_1)) 

157 union_bbox_1.include(cell_bbox) 

158 self.assertTrue(union_bbox_1.contains(bbox_1)) 

159 self._check(subset_container_1) 

160 

161 # Subset the subset container with an even smaller bbox, to check the 

162 # case where the original offset is nonzero. 

163 bbox_2 = Box2I(Point2I(x=6, y=5), Point2I(x=7, y=5)) 

164 subset_container_2 = subset_container_1.subset_overlapping(grid, bbox_2) 

165 self.assertEqual(subset_container_2.offset, Index2D(x=1, y=1)) 

166 self.assertEqual(subset_container_2.shape, Index2D(x=2, y=1)) 

167 union_bbox_2 = Box2I() 

168 for v in subset_container_2.keys(): # noqa: SIM118 

169 cell_bbox = grid.bbox_of(v) 

170 self.assertTrue(cell_bbox.overlaps(bbox_1)) 

171 union_bbox_2.include(cell_bbox) 

172 self.assertTrue(union_bbox_2.contains(bbox_2)) 

173 self._check(subset_container_2) 

174 

175 # Subsetting the container by a bbox that isn't contained by the cells 

176 # raise a KeyError. 

177 with self.assertRaises(KeyError): 

178 subset_container_1.subset_overlapping(grid, full_bbox) 

179 

180 def test_rebuild_transformed(self) -> None: 

181 """Test that rebuild_transformed method works by squaring 

182 transformation. 

183 """ 

184 container = GridContainer(shape=Index2D(x=3, y=2)) 

185 self._fill(container) 

186 container = container.rebuild_transformed( 

187 transform=lambda cell: {key: value**2 for key, value in cell.items()} 

188 ) 

189 for index in container.indices(): 

190 self.assertTrue(container[index]["x"] == index.x**2) 

191 self.assertTrue(container[index]["y"] == index.y**2) 

192 

193 @methodParameters(offset=(Index2D(x=1, y=2), None)) 

194 def test_pickle(self, offset) -> None: 

195 """Test that we can serialize GridContainer with pickle.""" 

196 shape = Index2D(x=3, y=2) 

197 gc = GridContainer(shape, offset) 

198 self._fill(gc) 

199 grid_container = gc 

200 pickled_container = pickle.loads(pickle.dumps(grid_container, pickle.HIGHEST_PROTOCOL)) 

201 self.assertIsInstance(pickled_container, GridContainer) 

202 # This line below is failing, so compare internals. 

203 # self.assertEqual(pickled_container, grid_container) 

204 self.assertEqual(pickled_container.shape, grid_container.shape) 

205 self.assertEqual(pickled_container.offset, grid_container.offset) 

206 self.assertEqual(pickled_container.first, grid_container.first) 

207 self.assertEqual(pickled_container.last, grid_container.last) 

208 self.assertEqual(pickled_container.arbitrary, grid_container.arbitrary) 

209 self.assertListEqual(list(pickled_container.__iter__()), list(grid_container.__iter__())) 

210 self._check(pickled_container) 

211 

212 

213class TestMemory(lsst.utils.tests.MemoryTestCase): 

214 """Test for memory/resource leaks.""" 

215 

216 

217def setup_module(module): # noqa: D103 

218 lsst.utils.tests.init() 

219 

220 

221if __name__ == "__main__": 221 ↛ 222line 221 didn't jump to line 222 because the condition on line 221 was never true

222 lsst.utils.tests.init() 

223 unittest.main()