From a3e264ec145d8ce446eaf6d752739d9eae251dd7 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 10:59:12 -0500 Subject: [PATCH 01/14] fix: raise on image views --- jax_galsim/fits.py | 2 +- jax_galsim/image.py | 119 +++++++++++++++++++------------- jax_galsim/interpolatedimage.py | 2 +- 3 files changed, 72 insertions(+), 51 deletions(-) diff --git a/jax_galsim/fits.py b/jax_galsim/fits.py index 1b67f03a..8c7837f9 100644 --- a/jax_galsim/fits.py +++ b/jax_galsim/fits.py @@ -19,7 +19,7 @@ def _maybe_convert_and_warn(image): "The dtype of the input image is not supported by jax_galsim. " "Converting to float64." ) - _image = image.view(dtype=jnp.float64) + _image = image.copy(dtype=jnp.float64) if hasattr(image, "header"): _image.header = image.header return _image diff --git a/jax_galsim/image.py b/jax_galsim/image.py index 7b2bff36..6a50d443 100644 --- a/jax_galsim/image.py +++ b/jax_galsim/image.py @@ -487,9 +487,72 @@ def imag(self): def conjugate(self): return self.__class__(self.array.conjugate(), bounds=self.bounds, wcs=self.wcs) - @implements(_galsim.Image.copy) - def copy(self): - return self.__class__(self.array.copy(), bounds=self.bounds, wcs=self.wcs) + @implements( + _galsim.Image.copy, + lax_description=( + "JAX-GalSim supports extra keyword arguments to ``.copy`` so " + "that users can make copies of images while also changing the image " + "properties (e.g., the wcs). The extra keywords behave exactly like " + "those of ``Image.view``." + ), + ) + def copy( + self, + scale=None, + wcs=None, + origin=None, + center=None, + dtype=None, + make_const=False, + contiguous=False, + ): + if origin is not None and center is not None: + raise _galsim.GalSimIncompatibleValuesError( + "Cannot provide both center and origin", center=center, origin=origin + ) + + if scale is not None: + if wcs is not None: + raise _galsim.GalSimIncompatibleValuesError( + "Cannot provide both scale and wcs", scale=scale, wcs=wcs + ) + wcs = PixelScale(scale) + elif wcs is not None: + if not isinstance(wcs, BaseWCS): + raise TypeError("wcs parameters must be a galsim.BaseWCS instance") + else: + wcs = self.wcs + + # Figure out the dtype for the return Image + dtype = dtype if dtype else self.dtype + + # If currently empty, just return a new empty image. + if not self.bounds.isDefined(): + return Image(wcs=wcs, dtype=dtype, make_const=make_const) + + # Recast the array type if necessary + array = self.array.copy() + if dtype != array.dtype: + # jax-galsim's rounding of float-to-int is platform dependent + # so we explicitly round to ints if needed + array = _safe_cast(array, jnp.issubdtype(dtype, jnp.integer), dtype) + elif contiguous: + # this is a noop since all jax arrays are contiguous + pass + else: + # do nothing here since we made copy above + pass + + # Make the return Image - already made copy above + ret = self.__class__(array, bounds=self.bounds, wcs=wcs, make_const=make_const) + + # Update the origin if requested + if origin is not None: + ret.setOrigin(origin) + elif center is not None: + ret.setCenter(center) + + return ret @implements(_galsim.Image.get_pixel_centers) def get_pixel_centers(self): @@ -930,7 +993,7 @@ def _copyFrom(self, rhs): @implements( _galsim.Image.view, - lax_description="Contrary to GalSim, this will create a copy of the orginal image.", + lax_description="JAX-GalSim does not support image views.", ) def view( self, @@ -942,51 +1005,9 @@ def view( make_const=False, contiguous=False, ): - if origin is not None and center is not None: - raise _galsim.GalSimIncompatibleValuesError( - "Cannot provide both center and origin", center=center, origin=origin - ) - - if scale is not None: - if wcs is not None: - raise _galsim.GalSimIncompatibleValuesError( - "Cannot provide both scale and wcs", scale=scale, wcs=wcs - ) - wcs = PixelScale(scale) - elif wcs is not None: - if not isinstance(wcs, BaseWCS): - raise TypeError("wcs parameters must be a galsim.BaseWCS instance") - else: - wcs = self.wcs - - # Figure out the dtype for the return Image - dtype = dtype if dtype else self.dtype - - # If currently empty, just return a new empty image. - if not self.bounds.isDefined(): - return Image(wcs=wcs, dtype=dtype, make_const=make_const) - - # Recast the array type if necessary - if dtype != self.array.dtype: - # jax-galsim's rounding of float-to-int is platform dependent - # so we explicitly round to ints if needed - array = _safe_cast(self.array, jnp.issubdtype(dtype, jnp.integer), dtype) - elif contiguous: - # this is a noop since all jax arrays are contiguous - pass - else: - array = self.array - - # Make the return Image - ret = self.__class__(array, bounds=self.bounds, wcs=wcs, make_const=make_const) - - # Update the origin if requested - if origin is not None: - ret.setOrigin(origin) - elif center is not None: - ret.setCenter(center) - - return ret + raise NotImplementedError( + "JAX-GalSim does not support views of images! Use ``.copy`` instead." + ) @implements(_galsim.Image.shift) def shift(self, *args, **kwargs): diff --git a/jax_galsim/interpolatedimage.py b/jax_galsim/interpolatedimage.py index f6558c12..0424e9d8 100644 --- a/jax_galsim/interpolatedimage.py +++ b/jax_galsim/interpolatedimage.py @@ -610,7 +610,7 @@ def _image(self): "InterpolatedImages do not support 'depixelize' in jax_galsim." ) else: - image = self._jax_children[0].view(dtype=float) + image = self._jax_children[0].copy(dtype=float) if self._jax_aux_data["_recenter_image"]: image.setCenter(0, 0) From 37180cecbd61063121ea6b43d18e921611735dba Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 11:04:20 -0500 Subject: [PATCH 02/14] test: use updated tests submodule --- tests/GalSim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GalSim b/tests/GalSim index 2ceb789d..2e8aacc9 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit 2ceb789db4c04644739c58bd629ee9fd79ae1ab8 +Subproject commit 2e8aacc9cd594724b77128e2bc30a68afcada3b0 From 7fbc2a5c419161470cfff7131b167ea19e546ffc Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 11:06:39 -0500 Subject: [PATCH 03/14] test: allow test failure --- tests/galsim_tests_config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/galsim_tests_config.yaml b/tests/galsim_tests_config.yaml index 2f96d201..46a64c18 100644 --- a/tests/galsim_tests_config.yaml +++ b/tests/galsim_tests_config.yaml @@ -146,3 +146,4 @@ allowed_failures: - "module 'jax_galsim' has no attribute 'zernike'" - "Invalid TFORM4: 1PE(7)" # see https://github.com/astropy/astropy/issues/15477 - "JAX-GalSim does not support truncated Moffat" + - "JAX-GalSim does not support views of images! Use ``.copy`` instead." From 30ab98e742c271c66078bfc2e460cb0f29d4a1cb Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 11:46:52 -0500 Subject: [PATCH 04/14] test: see what fails --- tests/galsim_tests_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/galsim_tests_config.yaml b/tests/galsim_tests_config.yaml index 46a64c18..3e886c7d 100644 --- a/tests/galsim_tests_config.yaml +++ b/tests/galsim_tests_config.yaml @@ -146,4 +146,4 @@ allowed_failures: - "module 'jax_galsim' has no attribute 'zernike'" - "Invalid TFORM4: 1PE(7)" # see https://github.com/astropy/astropy/issues/15477 - "JAX-GalSim does not support truncated Moffat" - - "JAX-GalSim does not support views of images! Use ``.copy`` instead." + # - "JAX-GalSim does not support views of images! Use ``.copy`` instead." From 630bf73d14d99267dc7d6cce463df62635fe4fa1 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 12:16:26 -0500 Subject: [PATCH 05/14] test: update to latest test module --- tests/GalSim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GalSim b/tests/GalSim index 2e8aacc9..36a69c8b 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit 2e8aacc9cd594724b77128e2bc30a68afcada3b0 +Subproject commit 36a69c8b4c814f4e6b5adc2352bd1fd24b3a73bb From c903405e9f61a99ce52f957e4acf64384dfc63c6 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 15:39:15 -0500 Subject: [PATCH 06/14] test: use latest submodule --- tests/GalSim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GalSim b/tests/GalSim index 36a69c8b..df210643 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit 36a69c8b4c814f4e6b5adc2352bd1fd24b3a73bb +Subproject commit df210643d3ce45c389520c41cb43ca98656898a3 From 0a8c53571c7bf9dc2621cb9585bba7a6ac4f3e32 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 16:07:20 -0500 Subject: [PATCH 07/14] test: update to latest testing module --- tests/GalSim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GalSim b/tests/GalSim index df210643..ae86b28f 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit df210643d3ce45c389520c41cb43ca98656898a3 +Subproject commit ae86b28f06ac75479649c94da8c48640dd9cc2dd From feaf6a5d0e26120f34418874332bb8d3ff1e3147 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 16:27:22 -0500 Subject: [PATCH 08/14] test: adjust tests for now views --- tests/GalSim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GalSim b/tests/GalSim index ae86b28f..bac4e43a 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit ae86b28f06ac75479649c94da8c48640dd9cc2dd +Subproject commit bac4e43adeec0155c1833d069439a0d9efad5e69 From 06613cc1a53e21a3f8fa9cd0f85a5feceebd8752 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 16:42:06 -0500 Subject: [PATCH 09/14] test: update to latest submodule --- tests/GalSim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GalSim b/tests/GalSim index bac4e43a..c943056b 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit bac4e43adeec0155c1833d069439a0d9efad5e69 +Subproject commit c943056ba17bf0241a0962ad32497b75e3e1bf6a From 3c9852e33cf534678c39a856758af472e391a213 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 16:54:07 -0500 Subject: [PATCH 10/14] test: allow error on image views --- tests/GalSim | 2 +- tests/galsim_tests_config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GalSim b/tests/GalSim index c943056b..fadb1494 160000 --- a/tests/GalSim +++ b/tests/GalSim @@ -1 +1 @@ -Subproject commit c943056ba17bf0241a0962ad32497b75e3e1bf6a +Subproject commit fadb1494a8599af328922719b62b421602d6f8e7 diff --git a/tests/galsim_tests_config.yaml b/tests/galsim_tests_config.yaml index 3e886c7d..46a64c18 100644 --- a/tests/galsim_tests_config.yaml +++ b/tests/galsim_tests_config.yaml @@ -146,4 +146,4 @@ allowed_failures: - "module 'jax_galsim' has no attribute 'zernike'" - "Invalid TFORM4: 1PE(7)" # see https://github.com/astropy/astropy/issues/15477 - "JAX-GalSim does not support truncated Moffat" - # - "JAX-GalSim does not support views of images! Use ``.copy`` instead." + - "JAX-GalSim does not support views of images! Use ``.copy`` instead." From d8296c38cde0212476f392fc2f5922c9f3dc4d74 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 16:56:31 -0500 Subject: [PATCH 11/14] test: add test to assert .view raises for images --- tests/jax/test_image_jax.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/jax/test_image_jax.py diff --git a/tests/jax/test_image_jax.py b/tests/jax/test_image_jax.py new file mode 100644 index 00000000..fb99f3fe --- /dev/null +++ b/tests/jax/test_image_jax.py @@ -0,0 +1,10 @@ +import numpy as np +import pytest + +import jax_galsim + + +def test_image_jax_view_raises(): + im = jax_galsim.ImageD(np.arange(20).reshape(4, 5)) + with pytest.raises(RuntimeError): + im.view() From d4aed01d0454c8fb3386e21d1d19d6a70d1db8ed Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 16:59:51 -0500 Subject: [PATCH 12/14] doc: update doc strings --- jax_galsim/image.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/jax_galsim/image.py b/jax_galsim/image.py index 6a50d443..3e8c0e69 100644 --- a/jax_galsim/image.py +++ b/jax_galsim/image.py @@ -18,14 +18,11 @@ IMAGE_LAX_DOCS = """\ Contrary to GalSim native Image, this implementation does not support -sharing of the underlying numpy array between different Images or Views. +sharing of the underlying array between different Images or views. This is due to the fact that in JAX numpy arrays are immutable, so any -operation applied to this Image will create a new ``jnp.ndarray``. - -In particular the following methods will create a copy of the Image: - -- ``Image.view()`` -- ``Image.subImage()`` +operation applied to this Image will create a new ``jnp.ndarray``. Making +a view via ``.view()`` will raise an error. Instead, use the ``.copy()`` +method. The ``Image.subImage()`` method will return a copy. """ @@ -993,7 +990,10 @@ def _copyFrom(self, rhs): @implements( _galsim.Image.view, - lax_description="JAX-GalSim does not support image views.", + lax_description=( + "JAX-GalSim does not support image views. This " + "method will raise an error if called." + ), ) def view( self, From 10efae528cd7abe7248ac275e7a8cb2f80cf0299 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 17:05:06 -0500 Subject: [PATCH 13/14] fix: check proper exception typer --- tests/jax/test_image_jax.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/jax/test_image_jax.py b/tests/jax/test_image_jax.py index fb99f3fe..19835f2b 100644 --- a/tests/jax/test_image_jax.py +++ b/tests/jax/test_image_jax.py @@ -6,5 +6,8 @@ def test_image_jax_view_raises(): im = jax_galsim.ImageD(np.arange(20).reshape(4, 5)) - with pytest.raises(RuntimeError): + with pytest.raises(NotImplementedError) as exc: im.view() + # for reasons I do not follow, pytest is not failing if I check + # the wrong exception type + assert exc.value.__class__ is NotImplementedError From 6b13a691789b9cf7c2fbba45f482b15aeeb6c8a1 Mon Sep 17 00:00:00 2001 From: beckermr Date: Tue, 12 May 2026 17:07:36 -0500 Subject: [PATCH 14/14] test: clean up code I do not need --- tests/jax/test_image_jax.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/jax/test_image_jax.py b/tests/jax/test_image_jax.py index 19835f2b..db14bb8c 100644 --- a/tests/jax/test_image_jax.py +++ b/tests/jax/test_image_jax.py @@ -6,8 +6,5 @@ def test_image_jax_view_raises(): im = jax_galsim.ImageD(np.arange(20).reshape(4, 5)) - with pytest.raises(NotImplementedError) as exc: + with pytest.raises(NotImplementedError): im.view() - # for reasons I do not follow, pytest is not failing if I check - # the wrong exception type - assert exc.value.__class__ is NotImplementedError