Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions galsim/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1590,13 +1590,18 @@ def calculateFWHM(self, center=None, Imax=0.):

# Find the first value with I < 0.5 * Imax
k = np.argmax(data < 0.5 * Imax)
Ik = data[k] / Imax
# If there are duplicate rsq values, argmax won't be deterministic across numpy versions.
# To achieve consistent results, we take the average of all data values with the same rsq.
k2 = np.searchsorted(rsqf, rsqf[k], side='right')
Ik = np.mean(data[k:k2]) / Imax

# Interpolate (linearly) between this and the previous value.
if k == 0:
rsqhm = rsqf[0] * (0.5 / Ik)
else:
Ikm1 = data[k-1] / Imax
# Similarly, use the mean data for all rsq equal to rsqf[k-1].
k1 = np.searchsorted(rsqf, rsqf[k-1], side='left')
Ikm1 = np.mean(data[k1:k]) / Imax
rsqhm = (rsqf[k-1] * (Ik-0.5) + rsqf[k] * (0.5-Ikm1)) / (Ik-Ikm1)

# This has all been done in pixels. So normalize according to the pixel scale.
Expand Down Expand Up @@ -1789,7 +1794,10 @@ def __pow__(self, other):
def __ipow__(self, other):
if not isinstance(other, int) and not isinstance(other, float):
raise TypeError("Can only raise an image to a float or int power!")
self.array[:,:] **= other
if not self.isinteger or isinstance(other, int):
self.array[:,:] **= other
else:
self.array[:,:] = self._safe_cast(self.array ** other)
return self

def __neg__(self):
Expand Down
25 changes: 25 additions & 0 deletions tests/test_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,30 @@ def test_fwhm():
err_msg="non-square image.calculateFWHM is not accurate.")


def test_fwhm_deterministic_fwhm():
# This test was proposed by Wiliam Lucas in issue #1336.
# Numpy changed some details about how argsort works, so different numpy versions had
# given different answers to the following calculation.

gal = galsim.Gaussian(flux=1.e5, sigma=5.)
rng = galsim.BaseDeviate(1234)
img = gal.drawImage(method='phot', rng=rng, nx=50, ny=50)
fwhm = img.calculateFWHM()
# This value used to be different on systems with different numpy versions.
# Note: this only affects situations where the center is a pixel center or pixel corner
# (or some other very special locations) such that the rsq values include repeated values.
# Then argsort could return a different data index for what pixel was the first one past
# the half max value. For symmetric profiles, even this probalby wasn't a problem, since all
# data values at that radius have the same value. But for photon shooting, there is photon
# noise, which means the values are not identical, leading to indeterminacy in the FWHM value.
print(fwhm)

# The code was changed in GalSim version 2.7 to be insensitive to the details of what argsort
# does with repeated values. Specifically, we now take the mean of all data values with the
# same rsq value.
# Thus, this value should be consistent across any algorithmic changes numpy might make to
# how argsort works in detail.
assert np.isclose(fwhm, 13.715780373024351)

if __name__ == "__main__":
runtests(__file__)
Loading