From bfd1c2e912fb1badf373b1083138f0d68834eab2 Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Thu, 30 Oct 2025 12:54:00 +0000 Subject: [PATCH 1/8] docs: add example using reproject to upscale resolution --- .../upscaling_resolution_via_reproject.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/upscaling_resolution_via_reproject.py diff --git a/examples/upscaling_resolution_via_reproject.py b/examples/upscaling_resolution_via_reproject.py new file mode 100644 index 000000000..800490351 --- /dev/null +++ b/examples/upscaling_resolution_via_reproject.py @@ -0,0 +1,58 @@ +""" +===================================== +Upscaling the resolution of an NDCube +===================================== + +This example shows how to increase the resolution of an NDCube by reprojecting to a finer grid. +""" + +import matplotlib.pyplot as plt + +from astropy.io import fits +from astropy.wcs import WCS + +from sunpy.data.sample import AIA_171_IMAGE +from sunpy.visualization.colormaps import cm + +from ndcube import NDCube + +############################################################################## +# We start by creating an NDCube from sample solar data provided by SunPy. +# Here we use an AIA 171 image, but the same approach can be applied to other datasets, including those with non celestial axes. + +hdul = fits.open(AIA_171_IMAGE) +cube = NDCube(hdul[1].data, WCS(hdul[1].header)) + +########################################################################### +# Next, we define a new WCS with a finer pixel scale, note that while it is obvious that the CDELT values are changed to reflect the finer scale, +# the CRPIX values also need to be adjusted as the reference pixel position changes with the new scale. +# You can use any value for the scale factor, including non-integer values, greater or less than 1. +# You can also scale each axis independently. + +scale_factor = 1.5 +new_wcs = cube.wcs.deepcopy() +new_wcs.wcs.cdelt /= scale_factor +new_wcs.wcs.crpix *= scale_factor + +########################################################################### +# Now we can reproject the original cube to the new WCS with higher resolution. +new_shape = tuple(int(s * scale_factor) for s in cube.data.shape) +reprojected_cube = cube.reproject_to(new_wcs, shape_out=new_shape) + +########################################################################### +# Our new NDCube now has a higher resolution. +# We can compare the shapes of the original and reprojected cubes with new pixel axes. +print(cube.data.shape, reprojected_cube.data.shape) + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 8)) + +ax1.imshow(cube.data, origin='lower', cmap=cm.sdoaia171, vmin=10, vmax=10000) +ax1.set_title('Original') +ax1.set_xlabel('X [pixel]') +ax1.set_ylabel('Y [pixel]') +ax2.imshow(reprojected_cube.data, origin='lower', cmap=cm.sdoaia171, vmin=10, vmax=10000) +ax2.set_title('Reprojected (Upscaled)') +ax2.set_xlabel('X [pixel]') +ax2.set_ylabel('Y [pixel]') +plt.tight_layout() +plt.show() From 6d6a28cdead75da25e7dc0ae4444e6358babbf86 Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Thu, 30 Oct 2025 12:54:45 +0000 Subject: [PATCH 2/8] tests: added clearer error message for attempting to rebin with scale factors greater than 1 --- ndcube/ndcube.py | 4 +++- ndcube/tests/test_ndcube_reproject_and_rebin.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 2d672329b..8b1a0c036 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1267,9 +1267,11 @@ def my_propagate(uncertainty, data, mask, **kwargs): bin_shape[bin_shape == -1] = np.array(data_shape)[bin_shape == -1] if (bin_shape < 0).any(): raise ValueError("bin_shape should not be less than -1.") + if np.any(bin_shape > data_shape): + raise ValueError("bin_shape cannot be larger than data shape in any dimension.") if (np.mod(data_shape, bin_shape) != 0).any(): raise ValueError( - "bin shape must be an integer fraction of the data shape in each dimension. " + "bin_shape must be an integer fraction of the data shape in each dimension. " f"data shape: {data_shape}; bin shape: {bin_shape}" ) diff --git a/ndcube/tests/test_ndcube_reproject_and_rebin.py b/ndcube/tests/test_ndcube_reproject_and_rebin.py index 2af66f6f6..5b3d12378 100644 --- a/ndcube/tests/test_ndcube_reproject_and_rebin.py +++ b/ndcube/tests/test_ndcube_reproject_and_rebin.py @@ -263,13 +263,20 @@ def test_rebin_some_masked_uncerts_exclude_masked_values(ndcube_2d_ln_lt_mask_un def test_rebin_errors(ndcube_3d_l_ln_lt_ectime): cube = ndcube_3d_l_ln_lt_ectime # Wrong number of axes in bin_shape) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="bin_shape must have an entry for each"): cube.rebin((2,)) + # bin shape shouldn't have any negatives + with pytest.raises(ValueError, match="bin_shape should not be less than -1"): + cube.rebin((2, -2, 1)) # bin_shape not integer multiple of data shape. - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="bin_shape must be an integer fraction"): cube.rebin((9, 2, 1)) + # bin_shape larger than data shape. + with pytest.raises(ValueError, match="bin_shape cannot be larger than data shape"): + cube.rebin((20, 2, 1)) + def test_rebin_no_propagate(ndcube_2d_ln_lt_mask_uncert): # Execute rebin. From be3de107b6f922c29be87e992e5ce9278f829818 Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Thu, 30 Oct 2025 13:14:15 +0000 Subject: [PATCH 3/8] changelog --- changelog/893.doc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/893.doc.rst diff --git a/changelog/893.doc.rst b/changelog/893.doc.rst new file mode 100644 index 000000000..52382ce03 --- /dev/null +++ b/changelog/893.doc.rst @@ -0,0 +1 @@ +Added an example (:ref:`sphx_glr_generated_gallery_upscaling_resolution_via_reproject.py`) showing how to change resolution of an NDCube using `~ndcube.NDCube.reproject`. From 5f27e1e26b853555bc49a9371d8ba188ff13fbca Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 30 Oct 2025 13:22:45 +0000 Subject: [PATCH 4/8] Update changelog/893.doc.rst --- changelog/893.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/893.doc.rst b/changelog/893.doc.rst index 52382ce03..2dfa20d73 100644 --- a/changelog/893.doc.rst +++ b/changelog/893.doc.rst @@ -1 +1 @@ -Added an example (:ref:`sphx_glr_generated_gallery_upscaling_resolution_via_reproject.py`) showing how to change resolution of an NDCube using `~ndcube.NDCube.reproject`. +Added an example (:ref:`sphx_glr_generated_gallery_upscaling_resolution_via_reproject.py`) showing how to change resolution of an NDCube using `~ndcube.NDCube.reproject_to`. From 8b0012036c179e92653c56d43acb2107bb4da5dc Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Fri, 31 Oct 2025 14:13:33 +0000 Subject: [PATCH 5/8] slight touchups to language --- .../upscaling_resolution_via_reproject.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/upscaling_resolution_via_reproject.py b/examples/upscaling_resolution_via_reproject.py index 800490351..05878bb3d 100644 --- a/examples/upscaling_resolution_via_reproject.py +++ b/examples/upscaling_resolution_via_reproject.py @@ -16,8 +16,8 @@ from ndcube import NDCube -############################################################################## -# We start by creating an NDCube from sample solar data provided by SunPy. +############################################################################ +# We start by creating an NDCube from sample solar data provided by `sunpy``. # Here we use an AIA 171 image, but the same approach can be applied to other datasets, including those with non celestial axes. hdul = fits.open(AIA_171_IMAGE) @@ -25,31 +25,36 @@ ########################################################################### # Next, we define a new WCS with a finer pixel scale, note that while it is obvious that the CDELT values are changed to reflect the finer scale, -# the CRPIX values also need to be adjusted as the reference pixel position changes with the new scale. -# You can use any value for the scale factor, including non-integer values, greater or less than 1. +# the CRPIX values also need to be adjusted as the reference pixel position is different on the new pixel scale. +# You can scale the axes by any amount, including non-integer values, greater or less than 1. # You can also scale each axis independently. +# Here we scale both spatial axes by a factor of 1.5. scale_factor = 1.5 + new_wcs = cube.wcs.deepcopy() new_wcs.wcs.cdelt /= scale_factor new_wcs.wcs.crpix *= scale_factor +new_shape = tuple(int(s * scale_factor) for s in cube.data.shape) ########################################################################### -# Now we can reproject the original cube to the new WCS with higher resolution. -new_shape = tuple(int(s * scale_factor) for s in cube.data.shape) +# We reproject the original cube to the new WCS. reprojected_cube = cube.reproject_to(new_wcs, shape_out=new_shape) ########################################################################### # Our new NDCube now has a higher resolution. -# We can compare the shapes of the original and reprojected cubes with new pixel axes. print(cube.data.shape, reprojected_cube.data.shape) +########################################################################### +# We can plot the original and reprojected cubes side by side. + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 8)) ax1.imshow(cube.data, origin='lower', cmap=cm.sdoaia171, vmin=10, vmax=10000) ax1.set_title('Original') ax1.set_xlabel('X [pixel]') ax1.set_ylabel('Y [pixel]') + ax2.imshow(reprojected_cube.data, origin='lower', cmap=cm.sdoaia171, vmin=10, vmax=10000) ax2.set_title('Reprojected (Upscaled)') ax2.set_xlabel('X [pixel]') From 9cb90febbc8e3f527260539d773797552b894125 Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Mon, 3 Nov 2025 09:54:27 +0000 Subject: [PATCH 6/8] oops --- examples/upscaling_resolution_via_reproject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/upscaling_resolution_via_reproject.py b/examples/upscaling_resolution_via_reproject.py index 05878bb3d..b675b9168 100644 --- a/examples/upscaling_resolution_via_reproject.py +++ b/examples/upscaling_resolution_via_reproject.py @@ -17,7 +17,7 @@ from ndcube import NDCube ############################################################################ -# We start by creating an NDCube from sample solar data provided by `sunpy``. +# We start by creating an NDCube from sample solar data provided by SunPy. # Here we use an AIA 171 image, but the same approach can be applied to other datasets, including those with non celestial axes. hdul = fits.open(AIA_171_IMAGE) From 1a871947fa2529eb9806d509efdf53564906d002 Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Tue, 4 Nov 2025 20:03:25 +0000 Subject: [PATCH 7/8] changed from upsacling to simply changing res --- ...reproject.py => changing_resolution_via_reproject.py} | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename examples/{upscaling_resolution_via_reproject.py => changing_resolution_via_reproject.py} (89%) diff --git a/examples/upscaling_resolution_via_reproject.py b/examples/changing_resolution_via_reproject.py similarity index 89% rename from examples/upscaling_resolution_via_reproject.py rename to examples/changing_resolution_via_reproject.py index b675b9168..2d9f7274c 100644 --- a/examples/upscaling_resolution_via_reproject.py +++ b/examples/changing_resolution_via_reproject.py @@ -1,9 +1,9 @@ """ ===================================== -Upscaling the resolution of an NDCube +Changing the resolution of an NDCube ===================================== -This example shows how to increase the resolution of an NDCube by reprojecting to a finer grid. +This example shows how to change the resolution of an NDCube by reprojecting to a finer grid. """ import matplotlib.pyplot as plt @@ -20,8 +20,9 @@ # We start by creating an NDCube from sample solar data provided by SunPy. # Here we use an AIA 171 image, but the same approach can be applied to other datasets, including those with non celestial axes. -hdul = fits.open(AIA_171_IMAGE) -cube = NDCube(hdul[1].data, WCS(hdul[1].header)) +image_data = fits.getdata(AIA_171_IMAGE) +image_header = fits.getheader(AIA_171_IMAGE) +cube = NDCube(image_data, WCS(image_header)) ########################################################################### # Next, we define a new WCS with a finer pixel scale, note that while it is obvious that the CDELT values are changed to reflect the finer scale, From d08707f42cb7d05f023798a3020d2d8bfc0a20af Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Tue, 4 Nov 2025 20:11:02 +0000 Subject: [PATCH 8/8] updated c hangelog ref to new file name --- changelog/893.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/893.doc.rst b/changelog/893.doc.rst index 2dfa20d73..181b0184e 100644 --- a/changelog/893.doc.rst +++ b/changelog/893.doc.rst @@ -1 +1 @@ -Added an example (:ref:`sphx_glr_generated_gallery_upscaling_resolution_via_reproject.py`) showing how to change resolution of an NDCube using `~ndcube.NDCube.reproject_to`. +Added an example (:ref:`sphx_glr_generated_gallery_changing_resolution_via_reproject.py`) showing how to change resolution of an NDCube using `~ndcube.NDCube.reproject_to`.