Coverage for tests / test_grid_container.py: 15%
133 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-22 08:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-22 08:54 +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/>.
22from __future__ import annotations
24import copy
25import pickle
26import unittest
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
35class GridContainerTestCase(unittest.TestCase):
36 """Tests for GridContainer and GridIndex/Index2D's C++/Python
37 translation.
38 """
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}
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)
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 )
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)
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)
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)
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()))
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)
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)
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)
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)
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)
213class TestMemory(lsst.utils.tests.MemoryTestCase):
214 """Test for memory/resource leaks."""
217def setup_module(module): # noqa: D103
218 lsst.utils.tests.init()
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()