Coverage for tests / test_image.py: 14%

501 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-28 01:42 -0700

1# This file is part of afw. 

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 

22""" 

23Tests for Images 

24 

25Run with: 

26 python test_image.py 

27or 

28 pytest test_image.py 

29""" 

30 

31import itertools 

32import os.path 

33import shutil 

34import tempfile 

35import unittest 

36 

37import numpy as np 

38 

39import lsst.utils 

40import lsst.utils.tests 

41import lsst.pex.exceptions 

42import lsst.daf.base 

43import lsst.geom 

44import lsst.afw.image as afwImage 

45import lsst.afw.math as afwMath 

46from lsst.afw.fits import readMetadata 

47import lsst.afw.display as afwDisplay 

48 

49try: 

50 afwdataDir = lsst.utils.getPackageDir("afwdata") 

51except LookupError: 

52 afwdataDir = None 

53 

54try: 

55 type(display) 

56except NameError: 

57 display = False 

58 

59 

60def makeRampImage(width, height, imgClass=afwImage.ImageF): 

61 """Make a ramp image of the specified size and image class 

62 

63 Values start from 0 at the lower left corner and increase by 1 along rows 

64 """ 

65 im = imgClass(width, height) 

66 val = 0 

67 for yInd in range(height): 

68 for xInd in range(width): 

69 im[xInd, yInd] = val 

70 val += 1 

71 return im 

72 

73 

74class ImageTestCase(lsst.utils.tests.TestCase): 

75 """A test case for Image""" 

76 

77 def setUp(self): 

78 np.random.seed(1) 

79 self.val1, self.val2 = 10, 100 

80 self.image1 = afwImage.ImageF(lsst.geom.ExtentI(100, 200)) 

81 self.image1.set(self.val1) 

82 self.image2 = afwImage.ImageF(self.image1.getDimensions()) 

83 self.image2.set(self.val2) 

84 self.function = afwMath.PolynomialFunction2D(2) 

85 self.function.setParameters( 

86 list(range(self.function.getNParameters()))) 

87 

88 def tearDown(self): 

89 del self.image1 

90 del self.image2 

91 del self.function 

92 

93 def testArrays(self): 

94 for cls in (afwImage.ImageU, afwImage.ImageI, afwImage.ImageF, afwImage.ImageD): 

95 image1 = cls(lsst.geom.Extent2I(5, 6)) 

96 array1 = image1.getArray() 

97 self.assertEqual(array1.shape[0], image1.getHeight()) 

98 self.assertEqual(array1.shape[1], image1.getWidth()) 

99 image2 = cls(array1, False) 

100 self.assertEqual(array1.shape[0], image2.getHeight()) 

101 self.assertEqual(array1.shape[1], image2.getWidth()) 

102 image3 = afwImage.makeImageFromArray(array1) 

103 self.assertEqual(array1.shape[0], image2.getHeight()) 

104 self.assertEqual(array1.shape[1], image2.getWidth()) 

105 self.assertEqual(type(image3), cls) 

106 array2 = image1.array 

107 np.testing.assert_array_equal(array1, array2) 

108 array1[:, :] = np.random.uniform(low=0, high=10, size=array1.shape) 

109 for j in range(image1.getHeight()): 

110 for i in range(image1.getWidth()): 

111 self.assertEqual(image1[i, j, afwImage.LOCAL], array1[j, i]) 

112 self.assertEqual(image2[i, j, afwImage.LOCAL], array1[j, i]) 

113 array3 = np.random.uniform(low=0, high=10, 

114 size=array1.shape).astype(array1.dtype) 

115 image1.array[:] = array3 

116 np.testing.assert_array_equal(array1, array3) 

117 image1.array[2:4, 3:] = 10 

118 np.testing.assert_array_equal(array1[2:4, 3:], 10) 

119 array4 = image1.array.copy() 

120 array4 += 5 

121 image1.array += 5 

122 np.testing.assert_array_equal(image1.array, array4) 

123 with self.assertRaises(TypeError): 

124 image1.array = None 

125 with self.assertRaises(ValueError): 

126 image1.array = np.zeros((11, 10), dtype=image1.array.dtype) # bad shape 

127 

128 def testImagesOverlap(self): 

129 dim = lsst.geom.Extent2I(10, 8) 

130 # a set of bounding boxes, some of which overlap each other 

131 # and some of which do not, and include the full image bounding box 

132 bboxes = ( 

133 lsst.geom.Box2I(lsst.geom.Point2I(0, 0), dim), 

134 lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(3, 3)), 

135 lsst.geom.Box2I(lsst.geom.Point2I(2, 2), lsst.geom.Extent2I(6, 4)), 

136 lsst.geom.Box2I(lsst.geom.Point2I(4, 4), lsst.geom.Extent2I(6, 4)), 

137 ) 

138 

139 imageClasses = (afwImage.ImageF, afwImage.ImageD, afwImage.ImageI, afwImage.Mask) 

140 

141 for ImageClass1, ImageClass2 in itertools.product(imageClasses, imageClasses): 

142 with self.subTest(ImageClass1=str(ImageClass1), ImageClass2=str(ImageClass2)): 

143 image1 = ImageClass1(dim) 

144 self.assertTrue(afwImage.imagesOverlap(image1, image1)) 

145 

146 image2 = ImageClass2(dim) 

147 self.assertFalse(afwImage.imagesOverlap(image1, image2)) 

148 self.assertFalse(afwImage.imagesOverlap(image2, image1)) 

149 

150 for bboxa, bboxb in itertools.product(bboxes, bboxes): 

151 shouldOverlap = bboxa.overlaps(bboxb) 

152 with self.subTest(bboxa=repr(bboxa), bboxb=repr(bboxb)): 

153 subim1a = ImageClass1(image1, bboxa) 

154 subim1b = ImageClass1(image1, bboxb) 

155 self.assertEqual(afwImage.imagesOverlap(subim1a, subim1b), shouldOverlap) 

156 self.assertEqual(afwImage.imagesOverlap(subim1b, subim1a), shouldOverlap) 

157 

158 subim2b = ImageClass2(image2, bboxb) 

159 self.assertFalse(afwImage.imagesOverlap(subim1a, subim2b)) 

160 self.assertFalse(afwImage.imagesOverlap(subim2b, subim1a)) 

161 

162 def testInitializeImages(self): 

163 val = 666 

164 for ctor in (afwImage.ImageU, afwImage.ImageI, afwImage.ImageF, afwImage.ImageD): 

165 im = ctor(10, 10, val) 

166 self.assertEqual(im[0, 0], val) 

167 

168 im2 = ctor(lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

169 lsst.geom.Extent2I(10, 10)), val) 

170 self.assertEqual(im2[0, 0], val) 

171 

172 def testSetGetImages(self): 

173 self.image1.setXY0(3, 4) 

174 self.assertEqual(self.image1[0, 0, afwImage.LOCAL], self.val1) 

175 self.assertEqual(self.image1[3, 4], self.val1) 

176 self.image1[0, 0, afwImage.LOCAL] = 42. 

177 self.assertEqual(self.image1[0, 0, afwImage.LOCAL], 42.) 

178 self.assertEqual(self.image1[3, 4], 42.) 

179 self.image1[3, 4] = self.val1 

180 self.assertEqual(self.image1[0, 0, afwImage.LOCAL], self.val1) 

181 self.assertEqual(self.image1[3, 4], self.val1) 

182 

183 def testAllocateLargeImages(self): 

184 """Try to allocate a Very large image""" 

185 bbox = lsst.geom.BoxI(lsst.geom.PointI(-1 << 30, -1 << 30), 

186 lsst.geom.PointI(1 << 30, 1 << 30)) 

187 

188 def tst(): 

189 afwImage.ImageF(bbox) 

190 

191 self.assertRaises(lsst.pex.exceptions.LengthError, tst) 

192 

193 def testAddImages(self): 

194 self.image2 += self.image1 

195 self.image1 += self.val1 

196 

197 self.assertEqual(self.image1[0, 0], 2*self.val1) 

198 self.assertEqual(self.image2[0, 0], self.val1 + self.val2) 

199 

200 self.image1.set(self.val1) 

201 self.image1 += self.function 

202 

203 for j in range(self.image1.getHeight()): 

204 for i in range(self.image1.getWidth()): 

205 self.assertEqual(self.image1[i, j], 

206 self.val1 + self.function(i, j)) 

207 

208 def testAssignWithBBox(self): 

209 """Test assign(rhs, bbox) with non-empty bbox 

210 """ 

211 for xy0 in (lsst.geom.Point2I(*val) for val in ( 

212 (0, 0), 

213 (-100, 120), # an arbitrary value that is off the image 

214 )): 

215 destImDim = lsst.geom.Extent2I(5, 4) 

216 srcImDim = lsst.geom.Extent2I(3, 2) 

217 destIm = afwImage.ImageF(destImDim) 

218 destIm.setXY0(xy0) 

219 srcIm = makeRampImage(*srcImDim) 

220 srcIm.setXY0(55, -33) # an arbitrary value that should be ignored 

221 self.assertRaises(Exception, destIm.set, srcIm) # size mismatch 

222 

223 for validMin in (lsst.geom.Point2I(*val) for val in ( 

224 (0, 0), 

225 (2, 0), 

226 (0, 1), 

227 (1, 2), 

228 )): 

229 # None to omit the argument 

230 for origin in (None, afwImage.PARENT, afwImage.LOCAL): 

231 destIm[:] = -1.0 

232 bbox = lsst.geom.Box2I(validMin, srcIm.getDimensions()) 

233 if origin != afwImage.LOCAL: 

234 bbox.shift(lsst.geom.Extent2I(xy0)) 

235 if origin is None: 

236 destIm.assign(srcIm, bbox) 

237 destImView = afwImage.ImageF(destIm, bbox) 

238 else: 

239 destIm.assign(srcIm, bbox, origin) 

240 destImView = afwImage.ImageF(destIm, bbox, origin) 

241 self.assertFloatsEqual( 

242 destImView.getArray(), srcIm.getArray()) 

243 numPixNotAssigned = (destImDim[0] * destImDim[1]) - \ 

244 (srcImDim[0] * srcImDim[1]) 

245 self.assertEqual( 

246 np.sum(destIm.getArray() < -0.5), numPixNotAssigned) 

247 

248 for badMin in (lsst.geom.Point2I(*val) + lsst.geom.Extent2I(xy0) for val in ( 

249 (-1, 0), 

250 (3, 0), 

251 (0, -1), 

252 (1, 3), 

253 )): 

254 # None to omit the argument 

255 for origin in (None, afwImage.PARENT, afwImage.LOCAL): 

256 bbox = lsst.geom.Box2I(badMin, srcIm.getDimensions()) 

257 if origin != afwImage.LOCAL: 

258 bbox.shift(lsst.geom.Extent2I(xy0)) 

259 if origin is None: 

260 self.assertRaises(Exception, destIm.set, srcIm, bbox) 

261 else: 

262 self.assertRaises( 

263 Exception, destIm.set, srcIm, bbox, origin) 

264 

265 def testAssignWithoutBBox(self): 

266 """Test assign(rhs, [bbox]) with an empty bbox and with no bbox specified; both set all pixels 

267 """ 

268 for xy0 in (lsst.geom.Point2I(*val) for val in ( 

269 (0, 0), 

270 (-100, 120), # an arbitrary value that is off the image 

271 )): 

272 destImDim = lsst.geom.Extent2I(5, 4) 

273 destIm = afwImage.ImageF(destImDim) 

274 destIm.setXY0(xy0) 

275 srcIm = makeRampImage(*destImDim) 

276 srcIm.setXY0(55, -33) # an arbitrary value that should be ignored 

277 

278 destIm[:] = -1.0 

279 destIm.assign(srcIm) 

280 self.assertFloatsEqual(destIm.getArray(), srcIm.getArray()) 

281 

282 destIm[:] = -1.0 

283 destIm.assign(srcIm, lsst.geom.Box2I()) 

284 self.assertFloatsEqual(destIm.getArray(), srcIm.getArray()) 

285 

286 def testAddScaledImages(self): 

287 c = 10.0 

288 self.image1.scaledPlus(c, self.image2) 

289 

290 self.assertEqual(self.image1[0, 0], self.val1 + c*self.val2) 

291 

292 def testSubtractImages(self): 

293 self.image2 -= self.image1 

294 self.image1 -= self.val1 

295 

296 self.assertEqual(self.image1[0, 0], 0) 

297 self.assertEqual(self.image2[0, 0], self.val2 - self.val1) 

298 

299 self.image1.set(self.val1) 

300 self.image1 -= self.function 

301 

302 for j in range(self.image1.getHeight()): 

303 for i in range(self.image1.getWidth()): 

304 self.assertEqual(self.image1[i, j], 

305 self.val1 - self.function(i, j)) 

306 

307 def testArithmeticImagesMismatch(self): 

308 "Test arithmetic operations on Images of different sizes" 

309 i1 = afwImage.ImageF(100, 100) 

310 i1.set(100) 

311 i2 = afwImage.ImageF(10, 10) 

312 i2.set(10) 

313 

314 def tst1(i1, i2): 

315 i1 -= i2 

316 

317 def tst2(i1, i2): 

318 i1.scaledMinus(1.0, i2) 

319 

320 def tst3(i1, i2): 

321 i1 += i2 

322 

323 def tst4(i1, i2): 

324 i1.scaledPlus(1.0, i2) 

325 

326 def tst5(i1, i2): 

327 i1 *= i2 

328 

329 def tst6(i1, i2): 

330 i1.scaledMultiplies(1.0, i2) 

331 

332 def tst7(i1, i2): 

333 i1 /= i2 

334 

335 def tst8(i1, i2): 

336 i1.scaledDivides(1.0, i2) 

337 

338 tsts12 = [tst1, tst3, tst5, tst7] 

339 for tst in tsts12: 

340 self.assertRaises(lsst.pex.exceptions.LengthError, tst, i1, i2) 

341 

342 tsts21 = [tst2, tst4, tst6, tst8] 

343 for tst in tsts21: 

344 self.assertRaises(lsst.pex.exceptions.LengthError, tst, i2, i1) 

345 

346 def testSubtractScaledImages(self): 

347 c = 10.0 

348 self.image1.scaledMinus(c, self.image2) 

349 

350 self.assertEqual(self.image1[0, 0], self.val1 - c*self.val2) 

351 

352 def testMultiplyImages(self): 

353 self.image2 *= self.image1 

354 self.image1 *= self.val1 

355 

356 self.assertEqual(self.image1[0, 0], self.val1*self.val1) 

357 self.assertEqual(self.image2[0, 0], self.val2*self.val1) 

358 

359 def testMultiplesScaledImages(self): 

360 c = 10.0 

361 self.image1.scaledMultiplies(c, self.image2) 

362 

363 self.assertEqual(self.image1[0, 0], self.val1 * c*self.val2) 

364 

365 def testDivideImages(self): 

366 self.image2 /= self.image1 

367 self.image1 /= self.val1 

368 

369 self.assertEqual(self.image1[0, 0], 1) 

370 self.assertEqual(self.image2[0, 0], self.val2/self.val1) 

371 

372 def testDividesScaledImages(self): 

373 c = 10.0 

374 self.image1.scaledDivides(c, self.image2) 

375 

376 self.assertAlmostEqual(self.image1[0, 0], self.val1/(c*self.val2)) 

377 

378 def testCopyConstructors(self): 

379 dimage = afwImage.ImageF(self.image1, True) # deep copy 

380 simage = afwImage.ImageF(self.image1) # shallow copy 

381 

382 self.image1 += 2 # should only change dimage 

383 self.assertEqual(dimage[0, 0], self.val1) 

384 self.assertEqual(simage[0, 0], self.val1 + 2) 

385 

386 def testGeneralisedCopyConstructors(self): 

387 # these are generalised (templated) copy constructors in C++ 

388 imageU = self.image1.convertU() 

389 imageF = imageU.convertF() 

390 imageD = imageF.convertD() 

391 

392 self.assertEqual(imageU[0, 0], self.val1) 

393 self.assertEqual(imageF[0, 0], self.val1) 

394 self.assertEqual(imageD[0, 0], self.val1) 

395 

396 def checkImgPatch(self, img, x0=0, y0=0): 

397 """Check that a patch of an image is correct; origin of patch is at (x0, y0)""" 

398 

399 self.assertEqual(img[x0 - 1, y0 - 1, afwImage.LOCAL], self.val1) 

400 self.assertEqual(img[x0, y0, afwImage.LOCAL], 666) 

401 self.assertEqual(img[x0 + 3, y0, afwImage.LOCAL], self.val1) 

402 self.assertEqual(img[x0, y0 + 1, afwImage.LOCAL], 666) 

403 self.assertEqual(img[x0 + 3, y0 + 1, afwImage.LOCAL], self.val1) 

404 self.assertEqual(img[x0, y0 + 2, afwImage.LOCAL], self.val1) 

405 

406 def testOrigin(self): 

407 """Check that we can set and read the origin""" 

408 

409 im = afwImage.ImageF(10, 20) 

410 x0 = y0 = 0 

411 

412 self.assertEqual(im.getX0(), x0) 

413 self.assertEqual(im.getY0(), y0) 

414 self.assertEqual(im.getXY0(), lsst.geom.Point2I(x0, y0)) 

415 

416 x0, y0 = 3, 5 

417 im.setXY0(x0, y0) 

418 self.assertEqual(im.getX0(), x0) 

419 self.assertEqual(im.getY0(), y0) 

420 self.assertEqual(im.getXY0(), lsst.geom.Point2I(x0, y0)) 

421 

422 x0, y0 = 30, 50 

423 im.setXY0(lsst.geom.Point2I(x0, y0)) 

424 self.assertEqual(im.getX0(), x0) 

425 self.assertEqual(im.getY0(), y0) 

426 self.assertEqual(im.getXY0(), lsst.geom.Point2I(x0, y0)) 

427 

428 def testSubimages(self): 

429 simage1 = afwImage.ImageF( 

430 self.image1, 

431 lsst.geom.Box2I(lsst.geom.Point2I(1, 1), lsst.geom.Extent2I(10, 5)), 

432 afwImage.LOCAL) 

433 

434 simage = afwImage.ImageF( 

435 simage1, 

436 lsst.geom.Box2I(lsst.geom.Point2I(1, 1), lsst.geom.Extent2I(3, 2)), 

437 afwImage.LOCAL 

438 ) 

439 self.assertEqual(simage.getX0(), 2) 

440 self.assertEqual(simage.getY0(), 2) # i.e. wrt self.image1 

441 

442 image2 = afwImage.ImageF(simage.getDimensions()) 

443 image2.set(666) 

444 simage[:] = image2 

445 del simage 

446 del image2 

447 

448 self.checkImgPatch(self.image1, 2, 2) 

449 self.checkImgPatch(simage1, 1, 1) 

450 

451 def testSubimages2(self): 

452 """Test subimages when we've played with the (x0, y0) value""" 

453 

454 self.image1[9, 4] = 888 

455 

456 simage1 = afwImage.ImageF( 

457 self.image1, 

458 lsst.geom.Box2I(lsst.geom.Point2I(1, 1), lsst.geom.Extent2I(10, 5)), 

459 afwImage.LOCAL 

460 ) 

461 # reset origin; doesn't affect pixel coordinate systems 

462 simage1.setXY0(lsst.geom.Point2I(0, 0)) 

463 

464 simage = afwImage.ImageF( 

465 simage1, 

466 lsst.geom.Box2I(lsst.geom.Point2I(1, 1), lsst.geom.Extent2I(3, 2)), 

467 afwImage.LOCAL 

468 ) 

469 self.assertEqual(simage.getX0(), 1) 

470 self.assertEqual(simage.getY0(), 1) 

471 

472 image2 = afwImage.ImageF(simage.getDimensions()) 

473 image2.set(666) 

474 simage[:] = image2 

475 del simage 

476 del image2 

477 

478 self.checkImgPatch(self.image1, 2, 2) 

479 self.checkImgPatch(simage1, 1, 1) 

480 

481 def testBadSubimages(self): 

482 def tst(): 

483 afwImage.ImageF( 

484 self.image1, 

485 lsst.geom.Box2I(lsst.geom.Point2I(1, -1), lsst.geom.Extent2I(10, 5)), 

486 afwImage.LOCAL 

487 ) 

488 

489 self.assertRaises(lsst.pex.exceptions.LengthError, tst) 

490 

491 def testImageInitialisation(self): 

492 dims = self.image1.getDimensions() 

493 factory = self.image1.Factory 

494 

495 self.image1.set(666) 

496 

497 del self.image1 # tempt C++ to reuse the memory 

498 self.image1 = factory(dims) 

499 self.assertEqual(self.image1[10, 10], 0) 

500 

501 def testImageSlicesOrigin(self): 

502 """Test image slicing, which generate sub-images using Box2I under the covers""" 

503 im = afwImage.ImageF(10, 20) 

504 im.setXY0(50, 100) 

505 im[54, 110] = 10 

506 im[-3:, -2:, afwImage.LOCAL] = 100 

507 im[-2, -2, afwImage.LOCAL] = -10 

508 sim = im[51:54, 106:110] 

509 sim[:] = -1 

510 im[50:54, 100:104] = im[2:6, 8:12, afwImage.LOCAL] 

511 

512 if display: 

513 afwDisplay.Display(frame=1).mtv(im, title="testImageSlicesOrigin") 

514 

515 self.assertEqual(im[0, 6, afwImage.LOCAL], 0) 

516 self.assertEqual(im[6, 17, afwImage.LOCAL], 0) 

517 self.assertEqual(im[7, 18, afwImage.LOCAL], 100) 

518 self.assertEqual(im[9, 19, afwImage.LOCAL], 100) 

519 self.assertEqual(im[8, 18, afwImage.LOCAL], -10) 

520 self.assertEqual(im[1, 6, afwImage.LOCAL], -1) 

521 self.assertEqual(im[3, 9, afwImage.LOCAL], -1) 

522 self.assertEqual(im[4, 10, afwImage.LOCAL], 10) 

523 self.assertEqual(im[4, 9, afwImage.LOCAL], 0) 

524 self.assertEqual(im[2, 2, afwImage.LOCAL], 10) 

525 self.assertEqual(im[0, 0, afwImage.LOCAL], -1) 

526 

527 def testImageSliceFromBox(self): 

528 """Test using a Box2I to index an Image""" 

529 im = afwImage.ImageF(10, 20) 

530 bbox = lsst.geom.BoxI(lsst.geom.PointI(1, 3), lsst.geom.PointI(6, 9)) 

531 im[bbox] = -1 

532 

533 if display: 

534 afwDisplay.Display(frame=0).mtv(im, title="testImageSliceFromBox") 

535 

536 self.assertEqual(im[0, 6], 0) 

537 self.assertEqual(im[1, 6], -1) 

538 self.assertEqual(im[3, 9], -1) 

539 

540 def testImageSliceFromBoxOrigin(self): 

541 """Test using a Box2I to index an Image""" 

542 im = afwImage.ImageF(10, 20) 

543 im.setXY0(50, 100) 

544 bbox = lsst.geom.BoxI(lsst.geom.PointI(51, 103), lsst.geom.ExtentI(6, 7)) 

545 im[bbox] = -1 

546 

547 if display: 

548 afwDisplay.Display(frame=2).mtv(im, title="testImageSliceFromBoxOrigin") 

549 

550 self.assertEqual(im[0, 6, afwImage.LOCAL], 0) 

551 self.assertEqual(im[1, 6, afwImage.LOCAL], -1) 

552 self.assertEqual(im[3, 9, afwImage.LOCAL], -1) 

553 

554 def testClone(self): 

555 """Test that clone works properly""" 

556 im = afwImage.ImageF(10, 20) 

557 im[0, 0] = 100 

558 

559 im2 = im.clone() # check that clone with no arguments makes a deep copy 

560 self.assertEqual(im.getDimensions(), im2.getDimensions()) 

561 self.assertEqual(im[0, 0], im2[0, 0]) 

562 im2[0, 0] += 100 

563 self.assertNotEqual(im[0, 0], im2[0, 0]) # so it's a deep copy 

564 

565 im2 = im[0:3, 0:5].clone() # check that we can slice-then-clone 

566 self.assertEqual(im2.getDimensions(), lsst.geom.ExtentI(3, 5)) 

567 self.assertEqual(im[0, 0], im2[0, 0]) 

568 im2[0, 0] += 10 

569 self.assertNotEqual(float(im[0, 0]), float(im2[0, 0])) 

570 

571 def testString(self): 

572 imageF = afwImage.ImageF(100, 100) 

573 imageDSmall = afwImage.ImageD(2, 2) 

574 imageISmall = afwImage.ImageI(2, 2) 

575 imageU = afwImage.ImageU(100, 100) 

576 

577 # NumPy's string representation varies depending on the size of the 

578 # array; we test both large and small. 

579 self.assertIn(str(np.zeros((100, 100), dtype=imageF.dtype)), str(imageF)) 

580 self.assertIn(f"bbox={imageF.getBBox()}", str(imageF)) 

581 

582 self.assertIn(str(np.zeros((2, 2), dtype=imageDSmall.dtype)), str(imageDSmall)) 

583 self.assertIn(str(np.zeros((2, 2), dtype=imageISmall.dtype)), str(imageISmall)) 

584 

585 self.assertIn("ImageF=", repr(imageF)) 

586 self.assertIn("ImageU=", repr(imageU)) 

587 

588 

589class DecoratedImageTestCase(lsst.utils.tests.TestCase): 

590 """A test case for DecoratedImage""" 

591 

592 def setUp(self): 

593 np.random.seed(1) 

594 self.val1, self.val2 = 10, 100 

595 self.width, self.height = 200, 100 

596 self.dimage1 = afwImage.DecoratedImageF( 

597 lsst.geom.Extent2I(self.width, self.height) 

598 ) 

599 self.dimage1.image.set(self.val1) 

600 

601 if afwdataDir is not None: 

602 self.fileForMetadata = os.path.join( 

603 afwdataDir, "data", "small_MI.fits") 

604 self.trueMetadata = {"RELHUMID": 10.69} 

605 

606 def tearDown(self): 

607 del self.dimage1 

608 

609 def testCreateDecoratedImage(self): 

610 self.assertEqual(self.dimage1.getWidth(), self.width) 

611 self.assertEqual(self.dimage1.getHeight(), self.height) 

612 self.assertEqual(self.dimage1.image[0, 0], self.val1) 

613 

614 def testCreateDecoratedImageFromImage(self): 

615 image = afwImage.ImageF(lsst.geom.Extent2I(self.width, self.height)) 

616 image[:] = self.dimage1.image 

617 

618 dimage = afwImage.DecoratedImageF(image) 

619 self.assertEqual(dimage.getWidth(), self.width) 

620 self.assertEqual(dimage.getHeight(), self.height) 

621 self.assertEqual(dimage.image[0, 0], self.val1) 

622 

623 def testCopyConstructors(self): 

624 dimage = afwImage.DecoratedImageF(self.dimage1, True) # deep copy 

625 self.dimage1.image[0, 0] = 1 + 2*self.val1 

626 self.assertEqual(dimage.image[0, 0], self.val1) 

627 

628 dimage = afwImage.DecoratedImageF(self.dimage1) # shallow copy 

629 self.dimage1.image[0, 0] = 1 + 2*self.val1 

630 self.assertNotEqual(dimage.image[0, 0], self.val1) 

631 

632 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

633 def testReadFits(self): 

634 """Test reading FITS files""" 

635 

636 hdus = {} 

637 hdus["img"] = 1 # an S16 fits HDU 

638 hdus["msk"] = 2 # an U8 fits HDU 

639 hdus["var"] = 3 # an F32 fits HDU 

640 

641 # read as unsigned short 

642 imgU = afwImage.DecoratedImageU(self.fileForMetadata, hdus["img"], allowUnsafe=True) 

643 # read as float 

644 imgF = afwImage.DecoratedImageF(self.fileForMetadata, hdus["img"]) 

645 

646 self.assertEqual(imgU.getHeight(), 256) 

647 self.assertEqual(imgF.image.getWidth(), 256) 

648 self.assertEqual(imgU.image[0, 0, afwImage.LOCAL], imgF.image[0, 0, afwImage.LOCAL]) 

649 # 

650 # Check the metadata 

651 # 

652 meta = self.trueMetadata 

653 for k in meta.keys(): 

654 self.assertEqual(imgU.getMetadata().getAsDouble(k), meta[k]) 

655 self.assertEqual(imgF.getMetadata().getAsDouble(k), meta[k]) 

656 # 

657 # Read an F32 image 

658 # 

659 # read as unsigned short 

660 varU = afwImage.DecoratedImageF(self.fileForMetadata, hdus["var"]) 

661 # read as float 

662 varF = afwImage.DecoratedImageF(self.fileForMetadata, hdus["var"]) 

663 

664 self.assertEqual(varU.getHeight(), 256) 

665 self.assertEqual(varF.image.getWidth(), 256) 

666 self.assertEqual(varU.image[0, 0, afwImage.LOCAL], varF.image[0, 0, afwImage.LOCAL]) 

667 # 

668 # Read a char image 

669 # 

670 maskImg = afwImage.DecoratedImageU( 

671 self.fileForMetadata, hdus["msk"]).image # read a char file 

672 

673 self.assertEqual(maskImg.getHeight(), 256) 

674 self.assertEqual(maskImg.getWidth(), 256) 

675 self.assertEqual(maskImg[0, 0, afwImage.LOCAL], 1) 

676 # 

677 # Read a U16 image 

678 # 

679 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

680 imgU.writeFits(tmpFile) 

681 

682 afwImage.DecoratedImageF(tmpFile) # read as unsigned short 

683 

684 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

685 def testWriteFits(self): 

686 """Test writing FITS files""" 

687 

688 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

689 if self.fileForMetadata: 

690 imgU = afwImage.DecoratedImageF(self.fileForMetadata) 

691 else: 

692 imgU = afwImage.DecoratedImageF() 

693 

694 self.dimage1.writeFits(tmpFile, imgU.getMetadata()) 

695 # 

696 # Read it back 

697 # 

698 rimage = afwImage.DecoratedImageF(tmpFile) 

699 

700 self.assertEqual(self.dimage1.image[0, 0, afwImage.LOCAL], 

701 rimage.image[0, 0, afwImage.LOCAL]) 

702 # 

703 # Check that we wrote (and read) the metadata successfully 

704 if self.fileForMetadata: 

705 meta = self.trueMetadata 

706 for k in meta.keys(): 

707 self.assertEqual( 

708 rimage.getMetadata().getAsDouble(k), meta[k]) 

709 

710 def testReadWriteXY0(self): 

711 """Test that we read and write (X0, Y0) correctly""" 

712 im = afwImage.ImageF(lsst.geom.Extent2I(10, 20)) 

713 

714 x0, y0 = 1, 2 

715 im.setXY0(x0, y0) 

716 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

717 im.writeFits(tmpFile) 

718 

719 im2 = im.Factory(tmpFile) 

720 

721 self.assertEqual(im2.getX0(), x0) 

722 self.assertEqual(im2.getY0(), y0) 

723 

724 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

725 def testReadMetadata(self): 

726 im = afwImage.DecoratedImageF(self.fileForMetadata) 

727 

728 meta = readMetadata(self.fileForMetadata) 

729 self.assertIn("NAXIS1", meta.names()) 

730 self.assertEqual(im.getWidth(), meta.getScalar("NAXIS1")) 

731 self.assertEqual(im.getHeight(), meta.getScalar("NAXIS2")) 

732 

733 def testTicket1040(self): 

734 """ How to repeat from #1040""" 

735 image = afwImage.ImageD(lsst.geom.Extent2I(6, 6)) 

736 image[2, 2] = 100 

737 

738 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 1), lsst.geom.Extent2I(5, 5)) 

739 subImage = image.Factory(image, bbox) 

740 subImageF = subImage.convertFloat() 

741 

742 if display: 

743 afwDisplay.Display(frame=0).mtv(subImage, title="subImage") 

744 afwDisplay.Display(frame=1).mtv(subImageF, title="converted subImage") 

745 

746 self.assertEqual(subImage[1, 1, afwImage.LOCAL], subImageF[1, 1, afwImage.LOCAL]) 

747 

748 def testDM882(self): 

749 """Test that we can write a dotted header unit to a FITS file. See DM-882.""" 

750 self.dimage1.getMetadata().add("A.B.C.D", 12345) 

751 tempdir = tempfile.mkdtemp() 

752 testfile = os.path.join(tempdir, "test.fits") 

753 try: 

754 self.dimage1.writeFits(testfile) 

755 meta = readMetadata(testfile) 

756 self.assertEqual(meta.getScalar("A.B.C.D"), 12345) 

757 finally: 

758 shutil.rmtree(tempdir, ignore_errors=True) 

759 

760 def testLargeImage(self): 

761 """Test that creating an extremely large image raises, rather than segfaulting. DM-89, -527.""" 

762 for imtype in (afwImage.ImageD, afwImage.ImageF, afwImage.ImageI, afwImage.ImageU): 

763 self.assertRaises(lsst.pex.exceptions.LengthError, 

764 imtype, 60000, 60000) 

765 

766 

767def printImg(img): 

768 print("%4s " % "", end=' ') 

769 for c in range(img.getWidth()): 

770 print("%7d" % c, end=' ') 

771 print() 

772 

773 for r in range(img.getHeight() - 1, -1, -1): 

774 print("%4d " % r, end=' ') 

775 for c in range(img.getWidth()): 

776 print("%7.1f" % float(img[c, r, afwImage.LOCAL]), end=' ') 

777 print() 

778 

779 

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

781 pass 

782 

783 

784def setup_module(module): 

785 lsst.utils.tests.init() 

786 

787 

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

789 lsst.utils.tests.init() 

790 unittest.main()