Coverage for tests/test_image.py: 14%

499 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-14 01:39 -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 

126 def testImagesOverlap(self): 

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

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

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

130 bboxes = ( 

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

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

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

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

135 ) 

136 

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

138 

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

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

141 image1 = ImageClass1(dim) 

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

143 

144 image2 = ImageClass2(dim) 

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

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

147 

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

149 shouldOverlap = bboxa.overlaps(bboxb) 

150 with self.subTest(bboxa=bboxa, bboxb=bboxb): 

151 subim1a = ImageClass1(image1, bboxa) 

152 subim1b = ImageClass1(image1, bboxb) 

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

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

155 

156 subim2b = ImageClass2(image2, bboxb) 

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

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

159 

160 def testInitializeImages(self): 

161 val = 666 

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

163 im = ctor(10, 10, val) 

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

165 

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

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

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

169 

170 def testSetGetImages(self): 

171 self.image1.setXY0(3, 4) 

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

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

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

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

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

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

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

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

180 

181 def testAllocateLargeImages(self): 

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

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

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

185 

186 def tst(): 

187 afwImage.ImageF(bbox) 

188 

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

190 

191 def testAddImages(self): 

192 self.image2 += self.image1 

193 self.image1 += self.val1 

194 

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

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

197 

198 self.image1.set(self.val1) 

199 self.image1 += self.function 

200 

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

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

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

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

205 

206 def testAssignWithBBox(self): 

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

208 """ 

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

210 (0, 0), 

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

212 )): 

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

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

215 destIm = afwImage.ImageF(destImDim) 

216 destIm.setXY0(xy0) 

217 srcIm = makeRampImage(*srcImDim) 

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

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

220 

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

222 (0, 0), 

223 (2, 0), 

224 (0, 1), 

225 (1, 2), 

226 )): 

227 # None to omit the argument 

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

229 destIm[:] = -1.0 

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

231 if origin != afwImage.LOCAL: 

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

233 if origin is None: 

234 destIm.assign(srcIm, bbox) 

235 destImView = afwImage.ImageF(destIm, bbox) 

236 else: 

237 destIm.assign(srcIm, bbox, origin) 

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

239 self.assertFloatsEqual( 

240 destImView.getArray(), srcIm.getArray()) 

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

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

243 self.assertEqual( 

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

245 

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

247 (-1, 0), 

248 (3, 0), 

249 (0, -1), 

250 (1, 3), 

251 )): 

252 # None to omit the argument 

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

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

255 if origin != afwImage.LOCAL: 

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

257 if origin is None: 

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

259 else: 

260 self.assertRaises( 

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

262 

263 def testAssignWithoutBBox(self): 

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

265 """ 

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

267 (0, 0), 

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

269 )): 

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

271 destIm = afwImage.ImageF(destImDim) 

272 destIm.setXY0(xy0) 

273 srcIm = makeRampImage(*destImDim) 

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

275 

276 destIm[:] = -1.0 

277 destIm.assign(srcIm) 

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

279 

280 destIm[:] = -1.0 

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

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

283 

284 def testAddScaledImages(self): 

285 c = 10.0 

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

287 

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

289 

290 def testSubtractImages(self): 

291 self.image2 -= self.image1 

292 self.image1 -= self.val1 

293 

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

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

296 

297 self.image1.set(self.val1) 

298 self.image1 -= self.function 

299 

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

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

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

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

304 

305 def testArithmeticImagesMismatch(self): 

306 "Test arithmetic operations on Images of different sizes" 

307 i1 = afwImage.ImageF(100, 100) 

308 i1.set(100) 

309 i2 = afwImage.ImageF(10, 10) 

310 i2.set(10) 

311 

312 def tst1(i1, i2): 

313 i1 -= i2 

314 

315 def tst2(i1, i2): 

316 i1.scaledMinus(1.0, i2) 

317 

318 def tst3(i1, i2): 

319 i1 += i2 

320 

321 def tst4(i1, i2): 

322 i1.scaledPlus(1.0, i2) 

323 

324 def tst5(i1, i2): 

325 i1 *= i2 

326 

327 def tst6(i1, i2): 

328 i1.scaledMultiplies(1.0, i2) 

329 

330 def tst7(i1, i2): 

331 i1 /= i2 

332 

333 def tst8(i1, i2): 

334 i1.scaledDivides(1.0, i2) 

335 

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

337 for tst in tsts12: 

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

339 

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

341 for tst in tsts21: 

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

343 

344 def testSubtractScaledImages(self): 

345 c = 10.0 

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

347 

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

349 

350 def testMultiplyImages(self): 

351 self.image2 *= self.image1 

352 self.image1 *= self.val1 

353 

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

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

356 

357 def testMultiplesScaledImages(self): 

358 c = 10.0 

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

360 

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

362 

363 def testDivideImages(self): 

364 self.image2 /= self.image1 

365 self.image1 /= self.val1 

366 

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

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

369 

370 def testDividesScaledImages(self): 

371 c = 10.0 

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

373 

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

375 

376 def testCopyConstructors(self): 

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

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

379 

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

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

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

383 

384 def testGeneralisedCopyConstructors(self): 

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

386 imageU = self.image1.convertU() 

387 imageF = imageU.convertF() 

388 imageD = imageF.convertD() 

389 

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

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

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

393 

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

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

396 

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

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

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

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

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

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

403 

404 def testOrigin(self): 

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

406 

407 im = afwImage.ImageF(10, 20) 

408 x0 = y0 = 0 

409 

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

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

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

413 

414 x0, y0 = 3, 5 

415 im.setXY0(x0, y0) 

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

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

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

419 

420 x0, y0 = 30, 50 

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

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

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

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

425 

426 def testSubimages(self): 

427 simage1 = afwImage.ImageF( 

428 self.image1, 

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

430 afwImage.LOCAL) 

431 

432 simage = afwImage.ImageF( 

433 simage1, 

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

435 afwImage.LOCAL 

436 ) 

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

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

439 

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

441 image2.set(666) 

442 simage[:] = image2 

443 del simage 

444 del image2 

445 

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

447 self.checkImgPatch(simage1, 1, 1) 

448 

449 def testSubimages2(self): 

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

451 

452 self.image1[9, 4] = 888 

453 

454 simage1 = afwImage.ImageF( 

455 self.image1, 

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

457 afwImage.LOCAL 

458 ) 

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

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

461 

462 simage = afwImage.ImageF( 

463 simage1, 

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

465 afwImage.LOCAL 

466 ) 

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

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

469 

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

471 image2.set(666) 

472 simage[:] = image2 

473 del simage 

474 del image2 

475 

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

477 self.checkImgPatch(simage1, 1, 1) 

478 

479 def testBadSubimages(self): 

480 def tst(): 

481 afwImage.ImageF( 

482 self.image1, 

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

484 afwImage.LOCAL 

485 ) 

486 

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

488 

489 def testImageInitialisation(self): 

490 dims = self.image1.getDimensions() 

491 factory = self.image1.Factory 

492 

493 self.image1.set(666) 

494 

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

496 self.image1 = factory(dims) 

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

498 

499 def testImageSlicesOrigin(self): 

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

501 im = afwImage.ImageF(10, 20) 

502 im.setXY0(50, 100) 

503 im[54, 110] = 10 

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

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

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

507 sim[:] = -1 

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

509 

510 if display: 

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

512 

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

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

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

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

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

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

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

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

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

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

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

524 

525 def testImageSliceFromBox(self): 

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

527 im = afwImage.ImageF(10, 20) 

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

529 im[bbox] = -1 

530 

531 if display: 

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

533 

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

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

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

537 

538 def testImageSliceFromBoxOrigin(self): 

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

540 im = afwImage.ImageF(10, 20) 

541 im.setXY0(50, 100) 

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

543 im[bbox] = -1 

544 

545 if display: 

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

547 

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

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

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

551 

552 def testClone(self): 

553 """Test that clone works properly""" 

554 im = afwImage.ImageF(10, 20) 

555 im[0, 0] = 100 

556 

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

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

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

560 im2[0, 0] += 100 

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

562 

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

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

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

566 im2[0, 0] += 10 

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

568 

569 def testString(self): 

570 imageF = afwImage.ImageF(100, 100) 

571 imageDSmall = afwImage.ImageD(2, 2) 

572 imageISmall = afwImage.ImageI(2, 2) 

573 imageU = afwImage.ImageU(100, 100) 

574 

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

576 # array; we test both large and small. 

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

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

579 

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

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

582 

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

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

585 

586 

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

588 """A test case for DecoratedImage""" 

589 

590 def setUp(self): 

591 np.random.seed(1) 

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

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

594 self.dimage1 = afwImage.DecoratedImageF( 

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

596 ) 

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

598 

599 if afwdataDir is not None: 

600 self.fileForMetadata = os.path.join( 

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

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

603 

604 def tearDown(self): 

605 del self.dimage1 

606 

607 def testCreateDecoratedImage(self): 

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

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

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

611 

612 def testCreateDecoratedImageFromImage(self): 

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

614 image[:] = self.dimage1.image 

615 

616 dimage = afwImage.DecoratedImageF(image) 

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

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

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

620 

621 def testCopyConstructors(self): 

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

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

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

625 

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

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

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

629 

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

631 def testReadFits(self): 

632 """Test reading FITS files""" 

633 

634 hdus = {} 

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

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

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

638 

639 # read as unsigned short 

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

641 # read as float 

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

643 

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

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

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

647 # 

648 # Check the metadata 

649 # 

650 meta = self.trueMetadata 

651 for k in meta.keys(): 

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

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

654 # 

655 # Read an F32 image 

656 # 

657 # read as unsigned short 

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

659 # read as float 

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

661 

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

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

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

665 # 

666 # Read a char image 

667 # 

668 maskImg = afwImage.DecoratedImageU( 

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

670 

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

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

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

674 # 

675 # Read a U16 image 

676 # 

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

678 imgU.writeFits(tmpFile) 

679 

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

681 

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

683 def testWriteFits(self): 

684 """Test writing FITS files""" 

685 

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

687 if self.fileForMetadata: 

688 imgU = afwImage.DecoratedImageF(self.fileForMetadata) 

689 else: 

690 imgU = afwImage.DecoratedImageF() 

691 

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

693 # 

694 # Read it back 

695 # 

696 rimage = afwImage.DecoratedImageF(tmpFile) 

697 

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

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

700 # 

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

702 if self.fileForMetadata: 

703 meta = self.trueMetadata 

704 for k in meta.keys(): 

705 self.assertEqual( 

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

707 

708 def testReadWriteXY0(self): 

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

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

711 

712 x0, y0 = 1, 2 

713 im.setXY0(x0, y0) 

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

715 im.writeFits(tmpFile) 

716 

717 im2 = im.Factory(tmpFile) 

718 

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

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

721 

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

723 def testReadMetadata(self): 

724 im = afwImage.DecoratedImageF(self.fileForMetadata) 

725 

726 meta = readMetadata(self.fileForMetadata) 

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

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

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

730 

731 def testTicket1040(self): 

732 """ How to repeat from #1040""" 

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

734 image[2, 2] = 100 

735 

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

737 subImage = image.Factory(image, bbox) 

738 subImageF = subImage.convertFloat() 

739 

740 if display: 

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

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

743 

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

745 

746 def testDM882(self): 

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

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

749 tempdir = tempfile.mkdtemp() 

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

751 try: 

752 self.dimage1.writeFits(testfile) 

753 meta = readMetadata(testfile) 

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

755 finally: 

756 shutil.rmtree(tempdir, ignore_errors=True) 

757 

758 def testLargeImage(self): 

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

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

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

762 imtype, 60000, 60000) 

763 

764 

765def printImg(img): 

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

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

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

769 print() 

770 

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

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

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

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

775 print() 

776 

777 

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

779 pass 

780 

781 

782def setup_module(module): 

783 lsst.utils.tests.init() 

784 

785 

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

787 lsst.utils.tests.init() 

788 unittest.main()