Coverage for tests/test_image.py: 6%

375 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-30 08:24 +0000

1# This file is part of lsst.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 

22import operator 

23 

24import numpy as np 

25from lsst.scarlet.lite import Box, Image 

26from lsst.scarlet.lite.image import MismatchedBandsError, MismatchedBoxError 

27from numpy.testing import assert_almost_equal, assert_array_equal 

28from utils import ScarletTestCase 

29 

30 

31class TestImage(ScarletTestCase): 

32 def test_constructors(self): 

33 # Default constructor 

34 data = np.arange(12).reshape(3, 4) # type: ignore 

35 image = Image(data) 

36 self.assertEqual(image.dtype, int) 

37 self.assertTupleEqual(image.bands, ()) 

38 self.assertEqual(image.n_bands, 0) 

39 assert_array_equal(image.shape, (3, 4)) 

40 self.assertEqual(image.height, 3) 

41 self.assertEqual(image.width, 4) 

42 assert_array_equal(image.yx0, (0, 0)) 

43 self.assertEqual(image.y0, 0) 

44 self.assertEqual(image.x0, 0) 

45 self.assertBoxEqual(image.bbox, Box((3, 4), (0, 0))) 

46 assert_array_equal(image.data, data) 

47 self.assertIsInstance(image.data, np.ndarray) 

48 self.assertNotIsInstance(image.data, Image) 

49 

50 # Test constructor with all parameters 

51 data = np.arange(24, dtype=float).reshape(2, 3, 4) # type: ignore 

52 bands = ("g", "i") 

53 y0, x0 = 10, 15 

54 image = Image( 

55 data, 

56 bands=bands, 

57 yx0=(y0, x0), 

58 ) 

59 self.assertEqual(image.dtype, float) 

60 assert_array_equal(image.bands, bands) 

61 self.assertEqual(image.n_bands, 2) 

62 assert_array_equal(image.shape, (2, 3, 4)) 

63 self.assertEqual(image.height, 3) 

64 self.assertEqual(image.width, 4) 

65 assert_array_equal(image.yx0, (10, 15)) 

66 self.assertEqual(image.y0, 10) 

67 self.assertEqual(image.x0, 15) 

68 self.assertBoxEqual(image.bbox, Box((3, 4), (10, 15))) 

69 assert_array_equal(image.data, data) 

70 self.assertIsInstance(image.data, np.ndarray) 

71 self.assertNotIsInstance(image.data, Image) 

72 

73 # test initializing an empty image from a bounding box 

74 image = Image.from_box(Box((10, 10), (13, 50))) 

75 self.assertImageEqual(image, Image(np.zeros((10, 10), dtype=float), bands=(), yx0=(13, 50))) 

76 bands = ("g", "r", "i") 

77 image = Image.from_box(Box((10, 10), (13, 50)), bands=bands) 

78 self.assertImageEqual(image, Image(np.zeros((3, 10, 10), dtype=float), bands=bands, yx0=(13, 50))) 

79 

80 with self.assertRaises(ValueError): 

81 Image(np.zeros((3, 4, 5)), bands=tuple("gr")) 

82 

83 truth = "Image:\n [[[0 1 2]\n [3 4 5]]]\n bands=('g',)\n bbox=Box(shape=(2, 3), origin=(3, 2))" 

84 data = np.arange(6).reshape(1, 2, 3) 

85 bands = tuple("g") 

86 yx0 = (3, 2) 

87 image = Image(data, bands=bands, yx0=yx0) 

88 self.assertEqual(str(image), truth) 

89 

90 def _binary_operation_test( 

91 self, 

92 lower_data: np.ndarray, 

93 higher_data: np.ndarray, 

94 lower_image: Image, 

95 higher_image: Image, 

96 op_name: str, 

97 ) -> None: 

98 lower = lower_image.copy() 

99 higher = higher_image.copy() 

100 op = getattr(operator, op_name) 

101 

102 # Test operation with constants 

103 for constant in (3, 3.14, 3.14 + 3j): 

104 if op_name in ("floordiv", "mod", "rshift", "lshift") and constant != 3: 

105 # Cannot use floats or complex numbers for some operations, 

106 # so skip them 

107 continue 

108 truth = op(lower_data, constant) 

109 truth_image = Image(truth, bands=lower.bands) 

110 result = op(lower, constant) 

111 assert_array_equal(result.data, truth) 

112 self.assertImageEqual(result, truth_image) 

113 

114 if op_name not in ("eq", "ne", "ge", "le", "lt", "gt") and (op_name != "pow" or constant == 3.14): 

115 truth = op(constant, lower_data) 

116 truth_image = Image(truth, bands=lower.bands) 

117 result = getattr(lower, f"__r{op_name}__")(constant) 

118 assert_array_equal(result.data, truth) 

119 self.assertImageEqual(result, truth_image) 

120 

121 if op_name in ["rshift", "lshift"]: 

122 # Shifting cannot be done with non-integer arrays 

123 return 

124 

125 # Test lower * higher 

126 truth = op(lower_data, higher_data) 

127 truth_image = Image(truth, bands=higher_image.bands) 

128 result = op(lower, higher) 

129 assert_array_equal(result.data, truth) 

130 self.assertImageEqual(result, truth_image) 

131 

132 if op_name not in ("eq", "ne", "ge", "le", "gt", "lt"): 

133 result = getattr(higher, f"__r{op_name}__")(lower) 

134 assert_array_equal(result.data, truth) 

135 self.assertImageEqual(result, truth_image) 

136 

137 truth = op(higher_data, lower_data) 

138 truth_image = Image(truth, bands=higher_image.bands) 

139 iop = getattr(operator, "i" + op_name) 

140 iop(higher, lower) 

141 assert_array_equal(higher.data, truth) 

142 self.assertImageEqual(higher, truth_image) 

143 

144 with self.assertRaises(ValueError): 

145 iop(lower_image, higher_image) 

146 

147 def check_simple_arithmetic(self, data_bool, data_int, data_float, bands): 

148 image_bool = Image(data_bool, bands=bands) 

149 image_int = Image(data_int, bands=bands) 

150 image_float = Image(data_float, bands=bands) 

151 

152 self.assertEqual(data_bool.dtype, bool) 

153 self.assertEqual(data_int.dtype, int) 

154 self.assertEqual(data_float.dtype, float) 

155 

156 # test casting for bool + int 

157 self._binary_operation_test( 

158 data_bool, 

159 data_int, 

160 image_bool, 

161 image_int, 

162 "add", 

163 ) 

164 

165 # Test binary operations 

166 binary_operations = ( 

167 "add", 

168 "sub", 

169 "mul", 

170 "truediv", 

171 "floordiv", 

172 "pow", 

173 "mod", 

174 "eq", 

175 "ne", 

176 "ge", 

177 "le", 

178 "gt", 

179 "lt", 

180 "rshift", 

181 "lshift", 

182 ) 

183 for op_name in binary_operations: 

184 if op_name == "pow": 

185 _data_int = np.abs(data_int) 

186 _data_int[_data_int == 0] = 1 

187 _image_int = image_int.copy() 

188 _image_int.data[:] = _data_int 

189 _data_float = np.abs(data_float) 

190 _data_float[_data_float == 0] = 1 

191 _image_float = image_float.copy() 

192 _image_float.data[:] = _data_float 

193 else: 

194 _data_float = data_float 

195 _image_float = image_float 

196 _data_int = data_int 

197 _image_int = image_int 

198 self._binary_operation_test( 

199 _data_int, 

200 _data_float, 

201 _image_int, 

202 _image_float, 

203 op_name, 

204 ) 

205 

206 # Test negation 

207 self.assertImageEqual(-image_float, Image(-data_float, bands=bands)) # type: ignore 

208 # Test unary positive operator 

209 self.assertImageEqual(+image_float, image_float) 

210 

211 # Test that matrix multiplication is not supported 

212 with self.assertRaises(TypeError): 

213 image_int @ image_float 

214 

215 with self.assertRaises(TypeError): 

216 image_int @= image_float 

217 

218 def test_simple_3d_arithmetic(self): 

219 np.random.seed(1) 

220 data_bool = np.random.choice((True, False), size=(2, 3, 4)) 

221 data_int = np.random.randint(-10, 10, (2, 3, 4)) 

222 data_int[data_int == 0] = 1 

223 data_float = (np.random.random((2, 3, 4)) - 0.5) * 10 

224 # Force a few pixels to be exactly equal so the comparison ops 

225 # (eq/ne, gt/ge, lt/le) actually differentiate strict from 

226 # non-strict comparisons (audit C-4). 

227 data_float[0, 0, 0] = data_int[0, 0, 0] 

228 data_float[1, 2, 3] = data_int[1, 2, 3] 

229 self.check_simple_arithmetic(data_bool, data_int, data_float, bands=("g", "r")) 

230 

231 def test_simple_2d_arithmetic(self): 

232 np.random.seed(1) 

233 data_bool = np.random.choice((True, False), size=(3, 4)) 

234 data_int = np.random.randint(-10, 10, (3, 4)) 

235 data_int[data_int == 0] = 1 

236 data_float = (np.random.random((3, 4)) - 0.5) * 10 

237 # See test_simple_3d_arithmetic for rationale. 

238 data_float[0, 0] = data_int[0, 0] 

239 data_float[2, 3] = data_int[2, 3] 

240 self.check_simple_arithmetic(data_bool, data_int, data_float, bands=None) 

241 

242 def test_3d_image_equality(self): 

243 # Note: equality of the arrays is tested in other tests. 

244 # This just checks that comparing non-images to images, 

245 # or images with different bounding boxes or bands raises 

246 # the appropriate exception. 

247 np.random.seed(1) 

248 bands = ("g", "r") 

249 data1 = np.random.randint(-10, 10, (2, 3, 4)) 

250 data2 = data1.astype(float) 

251 data3 = np.random.randint(-10, 10, (2, 3, 4)).astype(float) 

252 

253 image1 = Image(data1, bands=bands) 

254 image2 = Image(data2, bands=bands) 

255 image3 = Image(data3, bands=bands) 

256 

257 for op in (operator.eq, operator.ne): 

258 with self.assertRaises(TypeError): 

259 op(image1, data1) 

260 with self.assertRaises(MismatchedBandsError): 

261 op(image1, image2.copy_with(bands=("g", "i"))) 

262 with self.assertRaises(MismatchedBandsError): 

263 op(image1, image3.copy_with(bands=("g", "i"))) 

264 with self.assertRaises(MismatchedBoxError): 

265 op(image1, image2.copy_with(yx0=(30, 35))) 

266 with self.assertRaises(MismatchedBoxError): 

267 op(image1, image3.copy_with(yx0=(30, 35))) 

268 

269 def test_2d_image_equality(self): 

270 # Note: equality of the arrays is tested in other tests. 

271 # This just checks that comparing non-images to images, 

272 # or images with different bounding boxes or bands raises 

273 # the appropriate exception. 

274 np.random.seed(1) 

275 data1 = np.random.randint(-10, 10, (3, 4)) 

276 data2 = data1.astype(float) 

277 data3 = np.random.randint(-10, 10, (3, 4)).astype(float) 

278 

279 image1 = Image(data1) 

280 image2 = Image(data2) 

281 image3 = Image(data3) 

282 

283 for op in (operator.eq, operator.ne): 

284 with self.assertRaises(TypeError): 

285 op(image1, data1) 

286 with self.assertRaises(MismatchedBoxError): 

287 op(image1, image2.copy_with(yx0=(30, 35))) 

288 with self.assertRaises(MismatchedBoxError): 

289 op(image1, image3.copy_with(yx0=(30, 35))) 

290 

291 def test_simple_boolean_arithmetic(self): 

292 np.random.seed(1) 

293 # Test boolean operations 

294 boolean_operations = ( 

295 "and_", 

296 "or_", 

297 "xor", 

298 ) 

299 data1 = np.random.choice((True, False), size=(2, 3, 4)) 

300 data2 = np.random.choice((True, False), size=(2, 3, 4)) 

301 _image1 = Image(data1, bands=("g", "i")) 

302 _image2 = Image(data2, bands=("g", "i")) 

303 for op_name in boolean_operations: 

304 image1 = _image1.copy() 

305 image2 = _image2.copy() 

306 op = getattr(operator, op_name) 

307 result = op(image1, image2) 

308 data_result = op(data1, data2) 

309 self.assertImageEqual(result, Image(data_result, bands=("g", "i"))) 

310 

311 if op_name[-1] == "_": 

312 # Trim the underscore after `or` and `and` in operator 

313 op_name = op_name[:-1] 

314 

315 result = getattr(image2, f"__r{op_name}__")(image1) 

316 self.assertImageEqual(result, Image(data_result, bands=("g", "i"))) 

317 

318 iop = getattr(operator, "i" + op_name) 

319 iop(image1, image2) 

320 self.assertImageEqual(image1, Image(data_result, bands=("g", "i"))) 

321 

322 # Test inversion 

323 self.assertImageEqual(~_image1, Image(~data1, bands=("g", "i"))) 

324 

325 def _3d_mismatched_images_test( 

326 self, 

327 op_name: str, 

328 ): 

329 np.random.seed(1) 

330 op = getattr(operator, op_name) 

331 grizy = ("g", "r", "i", "z", "y") 

332 gir = ("g", "i", "r") 

333 igy = ("i", "g", "y") 

334 

335 # Test band insert 

336 if op_name == "add" or op_name == "subtract": 

337 data1 = (np.random.random((5, 3, 4)) - 0.5) * 10 

338 data2 = (np.random.random((3, 3, 4)) - 0.5) * 10 

339 image1 = Image(data1, bands=grizy) 

340 image2 = Image(data2, bands=gir) 

341 result = op(image1, image2) 

342 truth = np.zeros((5, 3, 4), dtype=float) 

343 truth += data1 

344 truth[(0, 2, 1), :, :] = op(truth[(0, 2, 1), :, :], data2) 

345 assert_almost_equal(result.data, truth) 

346 self.assertImageEqual(result, Image(truth, bands=grizy)) 

347 

348 # Test band mixture 

349 if op_name == "pow": 

350 data1 = np.random.random((3, 3, 4)) + 1 

351 data2 = np.random.random((3, 3, 4)) + 1 

352 else: 

353 data1 = (np.random.random((3, 3, 4)) - 0.5) * 10 

354 data2 = (np.random.random((3, 3, 4)) - 0.5) * 10 

355 image1 = Image(data1, bands=gir) 

356 image2 = Image(data2, bands=igy) 

357 result = op(image1, image2) 

358 truth = np.zeros((4, 3, 4), dtype=float) 

359 truth[(0, 1, 2), :, :] = data1 

360 truth[(1, 0, 3), :, :] = op(truth[(1, 0, 3), :, :], data2) 

361 assert_almost_equal(result.data, truth) 

362 self.assertImageEqual(result, Image(truth, bands=("g", "i", "r", "y"))) 

363 

364 # Test spatial offsets 

365 if op_name == "pow": 

366 data1 = np.random.random((3, 3, 4)) + 1 

367 data2 = np.random.random((3, 3, 4)) + 1 

368 else: 

369 data1 = (np.random.random((3, 3, 4)) - 0.5) * 10 

370 data2 = (np.random.random((3, 3, 4)) - 0.5) * 10 

371 image1 = Image(data1, bands=gir, yx0=(10, 20)) 

372 image2 = Image(data2, bands=gir, yx0=(11, 17)) 

373 result = op(image1, image2) 

374 

375 _data1 = np.zeros((3, 4, 7), dtype=float) 

376 _data2 = np.zeros((3, 4, 7), dtype=float) 

377 _data1[:, :3, 3:] = data1 

378 _data2[:, 1:, :4] = data2 

379 with np.errstate(divide="ignore", invalid="ignore"): 

380 truth = op(_data1, _data2) 

381 assert_almost_equal(result.data, truth) 

382 self.assertImageEqual(result, Image(truth, bands=gir, yx0=(10, 17))) 

383 

384 def _2d_mismatched_images_test( 

385 self, 

386 op_name: str, 

387 ): 

388 np.random.seed(1) 

389 op = getattr(operator, op_name) 

390 

391 # Test spatial offsets 

392 if op_name == "pow": 

393 data1 = np.random.random((3, 4)) + 1 

394 data2 = np.random.random((3, 4)) + 1 

395 else: 

396 data1 = (np.random.random((3, 4)) - 0.5) * 10 

397 data2 = (np.random.random((3, 4)) - 0.5) * 10 

398 image1 = Image(data1, yx0=(10, 20)) 

399 image2 = Image(data2, yx0=(11, 17)) 

400 result = op(image1, image2) 

401 

402 _data1 = np.zeros((4, 7), dtype=float) 

403 _data2 = np.zeros((4, 7), dtype=float) 

404 _data1[:3, 3:] = data1 

405 _data2[1:, :4] = data2 

406 with np.errstate(divide="ignore", invalid="ignore"): 

407 truth = op(_data1, _data2) 

408 assert_almost_equal(result.data, truth) 

409 self.assertImageEqual(result, Image(truth, yx0=(10, 17))) 

410 

411 def test_mismatchd_arithmetic(self): 

412 binary_operations = ( 

413 "add", 

414 "sub", 

415 "mul", 

416 "truediv", 

417 "floordiv", 

418 "pow", 

419 "mod", 

420 ) 

421 

422 for op_name in binary_operations: 

423 self._3d_mismatched_images_test(op_name) 

424 self._2d_mismatched_images_test(op_name) 

425 

426 def test_scalar_arithmetic(self): 

427 data = np.arange(6).reshape(1, 2, 3) 

428 bands = tuple("g") 

429 yx0 = (3, 2) 

430 image = Image(data, bands=bands, yx0=yx0) 

431 self.assertImageEqual(2 & image, Image(2 & data, bands=bands, yx0=yx0)) 

432 self.assertImageEqual(2 | image, Image(2 | data, bands=bands, yx0=yx0)) 

433 self.assertImageEqual(2 ^ image, Image(2 ^ data, bands=bands, yx0=yx0)) 

434 

435 with self.assertRaises(TypeError): 

436 image << 2.0 

437 with self.assertRaises(TypeError): 

438 image >> 2.0 

439 

440 image2 = image.copy() 

441 image2 <<= 2 

442 self.assertImageEqual(image2, Image(data << 2, bands=bands, yx0=yx0)) 

443 

444 image2 = image.copy() 

445 image2 >>= 2 

446 self.assertImageEqual(image2, Image(data >> 2, bands=bands, yx0=yx0)) 

447 

448 def test_slicing(self): 

449 bands = ("g", "r", "i", "z", "y") 

450 yx0 = (27, 82) 

451 data = (np.random.random((5, 30, 40)) - 0.5) * 10 

452 image = Image(data, bands=bands, yx0=yx0) 

453 image_2d = Image(data[0], yx0=yx0) 

454 

455 # test band slicing 

456 sub_img = image["g"] 

457 self.assertImageEqual(sub_img, Image(data[0], yx0=yx0)) 

458 

459 sub_img = image[:"g"] 

460 self.assertImageEqual(sub_img, Image(data[:1], bands=("g",), yx0=yx0)) 

461 

462 sub_img = image["g":"r"] 

463 self.assertImageEqual(sub_img, Image(data[0:2], bands=("g", "r"), yx0=yx0)) 

464 

465 sub_img = image["r":"z"] 

466 self.assertImageEqual(sub_img, Image(data[1:4], bands=("r", "i", "z"), yx0=yx0)) 

467 

468 sub_img = image["z":] 

469 self.assertImageEqual(sub_img, Image(data[-2:], bands=("z", "y"), yx0=yx0)) 

470 

471 sub_img = image[("z", "i", "y")] 

472 self.assertImageEqual(sub_img, Image(data[(3, 2, 4), :, :], bands=("z", "i", "y"), yx0=yx0)) 

473 

474 self.assertImageEqual(image[:], image) 

475 

476 # Test bounding box slicing 

477 sub_img = image[:, Box((10, 5), (37, 87))] 

478 self.assertImageEqual(sub_img, Image(data[:, 10:20, 5:10], bands=bands, yx0=(37, 87))) 

479 

480 sub_img = image[Box((10, 5), (37, 87))] 

481 self.assertImageEqual(sub_img, Image(data[:, 10:20, 5:10], bands=bands, yx0=(37, 87))) 

482 

483 sub_img = image_2d[Box((10, 5), (37, 87))] 

484 self.assertImageEqual(sub_img, Image(data[0, 10:20, 5:10], yx0=(37, 87))) 

485 

486 with self.assertRaises(IndexError): 

487 # Cannot index a single row, since it would not return an image 

488 _ = image["g", 0] 

489 

490 with self.assertRaises(IndexError): 

491 # Cannot index a single column, since it would not return an image 

492 _ = image[:, :, 0] 

493 

494 with self.assertRaises(IndexError): 

495 # Cannot use a tuple to select rows/columns 

496 _ = image[("r", "i"), (1, 2)] 

497 

498 with self.assertRaises(IndexError): 

499 # Cannot use a bounding box outside of the image 

500 _ = image[:, Box((10, 10), (0, 0))] 

501 

502 with self.assertRaises(IndexError): 

503 # Cannot use a bounding box partially outside of the image 

504 _ = image[:, Box((40, 40), (20, 80))] 

505 

506 with self.assertRaises(IndexError): 

507 # Too many spatial indices 

508 _ = image[:, :, :, :] 

509 

510 truth = ( 

511 (0, 1, 2, 3, 4), 

512 slice(27, 57), 

513 slice(82, 122), 

514 ) 

515 self.assertTupleEqual(image.multiband_slices, truth) 

516 

517 def test_overlap_detection(self): 

518 # Test 2D image 

519 image = Image(np.zeros((5, 6)), yx0=(10, 15)) 

520 slices = image.overlapped_slices(Box((8, 9), (7, 18))) 

521 truth = ((slice(0, 5), slice(3, 6)), (slice(3, 8), slice(0, 3))) 

522 self.assertTupleEqual(slices, truth) 

523 

524 # Test 3D image 

525 image = Image(np.zeros((3, 10, 12)), bands=("g", "r", "i"), yx0=(13, 21)) 

526 slices = image.overlapped_slices(Box((8, 9), (15, 18))) 

527 truth = ( 

528 (slice(None), slice(2, 10), slice(0, 6)), 

529 (slice(None), slice(0, 8), slice(3, 9)), 

530 ) 

531 self.assertTupleEqual(slices, truth) 

532 

533 # Test no overlap 

534 slices = image.overlapped_slices(Box((8, 9), (115, 118))) 

535 truth = ( 

536 (slice(None), slice(0, 0), slice(0, 0)), 

537 (slice(None), slice(0, 0), slice(0, 0)), 

538 ) 

539 self.assertTupleEqual(slices, truth) 

540 

541 def test_insertion(self): 

542 img1 = Image.from_box(Box((20, 20)), bands=tuple("gri")) 

543 img2 = Image.from_box(Box((5, 5), (11, 12)), bands=tuple("gi")) 

544 img2.data[:] = np.arange(1, 3)[:, None, None] 

545 img1.insert(img2) 

546 

547 truth = img1.copy() 

548 truth.data[0, 11:16, 12:17] = 1 

549 truth.data[2, 11:16, 12:17] = 2 

550 self.assertImageEqual(img1, truth) 

551 

552 def test_matched_spectral_indices(self): 

553 img1 = Image.from_box(Box((5, 5))) 

554 img2 = Image.from_box(Box((5, 5))) 

555 indices = img1.matched_spectral_indices(img2) 

556 self.assertTupleEqual(indices, ((), ())) 

557 

558 img3 = Image.from_box(Box((5, 5)), bands=tuple("gri")) 

559 with self.assertRaises(ValueError): 

560 img1.matched_spectral_indices(img3) 

561 

562 with self.assertRaises(ValueError): 

563 img3.matched_spectral_indices(img1) 

564 

565 def test_project(self): 

566 data = np.arange(30).reshape(5, 6) 

567 img = Image(data, yx0=(11, 15)) 

568 

569 result = img.project(bbox=Box((20, 20), (2, 3))) 

570 truth = np.zeros((20, 20)) 

571 truth[9:14, 12:18] = data 

572 truth = Image(truth, yx0=(2, 3)) 

573 self.assertImageEqual(result, truth) 

574 

575 data = np.arange(60).reshape(3, 4, 5) 

576 img = Image(data, bands=tuple("gri")) 

577 result = img.project(tuple("gi")) 

578 truth = data[(0, 2), :] 

579 self.assertImageEqual(result, Image(truth, bands=tuple("gi"))) 

580 

581 def test_repeat(self): 

582 data = np.arange(18).reshape(3, 6) 

583 image = Image(data, yx0=(15, 32)) 

584 result = image.repeat(tuple("grizy")) 

585 truth = np.array([data, data, data, data, data]) 

586 truth = Image(truth, bands=tuple("grizy"), yx0=(15, 32)) 

587 self.assertImageEqual(result, truth) 

588 

589 with self.assertRaises(ValueError): 

590 result.repeat(tuple("ubv"))