diff --git a/changelog/893.doc.rst b/changelog/893.doc.rst new file mode 100644 index 000000000..181b0184e --- /dev/null +++ b/changelog/893.doc.rst @@ -0,0 +1 @@ +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`. diff --git a/examples/changing_resolution_via_reproject.py b/examples/changing_resolution_via_reproject.py new file mode 100644 index 000000000..2d9f7274c --- /dev/null +++ b/examples/changing_resolution_via_reproject.py @@ -0,0 +1,64 @@ +""" +===================================== +Changing the resolution of an NDCube +===================================== + +This example shows how to change 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. + +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, +# 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) + +########################################################################### +# 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. +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]') +ax2.set_ylabel('Y [pixel]') +plt.tight_layout() +plt.show() 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 9e0c90ecb..b91779e51 100644 --- a/ndcube/tests/test_ndcube_reproject_and_rebin.py +++ b/ndcube/tests/test_ndcube_reproject_and_rebin.py @@ -258,13 +258,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.