Skip to content

Commit 967a7d8

Browse files
committedFeb 20, 2025
Add Image.array setter to avoid surprising results via numpy.array math operations
(#1272)
1 parent ac20811 commit 967a7d8

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed
 

‎galsim/image.py

+4
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ def iscontiguous(self):
479479
"""
480480
return self._array.strides[1]//self._array.itemsize == 1
481481

482+
@array.setter
483+
def array(self, other):
484+
self._array[:] = self._safe_cast(other)
485+
482486
@lazy_property
483487
def _image(self):
484488
cls = self._cpp_type[self.dtype]

‎tests/test_image.py

+78
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,19 @@ def test_Image_inplace_add():
17341734
err_msg="Inplace add in Image class does not match reference for dtypes = "
17351735
+str(types[i])+" and "+str(types[j]))
17361736

1737+
# Adding via array:
1738+
image4 = image2.copy()
1739+
image4.array += image2.array
1740+
np.testing.assert_allclose(image4.array, 2*image2.array)
1741+
image4.array[:] += image2.array
1742+
np.testing.assert_allclose(image4.array, 3*image2.array)
1743+
image4.array = image4.array + image2.array
1744+
np.testing.assert_allclose(image4.array, 4*image2.array)
1745+
with assert_raises(ValueError):
1746+
image4.array += image2.array[:2,:]
1747+
with assert_raises(ValueError):
1748+
image4.array = image4.array[:2,:] + image2.array[:2,:]
1749+
17371750
with assert_raises(ValueError):
17381751
image1 += image1.subImage(galsim.BoundsI(0,4,0,4))
17391752

@@ -1776,6 +1789,19 @@ def test_Image_inplace_subtract():
17761789
err_msg="Inplace subtract in Image class does not match reference for dtypes = "
17771790
+str(types[i])+" and "+str(types[j]))
17781791

1792+
# Subtracting via array:
1793+
image4 = 5*image2
1794+
image4.array -= image2.array
1795+
np.testing.assert_allclose(image4.array, 4*image2.array)
1796+
image4.array[:] -= image2.array
1797+
np.testing.assert_allclose(image4.array, 3*image2.array)
1798+
image4.array = image4.array - image2.array
1799+
np.testing.assert_allclose(image4.array, 2*image2.array)
1800+
with assert_raises(ValueError):
1801+
image4.array -= image2.array[:2,:]
1802+
with assert_raises(ValueError):
1803+
image4.array = image4.array[:2,:] - image2.array[:2,:]
1804+
17791805
with assert_raises(ValueError):
17801806
image1 -= image1.subImage(galsim.BoundsI(0,4,0,4))
17811807

@@ -1907,6 +1933,17 @@ def test_Image_inplace_scalar_add():
19071933
err_msg="Inplace scalar add in Image class does not match reference for dtype = "
19081934
+str(types[i]))
19091935

1936+
# Adding via array:
1937+
image4 = image1.copy()
1938+
image4.array += 1
1939+
np.testing.assert_allclose(image4.array, image1.array + 1)
1940+
image4.array[:] += 1
1941+
np.testing.assert_allclose(image4.array, image1.array + 2)
1942+
image4.array = image4.array + 1
1943+
np.testing.assert_allclose(image4.array, image1.array + 3)
1944+
with assert_raises(ValueError):
1945+
image4.array = image4.array[:2,:] + 1
1946+
19101947

19111948
@timer
19121949
def test_Image_inplace_scalar_subtract():
@@ -1961,6 +1998,17 @@ def test_Image_inplace_scalar_multiply():
19611998
err_msg="Inplace scalar multiply in Image class does"
19621999
+" not match reference for dtype = "+str(types[i]))
19632000

2001+
# Multiplying via array:
2002+
image4 = image2.copy()
2003+
image4.array *= 2
2004+
np.testing.assert_allclose(image4.array, 2*image2.array)
2005+
image4.array[:] *= 2
2006+
np.testing.assert_allclose(image4.array, 4*image2.array)
2007+
image4.array = image4.array * 2
2008+
np.testing.assert_allclose(image4.array, 8*image2.array)
2009+
with assert_raises(ValueError):
2010+
image4.array = image4.array[:2,:] * 2
2011+
19642012

19652013
@timer
19662014
def test_Image_inplace_scalar_divide():
@@ -1988,6 +2036,36 @@ def test_Image_inplace_scalar_divide():
19882036
err_msg="Inplace scalar divide in Image class does"
19892037
+" not match reference for dtype = "+str(types[i]))
19902038

2039+
# Dividing via array:
2040+
image4 = 16*image2
2041+
if simple_types[i] is int:
2042+
with assert_raises(TypeError):
2043+
image4.array /= 2
2044+
with assert_raises(TypeError):
2045+
image4.array[:] /= 2
2046+
np.testing.assert_array_equal(image4.array, 16*image2.array) # unchanged yet
2047+
image4.array //= 2
2048+
np.testing.assert_array_equal(image4.array, 8*image2.array)
2049+
image4.array[:] //= 2
2050+
np.testing.assert_array_equal(image4.array, 4*image2.array)
2051+
image4.array = image4.array // 2
2052+
np.testing.assert_array_equal(image4.array, 2*image2.array)
2053+
# The following works for integer by explicitly rounding and casting.
2054+
# The native numpy operation would use floor to cast to int, which is 1 smaller.
2055+
image4.array = (image4.array / 2.0001)
2056+
np.testing.assert_array_equal(image4.array, image2.array)
2057+
with assert_raises(ValueError):
2058+
image4.array = image4.array[:2,:] // 2
2059+
else:
2060+
image4.array /= 2
2061+
np.testing.assert_allclose(image4.array, 8*image2.array)
2062+
image4.array[:] /= 2
2063+
np.testing.assert_allclose(image4.array, 4*image2.array)
2064+
image4.array = image4.array / 2
2065+
np.testing.assert_allclose(image4.array, 2*image2.array)
2066+
with assert_raises(ValueError):
2067+
image4.array = image4.array[:2,:] / 2
2068+
19912069

19922070
@timer
19932071
def test_Image_inplace_scalar_pow():

0 commit comments

Comments
 (0)
Failed to load comments.