Coverage for tests / test_bbox.py: 13%

145 statements  

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

1# This file is part of scarlet_lite. 

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 copy import deepcopy 

23 

24import numpy as np 

25from lsst.scarlet.lite import Box 

26from utils import ScarletTestCase 

27 

28 

29class TestBox(ScarletTestCase): 

30 def check_bbox(self, bbox: Box, shape: tuple[int, ...], origin: tuple[int, ...]): 

31 """Check the attributes and properties of a Box 

32 

33 Parameters 

34 ---------- 

35 bbox: 

36 The box to test. 

37 shape: 

38 The shape of the Box. 

39 origin: 

40 The origin of the Box. 

41 """ 

42 self.assertTupleEqual(bbox.shape, shape) 

43 self.assertTupleEqual(bbox.origin, origin) 

44 self.assertEqual(bbox.ndim, len(shape)) 

45 self.assertTupleEqual(bbox.start, origin) 

46 

47 def test_constructors(self): 

48 shape = (2, 3, 4) 

49 origin = (0, 0, 0) 

50 bbox = Box(shape) 

51 self.check_bbox(bbox, shape=shape, origin=origin) 

52 

53 shape = (2, 4) 

54 origin = (5, 6) 

55 bbox = Box(shape, origin) 

56 self.check_bbox(bbox, shape=shape, origin=origin) 

57 

58 bbox = Box.from_bounds((5, 7), (6, 10)) 

59 self.check_bbox(bbox, shape, origin) 

60 

61 def test_from_data(self): 

62 x = np.arange(25).reshape(5, 5) 

63 x[0] = 0 

64 x[:, -2:] = 0 

65 bbox = Box.from_data(x) 

66 self.assertBoxEqual(bbox, Box((4, 3), origin=(1, 0))) 

67 

68 x += 10 

69 bbox = Box.from_data(x) 

70 self.assertBoxEqual(bbox, Box((5, 5), origin=(0, 0))) 

71 

72 bbox = Box.from_data(x, threshold=10) 

73 self.assertBoxEqual(bbox, Box((4, 3), origin=(1, 0))) 

74 

75 bbox = Box.from_data(x, threshold=100) 

76 self.assertBoxEqual(bbox, Box((0, 0), origin=(0, 0))) 

77 

78 def test_contains(self): 

79 bbox = Box((6, 4, 3), origin=(0, 1, 0)) 

80 p = (2, 2, 2) 

81 self.assertTrue(bbox.contains(p)) 

82 

83 p = (3, 0, 3) 

84 self.assertFalse(bbox.contains(p)) 

85 

86 p = (7, 3, 3) 

87 self.assertFalse(bbox.contains(p)) 

88 

89 p = (3, 3, -1) 

90 self.assertFalse(bbox.contains(p)) 

91 

92 with self.assertRaises(ValueError): 

93 bbox.contains((1, 2)) 

94 

95 def test_properties(self): 

96 shape = (10, 3, 8) 

97 origin = (2, 7, 5) 

98 bbox = Box(shape, origin) 

99 self.assertTupleEqual(bbox.stop, (12, 10, 13)) 

100 self.assertTupleEqual(bbox.center, (7, 8.5, 9)) 

101 self.assertTupleEqual(bbox.bounds, ((2, 12), (7, 10), (5, 13))) 

102 self.assertTupleEqual(bbox.shape, shape) 

103 self.assertTupleEqual(bbox.origin, origin) 

104 self.assertEqual(len(bbox.slices), 3) 

105 self.assertEqual(bbox.slices[0], slice(2, 12)) 

106 self.assertEqual(bbox.slices[1], slice(7, 10)) 

107 self.assertEqual(bbox.slices[2], slice(5, 13)) 

108 self.assertEqual(hash(bbox), hash((shape, origin))) 

109 

110 def test_simple_methods(self): 

111 shape = (2, 4, 8, 16) 

112 origin = (9, 5, 3, 9) 

113 bbox = Box(shape, origin) 

114 self.check_bbox(bbox, shape, origin) 

115 

116 # Grow the box 

117 grown = bbox.grow(3) 

118 self.check_bbox(grown, (8, 10, 14, 22), (6, 2, 0, 6)) 

119 

120 # Shift the box 

121 shifted = bbox.shifted_by((0, 5, 2, 10)) 

122 self.check_bbox(shifted, shape, (9, 10, 5, 19)) 

123 

124 def test_union(self): 

125 bbox1 = Box((3, 4), (20, 34)) 

126 bbox2 = Box((10, 15), (1, 2)) 

127 bbox3 = Box((20, 30, 40), (10, 20, 30)) 

128 

129 result = bbox1 | bbox2 

130 truth = Box((22, 36), (1, 2)) 

131 self.assertBoxEqual(result, truth) 

132 

133 with self.assertRaises(ValueError): 

134 bbox1 | bbox3 

135 

136 def test_intersection(self): 

137 bbox1 = Box((3, 4), (20, 34)) 

138 bbox2 = Box((20, 30), (10, 20)) 

139 bbox3 = Box((20, 30, 40), (10, 20, 30)) 

140 

141 result = bbox1 & bbox2 

142 truth = Box((3, 4), (20, 34)) 

143 self.assertBoxEqual(result, truth) 

144 

145 with self.assertRaises(ValueError): 

146 bbox1 & bbox3 

147 

148 def test_intersections(self): 

149 bbox1 = Box((3, 4), (20, 34)) 

150 bbox2 = Box((10, 15), (1, 2)) 

151 bbox3 = Box((20, 30), (10, 20)) 

152 

153 # Test intersection test 

154 self.assertFalse(bbox1.intersects(bbox2)) 

155 self.assertTrue(bbox1.intersects(bbox3)) 

156 self.assertFalse(bbox2.intersects(bbox1)) 

157 self.assertFalse(bbox2.intersects(bbox3)) 

158 self.assertTrue(bbox3.intersects(bbox1)) 

159 

160 # Test overlapping slices 

161 slices = bbox1.overlapped_slices(bbox2) 

162 self.assertTupleEqual(slices, ((slice(0, 0), slice(0, 0)), (slice(0, 0), slice(0, 0)))) 

163 slices = bbox1.overlapped_slices(bbox3) 

164 self.assertTupleEqual(slices, ((slice(0, 3), slice(0, 4)), (slice(10, 13), (slice(14, 18))))) 

165 

166 def test_offset(self): 

167 shape = (2, 5, 7) 

168 origin = (82, 34, 15) 

169 bbox = Box(shape, origin) 

170 bbox = bbox + 1 

171 self.assertBoxEqual(bbox, Box(shape, (83, 35, 16))) 

172 

173 def test_arithmetic(self): 

174 shape = (2, 5, 7) 

175 origin = (82, 34, 15) 

176 bbox = Box(shape, origin) 

177 

178 # Check addition 

179 shifted = bbox + (2, 4, 6) 

180 self.check_bbox(bbox, shape, origin) 

181 self.check_bbox(shifted, shape, (84, 38, 21)) 

182 

183 # Check subtraction 

184 shifted = bbox - (2, 4, 6) 

185 self.check_bbox(bbox, shape, origin) 

186 self.check_bbox(shifted, shape, (80, 30, 9)) 

187 

188 # Check "matrix multiplication" 

189 prebox = Box((2, 5), (3, 4)) 

190 new_box = prebox @ bbox 

191 self.check_bbox(new_box, (2, 5, 2, 5, 7), (3, 4, 82, 34, 15)) 

192 

193 # Check equality 

194 bbox1 = Box((1, 2, 3), (2, 4, 6)) 

195 bbox2 = Box((1, 2, 3), (2, 4, 6)) 

196 bbox3 = Box((1, 2), (5, 6)) 

197 

198 self.assertBoxEqual(bbox1, bbox2) 

199 with self.assertRaises(AssertionError): 

200 self.assertBoxEqual(bbox2, bbox3) 

201 

202 # Check a copy 

203 bbox2 = bbox.copy() 

204 self.assertBoxEqual(bbox, bbox2) 

205 

206 self.assertFalse(bbox1 == shape) 

207 self.assertNotEqual(bbox1, bbox2) 

208 self.assertEqual(bbox1, bbox1) 

209 

210 def test_slicing(self): 

211 bbox = Box((1, 2, 3, 4), (2, 4, 6, 8)) 

212 # Check integer index 

213 self.assertBoxEqual(bbox[2], Box((3,), (6,))) 

214 # check slice index 

215 self.assertBoxEqual(bbox[:3], Box((1, 2, 3), (2, 4, 6))) 

216 # check tuple index 

217 self.assertBoxEqual(bbox[(3, 1)], Box((4, 2), (8, 4))) 

218 

219 def test_shallow_copy(self): 

220 bbox = Box((3, 4, 5), (10, 20, 30)) 

221 bbox_copy = bbox.copy() 

222 self.assertBoxEqual(bbox, bbox_copy) 

223 self.assertIsNot(bbox, bbox_copy) 

224 

225 def test_deepcopy(self): 

226 bbox = Box((3, 4, 5), (10, 20, 30)) 

227 bbox_deepcopy = deepcopy(bbox) 

228 self.assertBoxEqual(bbox, bbox_deepcopy) 

229 self.assertIsNot(bbox, bbox_deepcopy)