Coverage for tests / test_image.py: 14%
501 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 01:18 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 01:18 -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/>.
22"""
23Tests for Images
25Run with:
26 python test_image.py
27or
28 pytest test_image.py
29"""
31import itertools
32import os.path
33import shutil
34import tempfile
35import unittest
37import numpy as np
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
49try:
50 afwdataDir = lsst.utils.getPackageDir("afwdata")
51except LookupError:
52 afwdataDir = None
54try:
55 type(display)
56except NameError:
57 display = False
60def makeRampImage(width, height, imgClass=afwImage.ImageF):
61 """Make a ramp image of the specified size and image class
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
74class ImageTestCase(lsst.utils.tests.TestCase):
75 """A test case for Image"""
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())))
88 def tearDown(self):
89 del self.image1
90 del self.image2
91 del self.function
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
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 )
139 imageClasses = (afwImage.ImageF, afwImage.ImageD, afwImage.ImageI, afwImage.Mask)
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))
146 image2 = ImageClass2(dim)
147 self.assertFalse(afwImage.imagesOverlap(image1, image2))
148 self.assertFalse(afwImage.imagesOverlap(image2, image1))
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)
158 subim2b = ImageClass2(image2, bboxb)
159 self.assertFalse(afwImage.imagesOverlap(subim1a, subim2b))
160 self.assertFalse(afwImage.imagesOverlap(subim2b, subim1a))
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)
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)
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)
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))
188 def tst():
189 afwImage.ImageF(bbox)
191 self.assertRaises(lsst.pex.exceptions.LengthError, tst)
193 def testAddImages(self):
194 self.image2 += self.image1
195 self.image1 += self.val1
197 self.assertEqual(self.image1[0, 0], 2*self.val1)
198 self.assertEqual(self.image2[0, 0], self.val1 + self.val2)
200 self.image1.set(self.val1)
201 self.image1 += self.function
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))
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
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)
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)
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
278 destIm[:] = -1.0
279 destIm.assign(srcIm)
280 self.assertFloatsEqual(destIm.getArray(), srcIm.getArray())
282 destIm[:] = -1.0
283 destIm.assign(srcIm, lsst.geom.Box2I())
284 self.assertFloatsEqual(destIm.getArray(), srcIm.getArray())
286 def testAddScaledImages(self):
287 c = 10.0
288 self.image1.scaledPlus(c, self.image2)
290 self.assertEqual(self.image1[0, 0], self.val1 + c*self.val2)
292 def testSubtractImages(self):
293 self.image2 -= self.image1
294 self.image1 -= self.val1
296 self.assertEqual(self.image1[0, 0], 0)
297 self.assertEqual(self.image2[0, 0], self.val2 - self.val1)
299 self.image1.set(self.val1)
300 self.image1 -= self.function
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))
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)
314 def tst1(i1, i2):
315 i1 -= i2
317 def tst2(i1, i2):
318 i1.scaledMinus(1.0, i2)
320 def tst3(i1, i2):
321 i1 += i2
323 def tst4(i1, i2):
324 i1.scaledPlus(1.0, i2)
326 def tst5(i1, i2):
327 i1 *= i2
329 def tst6(i1, i2):
330 i1.scaledMultiplies(1.0, i2)
332 def tst7(i1, i2):
333 i1 /= i2
335 def tst8(i1, i2):
336 i1.scaledDivides(1.0, i2)
338 tsts12 = [tst1, tst3, tst5, tst7]
339 for tst in tsts12:
340 self.assertRaises(lsst.pex.exceptions.LengthError, tst, i1, i2)
342 tsts21 = [tst2, tst4, tst6, tst8]
343 for tst in tsts21:
344 self.assertRaises(lsst.pex.exceptions.LengthError, tst, i2, i1)
346 def testSubtractScaledImages(self):
347 c = 10.0
348 self.image1.scaledMinus(c, self.image2)
350 self.assertEqual(self.image1[0, 0], self.val1 - c*self.val2)
352 def testMultiplyImages(self):
353 self.image2 *= self.image1
354 self.image1 *= self.val1
356 self.assertEqual(self.image1[0, 0], self.val1*self.val1)
357 self.assertEqual(self.image2[0, 0], self.val2*self.val1)
359 def testMultiplesScaledImages(self):
360 c = 10.0
361 self.image1.scaledMultiplies(c, self.image2)
363 self.assertEqual(self.image1[0, 0], self.val1 * c*self.val2)
365 def testDivideImages(self):
366 self.image2 /= self.image1
367 self.image1 /= self.val1
369 self.assertEqual(self.image1[0, 0], 1)
370 self.assertEqual(self.image2[0, 0], self.val2/self.val1)
372 def testDividesScaledImages(self):
373 c = 10.0
374 self.image1.scaledDivides(c, self.image2)
376 self.assertAlmostEqual(self.image1[0, 0], self.val1/(c*self.val2))
378 def testCopyConstructors(self):
379 dimage = afwImage.ImageF(self.image1, True) # deep copy
380 simage = afwImage.ImageF(self.image1) # shallow copy
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)
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()
392 self.assertEqual(imageU[0, 0], self.val1)
393 self.assertEqual(imageF[0, 0], self.val1)
394 self.assertEqual(imageD[0, 0], self.val1)
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)"""
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)
406 def testOrigin(self):
407 """Check that we can set and read the origin"""
409 im = afwImage.ImageF(10, 20)
410 x0 = y0 = 0
412 self.assertEqual(im.getX0(), x0)
413 self.assertEqual(im.getY0(), y0)
414 self.assertEqual(im.getXY0(), lsst.geom.Point2I(x0, y0))
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))
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))
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)
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
442 image2 = afwImage.ImageF(simage.getDimensions())
443 image2.set(666)
444 simage[:] = image2
445 del simage
446 del image2
448 self.checkImgPatch(self.image1, 2, 2)
449 self.checkImgPatch(simage1, 1, 1)
451 def testSubimages2(self):
452 """Test subimages when we've played with the (x0, y0) value"""
454 self.image1[9, 4] = 888
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))
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)
472 image2 = afwImage.ImageF(simage.getDimensions())
473 image2.set(666)
474 simage[:] = image2
475 del simage
476 del image2
478 self.checkImgPatch(self.image1, 2, 2)
479 self.checkImgPatch(simage1, 1, 1)
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 )
489 self.assertRaises(lsst.pex.exceptions.LengthError, tst)
491 def testImageInitialisation(self):
492 dims = self.image1.getDimensions()
493 factory = self.image1.Factory
495 self.image1.set(666)
497 del self.image1 # tempt C++ to reuse the memory
498 self.image1 = factory(dims)
499 self.assertEqual(self.image1[10, 10], 0)
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]
512 if display:
513 afwDisplay.Display(frame=1).mtv(im, title="testImageSlicesOrigin")
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)
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
533 if display:
534 afwDisplay.Display(frame=0).mtv(im, title="testImageSliceFromBox")
536 self.assertEqual(im[0, 6], 0)
537 self.assertEqual(im[1, 6], -1)
538 self.assertEqual(im[3, 9], -1)
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
547 if display:
548 afwDisplay.Display(frame=2).mtv(im, title="testImageSliceFromBoxOrigin")
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)
554 def testClone(self):
555 """Test that clone works properly"""
556 im = afwImage.ImageF(10, 20)
557 im[0, 0] = 100
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
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]))
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)
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))
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))
585 self.assertIn("ImageF=", repr(imageF))
586 self.assertIn("ImageU=", repr(imageU))
589class DecoratedImageTestCase(lsst.utils.tests.TestCase):
590 """A test case for DecoratedImage"""
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)
601 if afwdataDir is not None:
602 self.fileForMetadata = os.path.join(
603 afwdataDir, "data", "small_MI.fits")
604 self.trueMetadata = {"RELHUMID": 10.69}
606 def tearDown(self):
607 del self.dimage1
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)
614 def testCreateDecoratedImageFromImage(self):
615 image = afwImage.ImageF(lsst.geom.Extent2I(self.width, self.height))
616 image[:] = self.dimage1.image
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)
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)
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)
632 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
633 def testReadFits(self):
634 """Test reading FITS files"""
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
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"])
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"])
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
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)
682 afwImage.DecoratedImageF(tmpFile) # read as unsigned short
684 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
685 def testWriteFits(self):
686 """Test writing FITS files"""
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()
694 self.dimage1.writeFits(tmpFile, imgU.getMetadata())
695 #
696 # Read it back
697 #
698 rimage = afwImage.DecoratedImageF(tmpFile)
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])
710 def testReadWriteXY0(self):
711 """Test that we read and write (X0, Y0) correctly"""
712 im = afwImage.ImageF(lsst.geom.Extent2I(10, 20))
714 x0, y0 = 1, 2
715 im.setXY0(x0, y0)
716 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
717 im.writeFits(tmpFile)
719 im2 = im.Factory(tmpFile)
721 self.assertEqual(im2.getX0(), x0)
722 self.assertEqual(im2.getY0(), y0)
724 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
725 def testReadMetadata(self):
726 im = afwImage.DecoratedImageF(self.fileForMetadata)
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"))
733 def testTicket1040(self):
734 """ How to repeat from #1040"""
735 image = afwImage.ImageD(lsst.geom.Extent2I(6, 6))
736 image[2, 2] = 100
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()
742 if display:
743 afwDisplay.Display(frame=0).mtv(subImage, title="subImage")
744 afwDisplay.Display(frame=1).mtv(subImageF, title="converted subImage")
746 self.assertEqual(subImage[1, 1, afwImage.LOCAL], subImageF[1, 1, afwImage.LOCAL])
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)
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)
767def printImg(img):
768 print("%4s " % "", end=' ')
769 for c in range(img.getWidth()):
770 print("%7d" % c, end=' ')
771 print()
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()
780class TestMemory(lsst.utils.tests.MemoryTestCase):
781 pass
784def setup_module(module):
785 lsst.utils.tests.init()
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()