From 8467e178e40507536782bc615be2d754406b2b4a Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Tue, 23 Apr 2024 15:40:12 +0100 Subject: [PATCH 01/35] Implements new `shape` property on class Depricates `dimensions` property --- examples/slicing_ndcube.py | 18 ++++++------- ndcube/ndcube.py | 6 +++++ ndcube/ndcube_sequence.py | 4 +-- ndcube/tests/test_ndcube.py | 18 ++++++------- ndcube/tests/test_ndcubesequence.py | 40 ++++++++++++++--------------- 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/examples/slicing_ndcube.py b/examples/slicing_ndcube.py index 904559678..bfeb7b62c 100644 --- a/examples/slicing_ndcube.py +++ b/examples/slicing_ndcube.py @@ -54,8 +54,8 @@ example_cube.plot() ############################################################################## -# We can also inspect the dimensions of the cube: -example_cube.dimensions +# We can also inspect the shape of the cube: +example_cube.shape ############################################################################## # We can also inspect the world coordinates for all array elements: @@ -77,14 +77,14 @@ sliced_cube = example_cube[1, :, :] # here we can see we are left with a 2-D cube which is an image at one wavelength. -sliced_cube.dimensions +sliced_cube.shape # We can also index a region of interest of the cube at a particular wavelength. # Again note that we are slicing here based on the ``array`` index rather than cropping by # real world value sliced_cube = example_cube[1, 10:20, 20:40] -sliced_cube.dimensions +sliced_cube.shape # Now we can inspect the sliced cube, and see it's now a smaller region of interest. sliced_cube.plot() @@ -114,9 +114,9 @@ cropped_cube = example_cube.crop(point1, point2) ############################################################################## -# Similar to before, we can inspect the dimensions of the sliced cube: +# Similar to before, we can inspect the dimensions of the sliced cube via the shape property: -cropped_cube.dimensions +cropped_cube.shape ############################################################################## # and we can visualize it: @@ -134,7 +134,7 @@ ############################################################################## # we can inspect the dimensions of the cropped cube: -cropped_cube.dimensions +cropped_cube.shape ############################################################################## # and again visualize it: @@ -151,7 +151,7 @@ ############################################################################## # Check dimensions: -cropped_cube.dimensions +cropped_cube.shape ############################################################################## # Here we can just see how powerful this can be to easily crop over different world coordinates. @@ -162,4 +162,4 @@ point6 = [SkyCoord(200*u.arcsec, 100*u.arcsec, frame=frames.Helioprojective), SpectralCoord(10.6*u.angstrom)] cropped_cube = example_cube.crop(point5, point6) -cropped_cube.dimensions +cropped_cube.shape diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 40af1ba61..8a3858001 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -8,6 +8,7 @@ from collections.abc import Mapping, Iterable import numpy as np +from deprecated import deprecated import astropy.nddata import astropy.units as u @@ -413,10 +414,15 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) + @deprecated(version='3.0.0', reason='Quantity removed. Use `ndcube.NDCube.shape` instead.') @property def dimensions(self): return u.Quantity(self.data.shape, unit=u.pix) + @property + def shape(self): + return self.data.shape + @property def array_axis_physical_types(self): # Docstring in NDCubeABC. diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 1ede34e79..658fefca0 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -50,7 +50,7 @@ def dimensions(self): @property def _dimensions(self): - dimensions = [len(self.data) * u.pix] + list(self.data[0].dimensions) + dimensions = [len(self.data) * u.pix] + list(self.data[0].shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not # be the same. Therefore if the lengths are different, @@ -58,7 +58,7 @@ def _dimensions(self): if self._common_axis is not None: common_axis_lengths = [cube.data.shape[self._common_axis] for cube in self.data] if len(np.unique(common_axis_lengths)) != 1: - common_axis_dimensions = [cube.dimensions[self._common_axis] + common_axis_dimensions = [cube.shape[self._common_axis] for cube in self.data] dimensions[self._common_axis + 1] = u.Quantity( common_axis_dimensions, unit=common_axis_dimensions[0].unit) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index f7e2bd4d8..8660a7690 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -46,7 +46,7 @@ def test_wcs_object(all_ndcubes): indirect=("ndc",)) def test_slicing_ln_lt(ndc, item): sndc = ndc[item] - assert len(sndc.dimensions) == 2 + assert len(sndc.shape) == 2 assert set(sndc.wcs.world_axis_physical_types) == {"custom:pos.helioprojective.lat", "custom:pos.helioprojective.lon"} if sndc.uncertainty is not None: @@ -82,7 +82,7 @@ def test_slicing_ln_lt(ndc, item): indirect=("ndc",)) def test_slicing_wave(ndc, item): sndc = ndc[item] - assert len(sndc.dimensions) == 1 + assert len(sndc.shape) == 1 assert set(sndc.wcs.world_axis_physical_types) == {"em.wl"} if sndc.uncertainty is not None: assert np.allclose(sndc.data, sndc.uncertainty.array) @@ -117,7 +117,7 @@ def test_slicing_wave(ndc, item): indirect=("ndc",)) def test_slicing_split_celestial(ndc, item): sndc = ndc[item] - assert len(sndc.dimensions) == 2 + assert len(sndc.shape) == 2 if sndc.uncertainty is not None: assert np.allclose(sndc.data, sndc.uncertainty.array) if sndc.mask is not None: @@ -815,7 +815,7 @@ def test_rebin(ndcube_3d_l_ln_lt_ectime): expected_time.format = "fits" # Confirm output is as expected. - assert (output.dimensions.value == np.array([1, 2, 8])).all() + assert (output.shape.value == np.array([1, 2, 8])).all() assert (output.data == expected_data).all() assert (output.mask == expected_mask).all() assert output.uncertainty == expected_uncertainty @@ -1143,11 +1143,11 @@ def test_to_dask(ndcube_2d_dask): def test_squeeze(ndcube_4d_ln_l_t_lt): - assert np.array_equal(ndcube_4d_ln_l_t_lt.squeeze().dimensions, ndcube_4d_ln_l_t_lt.dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze().dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze(2).dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze([1,2]).dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0:1,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze(2).dimensions) + assert np.array_equal(ndcube_4d_ln_l_t_lt.squeeze().shape, ndcube_4d_ln_l_t_lt.shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].shape, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze().shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].shape, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze(2).shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0,0,:].shape, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze([1,2]).shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0:1,0,:].shape, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze(2).shape) def test_squeeze_error(ndcube_4d_ln_l_t_lt): diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index b952646cd..1929b471f 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -35,12 +35,12 @@ def derive_sliced_cube_dims(orig_cube_dims, tuple_item): def test_slice_sequence_axis(ndc, item): # Calculate expected dimensions of cubes with sequence after slicing. tuple_item = item if isinstance(item, tuple) else (item,) - expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].dimensions, tuple_item) + expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].shape, tuple_item) # Assert output is as expected. sliced_sequence = ndc[item] assert isinstance(sliced_sequence, NDCubeSequence) - assert int(sliced_sequence.dimensions[0].value) == tuple_item[0].stop - tuple_item[0].start - assert (sliced_sequence[0].dimensions == expected_cube0_dims).all() + assert int(sliced_sequence.shape[0].value) == tuple_item[0].stop - tuple_item[0].start + assert (sliced_sequence[0].shape == expected_cube0_dims).all() @pytest.mark.parametrize("ndc, item", @@ -54,9 +54,9 @@ def test_slice_sequence_axis(ndc, item): def test_extract_ndcube(ndc, item): cube = ndc[item] tuple_item = item if isinstance(item, tuple) else (item,) - expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].dimensions, tuple_item) + expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].shape, tuple_item) assert isinstance(cube, NDCube) - assert (cube.dimensions == expected_cube_dims).all() + assert (cube.shape == expected_cube_dims).all() @pytest.mark.parametrize("ndc, item, expected_common_axis", @@ -72,7 +72,7 @@ def test_slice_common_axis(ndc, item, expected_common_axis): assert sliced_sequence._common_axis == expected_common_axis -@pytest.mark.parametrize("ndc, item, expected_dimensions", +@pytest.mark.parametrize("ndc, item, expected_shape", ( ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7], (3 * u.pix, 2 * u.pix, @@ -96,14 +96,14 @@ def test_slice_common_axis(ndc, item, expected_common_axis): 2 * u.pix)) ), indirect=("ndc",)) -def test_index_as_cube(ndc, item, expected_dimensions): +def test_index_as_cube(ndc, item, expected_shape): sliced_sequence = ndc.index_as_cube[item] - sliced_dims = sliced_sequence.dimensions - for dim, expected_dim in zip(sliced_dims, expected_dimensions): + sliced_dims = sliced_sequence.shape + for dim, expected_dim in zip(sliced_dims, expected_shape): (dim == expected_dim).all() -@pytest.mark.parametrize("ndc, axis, expected_dimensions", +@pytest.mark.parametrize("ndc, axis, expected_shape", ( ("ndcubesequence_4c_ln_lt_l", 0, (8 * u.pix, 3 * u.pix, @@ -113,27 +113,27 @@ def test_index_as_cube(ndc, item, expected_dimensions): 4 * u.pix)) ), indirect=("ndc",)) -def test_explode_along_axis_common_axis_None(ndc, axis, expected_dimensions): +def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape): exploded_sequence = ndc.explode_along_axis(axis) - assert exploded_sequence.dimensions == expected_dimensions + assert exploded_sequence.shape == expected_shape assert exploded_sequence._common_axis is None @pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) def test_explode_along_axis_common_axis_same(ndc): exploded_sequence = ndc.explode_along_axis(2) - assert exploded_sequence.dimensions == (16*u.pix, 2*u.pix, 3*u.pix) + assert exploded_sequence.shape == (16*u.pix, 2*u.pix, 3*u.pix) assert exploded_sequence._common_axis == ndc._common_axis @pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) def test_explode_along_axis_common_axis_changed(ndc): exploded_sequence = ndc.explode_along_axis(0) - assert exploded_sequence.dimensions == (8*u.pix, 3*u.pix, 4*u.pix) + assert exploded_sequence.shape == (8*u.pix, 3*u.pix, 4*u.pix) assert exploded_sequence._common_axis == ndc._common_axis - 1 -@pytest.mark.parametrize("ndc, expected_dimensions", +@pytest.mark.parametrize("ndc, expected_shape", ( ("ndcubesequence_4c_ln_lt_l_cax1", (4 * u.pix, 2. * u.pix, @@ -141,18 +141,18 @@ def test_explode_along_axis_common_axis_changed(ndc): 4. * u.pix)), ), indirect=("ndc",)) -def test_dimensions(ndc, expected_dimensions): +def test_shape(ndc, expected_shape): unit_tester = unittest.TestCase() - unit_tester.assertEqual(ndc.dimensions, expected_dimensions) + unit_tester.assertEqual(ndc.shape, expected_shape) -@pytest.mark.parametrize("ndc, expected_dimensions", +@pytest.mark.parametrize("ndc, expected_shape", ( ("ndcubesequence_4c_ln_lt_l_cax1", [2., 12, 4] * u.pix), ), indirect=("ndc",)) -def test_cube_like_dimensions(ndc, expected_dimensions): - assert (ndc.cube_like_dimensions == expected_dimensions).all() +def test_cube_like_shape(ndc, expected_shape): + assert (ndc.cube_like_dimensions == expected_shape).all() @pytest.mark.parametrize("ndc", (("ndcubesequence_4c_ln_lt_l",)), indirect=("ndc",)) From 4e251f8eca98a9c769d6aa67f327e08512ee461b Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Tue, 23 Apr 2024 15:58:09 +0100 Subject: [PATCH 02/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 658fefca0..e4c144e3c 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -50,7 +50,7 @@ def dimensions(self): @property def _dimensions(self): - dimensions = [len(self.data) * u.pix] + list(self.data[0].shape) + dimensions = [len(self.data)] + list(self.data[0].shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not # be the same. Therefore if the lengths are different, From 6f1830927217dd40a6764015fc9e6f4ad13c8637 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Tue, 23 Apr 2024 15:58:32 +0100 Subject: [PATCH 03/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index e4c144e3c..a22d06e7e 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -49,7 +49,7 @@ def dimensions(self): return self._dimensions @property - def _dimensions(self): + def _shape(self): dimensions = [len(self.data)] + list(self.data[0].shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not From 859690bf17ff15fbca667d08088c0a9ad833878f Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Tue, 23 Apr 2024 15:59:20 +0100 Subject: [PATCH 04/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index a22d06e7e..32ec5ab28 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -58,10 +58,8 @@ def _shape(self): if self._common_axis is not None: common_axis_lengths = [cube.data.shape[self._common_axis] for cube in self.data] if len(np.unique(common_axis_lengths)) != 1: - common_axis_dimensions = [cube.shape[self._common_axis] - for cube in self.data] - dimensions[self._common_axis + 1] = u.Quantity( - common_axis_dimensions, unit=common_axis_dimensions[0].unit) + common_axis_dimensions = tuple([cube.shape[self._common_axis] + for cube in self.data]) return tuple(dimensions) @property From 4e58b826c37cb769afec5a32c1bb39b223518229 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Tue, 23 Apr 2024 16:06:17 +0100 Subject: [PATCH 05/35] Adds shape property to NDCubeSequence --- ndcube/ndcube_sequence.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 32ec5ab28..805c34ba7 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -46,7 +46,11 @@ def dimensions(self): """ The length of each axis including the sequence axis. """ - return self._dimensions + return tuple([d * u.pix for d in self._shape]) + + @property + def shape(self): + return self._shape @property def _shape(self): From 9151f7606cac5b050824745a2cc2ab46369b27f4 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Tue, 23 Apr 2024 16:47:30 +0100 Subject: [PATCH 06/35] Adds exceptions package --- ndcube/ndcube.py | 3 ++- ndcube/utils/exceptions.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 ndcube/utils/exceptions.py diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 553a73726..5862390f8 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -8,7 +8,6 @@ from collections.abc import Mapping, Iterable import numpy as np -from deprecated import deprecated import astropy.nddata import astropy.units as u @@ -29,6 +28,7 @@ from ndcube.global_coords import GlobalCoords, GlobalCoordsABC from ndcube.mixins import NDCubeSlicingMixin from ndcube.ndcube_sequence import NDCubeSequence +from ndcube.utils.exceptions import NDCubeDeprecationWarning from ndcube.utils.wcs_high_level_conversion import values_to_high_level_objects from ndcube.visualization import PlotterDescriptor from ndcube.wcs.wrappers import CompoundLowLevelWCS, ResampledLowLevelWCS @@ -417,6 +417,7 @@ def combined_wcs(self): @deprecated(version='3.0.0', reason='Quantity removed. Use `ndcube.NDCube.shape` instead.') @property def dimensions(self): + warnings.warn("Quantity removed. Use `ndcube.NDCube.shape` instead.", NDCubeDeprecationWarning) return u.Quantity(self.data.shape, unit=u.pix) @property diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py new file mode 100644 index 000000000..f70cc3e1b --- /dev/null +++ b/ndcube/utils/exceptions.py @@ -0,0 +1,35 @@ +""" +This module provides errors/exceptions and warnings of general use for NDCube. + +Exceptions that are specific to a given package should **not** be here, +but rather in the particular package. +""" +import warnings + +__all__ = ["NDCubeDeprecationWarning"] + + +class NDCubeWarning(UserWarning): + """ + A general NDCube warning + """ + +class NDCubeDeprecationWarning(FutureWarning, NDCubeWarning): + """ + A warning class to indicate a deprecated feature. + """ + +def warn_deprecated(msg, stacklevel=1): + """ + Raise a `NDCubeDeprecationWarning`. + + Parameters + ---------- + msg : str + Warning message. + stacklevel : int + This is interpreted relative to the call to this function, + e.g. ``stacklevel=1`` (the default) sets the stack level in the + code that calls this function. + """ + warnings.warn(msg, NDCubeDeprecationWarning , stacklevel + 1) From 9176883650e26d1170a1a8122a74e0167d589a31 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:11:39 +0100 Subject: [PATCH 07/35] Update ndcube/utils/exceptions.py Co-authored-by: Nabil Freij --- ndcube/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py index f70cc3e1b..f79876f35 100644 --- a/ndcube/utils/exceptions.py +++ b/ndcube/utils/exceptions.py @@ -1,7 +1,7 @@ """ This module provides errors/exceptions and warnings of general use for NDCube. -Exceptions that are specific to a given package should **not** be here, +Exceptions that are specific to a given subpackage should **not** be here, but rather in the particular package. """ import warnings From 237a34900dbf9508071e086a26844d27ebddd377 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:11:53 +0100 Subject: [PATCH 08/35] Update ndcube/utils/exceptions.py Co-authored-by: Nabil Freij --- ndcube/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py index f79876f35..2ddac0ba1 100644 --- a/ndcube/utils/exceptions.py +++ b/ndcube/utils/exceptions.py @@ -6,7 +6,7 @@ """ import warnings -__all__ = ["NDCubeDeprecationWarning"] +__all__ = ["NDCubeWarning", "NDCubeDeprecationWarning", "warn_deprecated"] class NDCubeWarning(UserWarning): From b3067b5971112a9b3e6f8fb7849cf038b6a2559e Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:12:07 +0100 Subject: [PATCH 09/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 805c34ba7..fcadbeab0 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -63,7 +63,8 @@ def _shape(self): common_axis_lengths = [cube.data.shape[self._common_axis] for cube in self.data] if len(np.unique(common_axis_lengths)) != 1: common_axis_dimensions = tuple([cube.shape[self._common_axis] - for cube in self.data]) + for cube in self.data]) + dimensions[self._common_axis + 1] = common_axis_dimensions return tuple(dimensions) @property From 5f9d832cb5fe9969b8f696c36f8b27e751ae57f9 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:12:20 +0100 Subject: [PATCH 10/35] Update ndcube/ndcube.py Co-authored-by: Nabil Freij --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 5862390f8..ce0455ade 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -414,7 +414,7 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) - @deprecated(version='3.0.0', reason='Quantity removed. Use `ndcube.NDCube.shape` instead.') + @deprecated(version='3.0.0', reason='Replaced by ndcube.NDCube.shape') @property def dimensions(self): warnings.warn("Quantity removed. Use `ndcube.NDCube.shape` instead.", NDCubeDeprecationWarning) From 616c11183b3575ed822aea9a7c6c13f82fd6ae91 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:12:31 +0100 Subject: [PATCH 11/35] Update ndcube/ndcube.py Co-authored-by: Nabil Freij --- ndcube/ndcube.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index ce0455ade..fb10b20f9 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -417,7 +417,6 @@ def combined_wcs(self): @deprecated(version='3.0.0', reason='Replaced by ndcube.NDCube.shape') @property def dimensions(self): - warnings.warn("Quantity removed. Use `ndcube.NDCube.shape` instead.", NDCubeDeprecationWarning) return u.Quantity(self.data.shape, unit=u.pix) @property From c8676c2b3e4c3f095fac36d9f96967de76c12f00 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Wed, 24 Apr 2024 10:20:15 +0100 Subject: [PATCH 12/35] Implements deprecation warnings on dimensions --- ndcube/ndcollection.py | 6 +++--- ndcube/ndcube.py | 2 +- ndcube/ndcube_sequence.py | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ndcube/ndcollection.py b/ndcube/ndcollection.py index b797f4698..0b37e4e91 100644 --- a/ndcube/ndcollection.py +++ b/ndcube/ndcollection.py @@ -102,7 +102,7 @@ def aligned_dimensions(self): If there are no aligned axes, returns None. """ if self.aligned_axes is not None: - return np.asanyarray(self[self._first_key].dimensions, dtype=object)[ + return np.asanyarray(self[self._first_key].shape, dtype=object)[ np.array(self.aligned_axes[self._first_key]) ] @@ -178,7 +178,7 @@ def _generate_collection_getitems(self, item): # and drop any aligned axes that are sliced out. # First, define empty lists of slice items to be applied to each cube in collection. - collection_items = [[slice(None)] * len(self[key].dimensions) for key in self] + collection_items = [[slice(None)] * len(self[key].shape) for key in self] # Define empty list to hold aligned axes dropped by the slicing. drop_aligned_axes_indices = [] @@ -275,7 +275,7 @@ def update(self, *args): first_old_aligned_axes = self.aligned_axes[self._first_key] if self.aligned_axes is not None else None first_new_aligned_axes = new_aligned_axes[new_keys[0]] if new_aligned_axes is not None else None collection_utils.assert_aligned_axes_compatible( - self[self._first_key].dimensions, new_data[0].dimensions, + self[self._first_key].shape, new_data[0].shape, first_old_aligned_axes, first_new_aligned_axes ) # Update collection diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index f1e622194..242d6765d 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -414,9 +414,9 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) - @deprecated(version='3.0.0', reason='Replaced by ndcube.NDCube.shape') @property def dimensions(self): + warnings.warn("Replaced by ndcube.NDCube.shape", NDCubeDeprecationWarning) return u.Quantity(self.data.shape, unit=u.pix) @property diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index fcadbeab0..018322eae 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -1,12 +1,14 @@ import copy import numbers import textwrap +import warnings import numpy as np import astropy.units as u from ndcube import utils +from ndcube.utils.exceptions import NDCubeDeprecationWarning from ndcube.visualization.descriptor import PlotterDescriptor @@ -46,6 +48,7 @@ def dimensions(self): """ The length of each axis including the sequence axis. """ + warnings.warn("Replaced by ndcube.NDCubeSequence.shape", NDCubeDeprecationWarning) return tuple([d * u.pix for d in self._shape]) @property From 2d66db602acc685bb5509b12f9b54ae9ee1c7a69 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Tue, 23 Apr 2024 15:40:12 +0100 Subject: [PATCH 13/35] Implements new `shape` property on class Depricates `dimensions` property --- examples/slicing_ndcube.py | 18 ++++++------- ndcube/ndcube.py | 6 +++++ ndcube/ndcube_sequence.py | 4 +-- ndcube/tests/test_ndcube.py | 18 ++++++------- ndcube/tests/test_ndcubesequence.py | 40 ++++++++++++++--------------- 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/examples/slicing_ndcube.py b/examples/slicing_ndcube.py index 904559678..bfeb7b62c 100644 --- a/examples/slicing_ndcube.py +++ b/examples/slicing_ndcube.py @@ -54,8 +54,8 @@ example_cube.plot() ############################################################################## -# We can also inspect the dimensions of the cube: -example_cube.dimensions +# We can also inspect the shape of the cube: +example_cube.shape ############################################################################## # We can also inspect the world coordinates for all array elements: @@ -77,14 +77,14 @@ sliced_cube = example_cube[1, :, :] # here we can see we are left with a 2-D cube which is an image at one wavelength. -sliced_cube.dimensions +sliced_cube.shape # We can also index a region of interest of the cube at a particular wavelength. # Again note that we are slicing here based on the ``array`` index rather than cropping by # real world value sliced_cube = example_cube[1, 10:20, 20:40] -sliced_cube.dimensions +sliced_cube.shape # Now we can inspect the sliced cube, and see it's now a smaller region of interest. sliced_cube.plot() @@ -114,9 +114,9 @@ cropped_cube = example_cube.crop(point1, point2) ############################################################################## -# Similar to before, we can inspect the dimensions of the sliced cube: +# Similar to before, we can inspect the dimensions of the sliced cube via the shape property: -cropped_cube.dimensions +cropped_cube.shape ############################################################################## # and we can visualize it: @@ -134,7 +134,7 @@ ############################################################################## # we can inspect the dimensions of the cropped cube: -cropped_cube.dimensions +cropped_cube.shape ############################################################################## # and again visualize it: @@ -151,7 +151,7 @@ ############################################################################## # Check dimensions: -cropped_cube.dimensions +cropped_cube.shape ############################################################################## # Here we can just see how powerful this can be to easily crop over different world coordinates. @@ -162,4 +162,4 @@ point6 = [SkyCoord(200*u.arcsec, 100*u.arcsec, frame=frames.Helioprojective), SpectralCoord(10.6*u.angstrom)] cropped_cube = example_cube.crop(point5, point6) -cropped_cube.dimensions +cropped_cube.shape diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index ee730dff5..16275c59e 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -8,6 +8,7 @@ from collections.abc import Mapping, Iterable import numpy as np +from deprecated import deprecated import astropy.nddata import astropy.units as u @@ -413,10 +414,15 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) + @deprecated(version='3.0.0', reason='Quantity removed. Use `ndcube.NDCube.shape` instead.') @property def dimensions(self): return u.Quantity(self.data.shape, unit=u.pix) + @property + def shape(self): + return self.data.shape + @property def array_axis_physical_types(self): # Docstring in NDCubeABC. diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 1ede34e79..658fefca0 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -50,7 +50,7 @@ def dimensions(self): @property def _dimensions(self): - dimensions = [len(self.data) * u.pix] + list(self.data[0].dimensions) + dimensions = [len(self.data) * u.pix] + list(self.data[0].shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not # be the same. Therefore if the lengths are different, @@ -58,7 +58,7 @@ def _dimensions(self): if self._common_axis is not None: common_axis_lengths = [cube.data.shape[self._common_axis] for cube in self.data] if len(np.unique(common_axis_lengths)) != 1: - common_axis_dimensions = [cube.dimensions[self._common_axis] + common_axis_dimensions = [cube.shape[self._common_axis] for cube in self.data] dimensions[self._common_axis + 1] = u.Quantity( common_axis_dimensions, unit=common_axis_dimensions[0].unit) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index dff3761ec..af6a2ffbc 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -46,7 +46,7 @@ def test_wcs_object(all_ndcubes): indirect=("ndc",)) def test_slicing_ln_lt(ndc, item): sndc = ndc[item] - assert len(sndc.dimensions) == 2 + assert len(sndc.shape) == 2 assert set(sndc.wcs.world_axis_physical_types) == {"custom:pos.helioprojective.lat", "custom:pos.helioprojective.lon"} if sndc.uncertainty is not None: @@ -82,7 +82,7 @@ def test_slicing_ln_lt(ndc, item): indirect=("ndc",)) def test_slicing_wave(ndc, item): sndc = ndc[item] - assert len(sndc.dimensions) == 1 + assert len(sndc.shape) == 1 assert set(sndc.wcs.world_axis_physical_types) == {"em.wl"} if sndc.uncertainty is not None: assert np.allclose(sndc.data, sndc.uncertainty.array) @@ -117,7 +117,7 @@ def test_slicing_wave(ndc, item): indirect=("ndc",)) def test_slicing_split_celestial(ndc, item): sndc = ndc[item] - assert len(sndc.dimensions) == 2 + assert len(sndc.shape) == 2 if sndc.uncertainty is not None: assert np.allclose(sndc.data, sndc.uncertainty.array) if sndc.mask is not None: @@ -815,7 +815,7 @@ def test_rebin(ndcube_3d_l_ln_lt_ectime): expected_time.format = "fits" # Confirm output is as expected. - assert (output.dimensions.value == np.array([1, 2, 8])).all() + assert (output.shape.value == np.array([1, 2, 8])).all() assert (output.data == expected_data).all() assert (output.mask == expected_mask).all() assert output.uncertainty == expected_uncertainty @@ -1184,11 +1184,11 @@ def test_to_dask(ndcube_2d_dask): def test_squeeze(ndcube_4d_ln_l_t_lt): - assert np.array_equal(ndcube_4d_ln_l_t_lt.squeeze().dimensions, ndcube_4d_ln_l_t_lt.dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze().dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze(2).dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze([1,2]).dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0:1,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze(2).dimensions) + assert np.array_equal(ndcube_4d_ln_l_t_lt.squeeze().shape, ndcube_4d_ln_l_t_lt.shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].shape, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze().shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].shape, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze(2).shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0,0,:].shape, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze([1,2]).shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0:1,0,:].shape, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze(2).shape) def test_squeeze_error(ndcube_4d_ln_l_t_lt): diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index b952646cd..1929b471f 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -35,12 +35,12 @@ def derive_sliced_cube_dims(orig_cube_dims, tuple_item): def test_slice_sequence_axis(ndc, item): # Calculate expected dimensions of cubes with sequence after slicing. tuple_item = item if isinstance(item, tuple) else (item,) - expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].dimensions, tuple_item) + expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].shape, tuple_item) # Assert output is as expected. sliced_sequence = ndc[item] assert isinstance(sliced_sequence, NDCubeSequence) - assert int(sliced_sequence.dimensions[0].value) == tuple_item[0].stop - tuple_item[0].start - assert (sliced_sequence[0].dimensions == expected_cube0_dims).all() + assert int(sliced_sequence.shape[0].value) == tuple_item[0].stop - tuple_item[0].start + assert (sliced_sequence[0].shape == expected_cube0_dims).all() @pytest.mark.parametrize("ndc, item", @@ -54,9 +54,9 @@ def test_slice_sequence_axis(ndc, item): def test_extract_ndcube(ndc, item): cube = ndc[item] tuple_item = item if isinstance(item, tuple) else (item,) - expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].dimensions, tuple_item) + expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].shape, tuple_item) assert isinstance(cube, NDCube) - assert (cube.dimensions == expected_cube_dims).all() + assert (cube.shape == expected_cube_dims).all() @pytest.mark.parametrize("ndc, item, expected_common_axis", @@ -72,7 +72,7 @@ def test_slice_common_axis(ndc, item, expected_common_axis): assert sliced_sequence._common_axis == expected_common_axis -@pytest.mark.parametrize("ndc, item, expected_dimensions", +@pytest.mark.parametrize("ndc, item, expected_shape", ( ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7], (3 * u.pix, 2 * u.pix, @@ -96,14 +96,14 @@ def test_slice_common_axis(ndc, item, expected_common_axis): 2 * u.pix)) ), indirect=("ndc",)) -def test_index_as_cube(ndc, item, expected_dimensions): +def test_index_as_cube(ndc, item, expected_shape): sliced_sequence = ndc.index_as_cube[item] - sliced_dims = sliced_sequence.dimensions - for dim, expected_dim in zip(sliced_dims, expected_dimensions): + sliced_dims = sliced_sequence.shape + for dim, expected_dim in zip(sliced_dims, expected_shape): (dim == expected_dim).all() -@pytest.mark.parametrize("ndc, axis, expected_dimensions", +@pytest.mark.parametrize("ndc, axis, expected_shape", ( ("ndcubesequence_4c_ln_lt_l", 0, (8 * u.pix, 3 * u.pix, @@ -113,27 +113,27 @@ def test_index_as_cube(ndc, item, expected_dimensions): 4 * u.pix)) ), indirect=("ndc",)) -def test_explode_along_axis_common_axis_None(ndc, axis, expected_dimensions): +def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape): exploded_sequence = ndc.explode_along_axis(axis) - assert exploded_sequence.dimensions == expected_dimensions + assert exploded_sequence.shape == expected_shape assert exploded_sequence._common_axis is None @pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) def test_explode_along_axis_common_axis_same(ndc): exploded_sequence = ndc.explode_along_axis(2) - assert exploded_sequence.dimensions == (16*u.pix, 2*u.pix, 3*u.pix) + assert exploded_sequence.shape == (16*u.pix, 2*u.pix, 3*u.pix) assert exploded_sequence._common_axis == ndc._common_axis @pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) def test_explode_along_axis_common_axis_changed(ndc): exploded_sequence = ndc.explode_along_axis(0) - assert exploded_sequence.dimensions == (8*u.pix, 3*u.pix, 4*u.pix) + assert exploded_sequence.shape == (8*u.pix, 3*u.pix, 4*u.pix) assert exploded_sequence._common_axis == ndc._common_axis - 1 -@pytest.mark.parametrize("ndc, expected_dimensions", +@pytest.mark.parametrize("ndc, expected_shape", ( ("ndcubesequence_4c_ln_lt_l_cax1", (4 * u.pix, 2. * u.pix, @@ -141,18 +141,18 @@ def test_explode_along_axis_common_axis_changed(ndc): 4. * u.pix)), ), indirect=("ndc",)) -def test_dimensions(ndc, expected_dimensions): +def test_shape(ndc, expected_shape): unit_tester = unittest.TestCase() - unit_tester.assertEqual(ndc.dimensions, expected_dimensions) + unit_tester.assertEqual(ndc.shape, expected_shape) -@pytest.mark.parametrize("ndc, expected_dimensions", +@pytest.mark.parametrize("ndc, expected_shape", ( ("ndcubesequence_4c_ln_lt_l_cax1", [2., 12, 4] * u.pix), ), indirect=("ndc",)) -def test_cube_like_dimensions(ndc, expected_dimensions): - assert (ndc.cube_like_dimensions == expected_dimensions).all() +def test_cube_like_shape(ndc, expected_shape): + assert (ndc.cube_like_dimensions == expected_shape).all() @pytest.mark.parametrize("ndc", (("ndcubesequence_4c_ln_lt_l",)), indirect=("ndc",)) From 3e3aff333c7474287a3443364419936d93f23118 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Tue, 23 Apr 2024 15:58:09 +0100 Subject: [PATCH 14/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 658fefca0..e4c144e3c 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -50,7 +50,7 @@ def dimensions(self): @property def _dimensions(self): - dimensions = [len(self.data) * u.pix] + list(self.data[0].shape) + dimensions = [len(self.data)] + list(self.data[0].shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not # be the same. Therefore if the lengths are different, From 75cc8c6113c28ce2b12c4d486d0a4c50456050d0 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Tue, 23 Apr 2024 15:58:32 +0100 Subject: [PATCH 15/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index e4c144e3c..a22d06e7e 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -49,7 +49,7 @@ def dimensions(self): return self._dimensions @property - def _dimensions(self): + def _shape(self): dimensions = [len(self.data)] + list(self.data[0].shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not From 2575cd37875f683cdf330ca1bb5c2ebc277e5197 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Tue, 23 Apr 2024 15:59:20 +0100 Subject: [PATCH 16/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index a22d06e7e..32ec5ab28 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -58,10 +58,8 @@ def _shape(self): if self._common_axis is not None: common_axis_lengths = [cube.data.shape[self._common_axis] for cube in self.data] if len(np.unique(common_axis_lengths)) != 1: - common_axis_dimensions = [cube.shape[self._common_axis] - for cube in self.data] - dimensions[self._common_axis + 1] = u.Quantity( - common_axis_dimensions, unit=common_axis_dimensions[0].unit) + common_axis_dimensions = tuple([cube.shape[self._common_axis] + for cube in self.data]) return tuple(dimensions) @property From 1c3301012863ce75634319293da80a2e07794cda Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Tue, 23 Apr 2024 16:06:17 +0100 Subject: [PATCH 17/35] Adds shape property to NDCubeSequence --- ndcube/ndcube_sequence.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 32ec5ab28..805c34ba7 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -46,7 +46,11 @@ def dimensions(self): """ The length of each axis including the sequence axis. """ - return self._dimensions + return tuple([d * u.pix for d in self._shape]) + + @property + def shape(self): + return self._shape @property def _shape(self): From 996f157cd106003de573181684afb1e5a6f14fa9 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Tue, 23 Apr 2024 16:47:30 +0100 Subject: [PATCH 18/35] Adds exceptions package --- ndcube/ndcube.py | 3 ++- ndcube/utils/exceptions.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 ndcube/utils/exceptions.py diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 16275c59e..da2abf1bd 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -8,7 +8,6 @@ from collections.abc import Mapping, Iterable import numpy as np -from deprecated import deprecated import astropy.nddata import astropy.units as u @@ -29,6 +28,7 @@ from ndcube.global_coords import GlobalCoords, GlobalCoordsABC from ndcube.mixins import NDCubeSlicingMixin from ndcube.ndcube_sequence import NDCubeSequence +from ndcube.utils.exceptions import NDCubeDeprecationWarning from ndcube.utils.wcs_high_level_conversion import values_to_high_level_objects from ndcube.visualization import PlotterDescriptor from ndcube.wcs.wrappers import CompoundLowLevelWCS, ResampledLowLevelWCS @@ -417,6 +417,7 @@ def combined_wcs(self): @deprecated(version='3.0.0', reason='Quantity removed. Use `ndcube.NDCube.shape` instead.') @property def dimensions(self): + warnings.warn("Quantity removed. Use `ndcube.NDCube.shape` instead.", NDCubeDeprecationWarning) return u.Quantity(self.data.shape, unit=u.pix) @property diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py new file mode 100644 index 000000000..f70cc3e1b --- /dev/null +++ b/ndcube/utils/exceptions.py @@ -0,0 +1,35 @@ +""" +This module provides errors/exceptions and warnings of general use for NDCube. + +Exceptions that are specific to a given package should **not** be here, +but rather in the particular package. +""" +import warnings + +__all__ = ["NDCubeDeprecationWarning"] + + +class NDCubeWarning(UserWarning): + """ + A general NDCube warning + """ + +class NDCubeDeprecationWarning(FutureWarning, NDCubeWarning): + """ + A warning class to indicate a deprecated feature. + """ + +def warn_deprecated(msg, stacklevel=1): + """ + Raise a `NDCubeDeprecationWarning`. + + Parameters + ---------- + msg : str + Warning message. + stacklevel : int + This is interpreted relative to the call to this function, + e.g. ``stacklevel=1`` (the default) sets the stack level in the + code that calls this function. + """ + warnings.warn(msg, NDCubeDeprecationWarning , stacklevel + 1) From b941d22e1b1ff19bb9e9caaa942acd54f17c5042 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:11:39 +0100 Subject: [PATCH 19/35] Update ndcube/utils/exceptions.py Co-authored-by: Nabil Freij --- ndcube/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py index f70cc3e1b..f79876f35 100644 --- a/ndcube/utils/exceptions.py +++ b/ndcube/utils/exceptions.py @@ -1,7 +1,7 @@ """ This module provides errors/exceptions and warnings of general use for NDCube. -Exceptions that are specific to a given package should **not** be here, +Exceptions that are specific to a given subpackage should **not** be here, but rather in the particular package. """ import warnings From ca9c8b2e69dc6e140101b43aa80471f3ac56468e Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:11:53 +0100 Subject: [PATCH 20/35] Update ndcube/utils/exceptions.py Co-authored-by: Nabil Freij --- ndcube/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py index f79876f35..2ddac0ba1 100644 --- a/ndcube/utils/exceptions.py +++ b/ndcube/utils/exceptions.py @@ -6,7 +6,7 @@ """ import warnings -__all__ = ["NDCubeDeprecationWarning"] +__all__ = ["NDCubeWarning", "NDCubeDeprecationWarning", "warn_deprecated"] class NDCubeWarning(UserWarning): From 2c5542f77b6fd1b9b111777c4ebfbb6683236b16 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:12:07 +0100 Subject: [PATCH 21/35] Update ndcube/ndcube_sequence.py Co-authored-by: DanRyanIrish --- ndcube/ndcube_sequence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 805c34ba7..fcadbeab0 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -63,7 +63,8 @@ def _shape(self): common_axis_lengths = [cube.data.shape[self._common_axis] for cube in self.data] if len(np.unique(common_axis_lengths)) != 1: common_axis_dimensions = tuple([cube.shape[self._common_axis] - for cube in self.data]) + for cube in self.data]) + dimensions[self._common_axis + 1] = common_axis_dimensions return tuple(dimensions) @property From e64670a4e80599a7461698d2fe3524f1bd8f3080 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:12:20 +0100 Subject: [PATCH 22/35] Update ndcube/ndcube.py Co-authored-by: Nabil Freij --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index da2abf1bd..b08ffee0f 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -414,7 +414,7 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) - @deprecated(version='3.0.0', reason='Quantity removed. Use `ndcube.NDCube.shape` instead.') + @deprecated(version='3.0.0', reason='Replaced by ndcube.NDCube.shape') @property def dimensions(self): warnings.warn("Quantity removed. Use `ndcube.NDCube.shape` instead.", NDCubeDeprecationWarning) From 59d2e306613a98b61bf4ff5282f67a8b272201c6 Mon Sep 17 00:00:00 2001 From: Samuel Bennett Date: Wed, 24 Apr 2024 10:12:31 +0100 Subject: [PATCH 23/35] Update ndcube/ndcube.py Co-authored-by: Nabil Freij --- ndcube/ndcube.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index b08ffee0f..f1e622194 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -417,7 +417,6 @@ def combined_wcs(self): @deprecated(version='3.0.0', reason='Replaced by ndcube.NDCube.shape') @property def dimensions(self): - warnings.warn("Quantity removed. Use `ndcube.NDCube.shape` instead.", NDCubeDeprecationWarning) return u.Quantity(self.data.shape, unit=u.pix) @property From e186d53fc247751ae6a531430a2ca4e2496f238a Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Wed, 24 Apr 2024 10:20:15 +0100 Subject: [PATCH 24/35] Implements deprecation warnings on dimensions --- ndcube/ndcollection.py | 6 +++--- ndcube/ndcube.py | 2 +- ndcube/ndcube_sequence.py | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ndcube/ndcollection.py b/ndcube/ndcollection.py index b797f4698..0b37e4e91 100644 --- a/ndcube/ndcollection.py +++ b/ndcube/ndcollection.py @@ -102,7 +102,7 @@ def aligned_dimensions(self): If there are no aligned axes, returns None. """ if self.aligned_axes is not None: - return np.asanyarray(self[self._first_key].dimensions, dtype=object)[ + return np.asanyarray(self[self._first_key].shape, dtype=object)[ np.array(self.aligned_axes[self._first_key]) ] @@ -178,7 +178,7 @@ def _generate_collection_getitems(self, item): # and drop any aligned axes that are sliced out. # First, define empty lists of slice items to be applied to each cube in collection. - collection_items = [[slice(None)] * len(self[key].dimensions) for key in self] + collection_items = [[slice(None)] * len(self[key].shape) for key in self] # Define empty list to hold aligned axes dropped by the slicing. drop_aligned_axes_indices = [] @@ -275,7 +275,7 @@ def update(self, *args): first_old_aligned_axes = self.aligned_axes[self._first_key] if self.aligned_axes is not None else None first_new_aligned_axes = new_aligned_axes[new_keys[0]] if new_aligned_axes is not None else None collection_utils.assert_aligned_axes_compatible( - self[self._first_key].dimensions, new_data[0].dimensions, + self[self._first_key].shape, new_data[0].shape, first_old_aligned_axes, first_new_aligned_axes ) # Update collection diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index f1e622194..242d6765d 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -414,9 +414,9 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) - @deprecated(version='3.0.0', reason='Replaced by ndcube.NDCube.shape') @property def dimensions(self): + warnings.warn("Replaced by ndcube.NDCube.shape", NDCubeDeprecationWarning) return u.Quantity(self.data.shape, unit=u.pix) @property diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index fcadbeab0..018322eae 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -1,12 +1,14 @@ import copy import numbers import textwrap +import warnings import numpy as np import astropy.units as u from ndcube import utils +from ndcube.utils.exceptions import NDCubeDeprecationWarning from ndcube.visualization.descriptor import PlotterDescriptor @@ -46,6 +48,7 @@ def dimensions(self): """ The length of each axis including the sequence axis. """ + warnings.warn("Replaced by ndcube.NDCubeSequence.shape", NDCubeDeprecationWarning) return tuple([d * u.pix for d in self._shape]) @property From 69d2cf1be8202708c425352095227d2cc03970ef Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 11:30:28 +0200 Subject: [PATCH 25/35] add changelog --- changelog/684.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/684.feature.rst diff --git a/changelog/684.feature.rst b/changelog/684.feature.rst new file mode 100644 index 000000000..685ff5d2b --- /dev/null +++ b/changelog/684.feature.rst @@ -0,0 +1 @@ +Added `ndcube.NDCube.shape` as a replacement for "dimensions". From 22bd1b32e3e356eead934ca19bbf6549048ab50b Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 11:32:07 +0200 Subject: [PATCH 26/35] add changelog v2 --- changelog/684.breaking.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/684.breaking.rst diff --git a/changelog/684.breaking.rst b/changelog/684.breaking.rst new file mode 100644 index 000000000..f1dabb66a --- /dev/null +++ b/changelog/684.breaking.rst @@ -0,0 +1 @@ +"dimensions" property has been depcreated and replaced by "shape" From 642dd89df734ceb16c60d0d39da0dc93e57d53c7 Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 11:32:59 +0200 Subject: [PATCH 27/35] Apply suggestions from code review --- ndcube/ndcube.py | 4 ++-- ndcube/ndcube_sequence.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 242d6765d..ad9c3cb6b 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -28,7 +28,7 @@ from ndcube.global_coords import GlobalCoords, GlobalCoordsABC from ndcube.mixins import NDCubeSlicingMixin from ndcube.ndcube_sequence import NDCubeSequence -from ndcube.utils.exceptions import NDCubeDeprecationWarning +from ndcube.utils.exceptions import warn_deprecated from ndcube.utils.wcs_high_level_conversion import values_to_high_level_objects from ndcube.visualization import PlotterDescriptor from ndcube.wcs.wrappers import CompoundLowLevelWCS, ResampledLowLevelWCS @@ -416,7 +416,7 @@ def combined_wcs(self): @property def dimensions(self): - warnings.warn("Replaced by ndcube.NDCube.shape", NDCubeDeprecationWarning) + warn_deprecated("Replaced by ndcube.NDCube.shape") return u.Quantity(self.data.shape, unit=u.pix) @property diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 018322eae..90ea9dbb4 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -1,14 +1,13 @@ import copy import numbers import textwrap -import warnings import numpy as np import astropy.units as u from ndcube import utils -from ndcube.utils.exceptions import NDCubeDeprecationWarning +from ndcube.utils.exceptions import warn_deprecated from ndcube.visualization.descriptor import PlotterDescriptor @@ -48,7 +47,7 @@ def dimensions(self): """ The length of each axis including the sequence axis. """ - warnings.warn("Replaced by ndcube.NDCubeSequence.shape", NDCubeDeprecationWarning) + warn_deprecated("Replaced by ndcube.NDCubeSequence.shape") return tuple([d * u.pix for d in self._shape]) @property From f7ee5eed39aac19781a0c37a72265360198dfb88 Mon Sep 17 00:00:00 2001 From: CyclingNinja Date: Wed, 24 Apr 2024 10:50:56 +0100 Subject: [PATCH 28/35] Applies dimension -> shape refactor --- examples/creating_ndcube_from_fitsfile.py | 2 +- ndcube/extra_coords/extra_coords.py | 4 ++-- ndcube/extra_coords/tests/test_extra_coords.py | 2 +- ndcube/ndcube.py | 6 +++--- ndcube/ndcube_sequence.py | 8 ++++---- ndcube/tests/helpers.py | 3 +-- ndcube/tests/test_ndcubesequence.py | 10 +++++----- ndcube/utils/collection.py | 16 ++++++++-------- ndcube/visualization/mpl_plotter.py | 4 ++-- ndcube/visualization/mpl_sequence_plotter.py | 4 ++-- 10 files changed, 29 insertions(+), 30 deletions(-) diff --git a/examples/creating_ndcube_from_fitsfile.py b/examples/creating_ndcube_from_fitsfile.py index b3a06435f..f2c050759 100644 --- a/examples/creating_ndcube_from_fitsfile.py +++ b/examples/creating_ndcube_from_fitsfile.py @@ -46,7 +46,7 @@ ########################################################################## # and we can also inspect the dimensions. -print(example_ndcube.dimensions) +print(example_ndcube.shape) ########################################################################## # We can also quickly visualize the data using the :meth:`~ndcube.NDCube.plot` method. diff --git a/ndcube/extra_coords/extra_coords.py b/ndcube/extra_coords/extra_coords.py index 7c88af705..87f4de9b7 100644 --- a/ndcube/extra_coords/extra_coords.py +++ b/ndcube/extra_coords/extra_coords.py @@ -261,7 +261,7 @@ def mapping(self): # The mapping is from the array index (position in the list) to the # pixel dimensions (numbers in the list) lts = [list([lt[0]] if isinstance(lt[0], Integral) else lt[0]) for lt in self._lookup_tables] - converter = partial(convert_between_array_and_pixel_axes, naxes=len(self._ndcube.dimensions)) + converter = partial(convert_between_array_and_pixel_axes, naxes=len(self._ndcube.shape)) pixel_indicies = [list(converter(np.array(ids))) for ids in lts] return tuple(reduce(list.__add__, pixel_indicies)) @@ -527,7 +527,7 @@ def cube_wcs(self): @property def _cube_array_axes_without_extra_coords(self): """Return the array axes not associated with any extra coord.""" - return set(range(len(self._ndcube.dimensions))) - set(self.mapping) + return set(range(len(self._ndcube.shape))) - set(self.mapping) def __str__(self): classname = self.__class__.__name__ diff --git a/ndcube/extra_coords/tests/test_extra_coords.py b/ndcube/extra_coords/tests/test_extra_coords.py index c8b9bd706..88dcd4d1c 100644 --- a/ndcube/extra_coords/tests/test_extra_coords.py +++ b/ndcube/extra_coords/tests/test_extra_coords.py @@ -481,7 +481,7 @@ def test_resample(time_lut, wave_lut, skycoord_1d_lut, ndcube_4d_ln_lt_l_t): ec.add(("lon", "lat"), 1, skycoord_1d_lut) # Add coord that will not be sliced a = 2 - energy_lut = range(int(cube.dimensions[a].value)) * u.keV + energy_lut = range(int(cube.shape[a].value)) * u.keV ec.add("hello", a, energy_lut) # Call resample. diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 242d6765d..5fceacb21 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -636,7 +636,7 @@ def __str__(self): return textwrap.dedent(f"""\ NDCube ------ - Dimensions: {self.dimensions} + Dimensions: {self.shape} Physical Types of Axes: {self.array_axis_physical_types} Unit: {self.unit} Data Type: {self.data.dtype}""") @@ -659,7 +659,7 @@ def explode_along_axis(self, axis): """ # If axis is -ve then calculate the axis from the length of the dimensions of one cube if axis < 0: - axis = len(self.dimensions) + axis + axis = len(self.shape) + axis # To store the resultant cube result_cubes = [] # All slices are initially initialised as slice(None, None, None) @@ -1123,7 +1123,7 @@ def my_propagate(uncertainty, data, mask, **kwargs): return self # Ensure bin_size has right number of entries and each entry is an # integer fraction of the array shape in each dimension. - data_shape = self.dimensions.value.astype(int) + data_shape = self.shape.astype(int) naxes = len(data_shape) if len(bin_shape) != naxes: raise ValueError("bin_shape must have an entry for each array axis.") diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 018322eae..669d7b3ef 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -214,7 +214,7 @@ def explode_along_axis(self, axis): """ # If axis is -ve then calculate the axis from the length of the dimensions of one cube. if axis < 0: - axis = len(self.dimensions[1::]) + axis + axis = len(self.shape[1::]) + axis # To store the resultant cube result_cubes = [] # All slices are initially initialised as slice(None, None, None) @@ -348,7 +348,7 @@ def _get_sequence_crop_item(self, *points, wcses=None, crop_by_values=False, uni Only used if crop_by_values is True. """ n_cubes = len(self.data) - cube_ndim = len(self.dimensions[1:]) + cube_ndim = len(self.shape[1:]) starts = np.zeros((n_cubes, cube_ndim), dtype=int) stops = np.zeros((n_cubes, cube_ndim), dtype=int) if wcses is None: @@ -378,7 +378,7 @@ def __str__(self): return (textwrap.dedent(f"""\ NDCubeSequence -------------- - Dimensions: {self.dimensions} + Dimensions: {self.shape} Physical Types of Axes: {self.array_axis_physical_types} Common Cube Axis: {self._common_axis}""")) @@ -473,7 +473,7 @@ def __init__(self, seq): def __getitem__(self, item): common_axis = self.seq._common_axis - common_axis_lengths = [int(cube.dimensions[common_axis].value) for cube in self.seq.data] + common_axis_lengths = [int(cube.shape[common_axis].value) for cube in self.seq.data] n_cube_dims = len(self.seq.cube_like_dimensions) n_uncommon_cube_dims = n_cube_dims - 1 # If item is iint or slice, turn into a tuple, filling in items diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index e0cd7ec21..37008bf9b 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -103,8 +103,7 @@ def assert_cubes_equal(test_input, expected_cube): assert_wcs_are_equal(test_input.wcs, expected_cube.wcs) if test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape - assert all(test_input.dimensions.value == expected_cube.dimensions.value) - assert test_input.dimensions.unit == expected_cube.dimensions.unit + assert test_input.shape == expected_cube.shape if type(test_input.extra_coords) is not type(expected_cube.extra_coords): raise AssertionError("NDCube extra_coords not of same type: {0} != {1}".format( type(test_input.extra_coords), type(expected_cube.extra_coords))) diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index 285892950..ab7b56967 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -35,12 +35,12 @@ def derive_sliced_cube_dims(orig_cube_dims, tuple_item): def test_slice_sequence_axis(ndc, item): # Calculate expected dimensions of cubes with sequence after slicing. tuple_item = item if isinstance(item, tuple) else (item,) - expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].dimensions, tuple_item) + expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].shape, tuple_item) # Assert output is as expected. sliced_sequence = ndc[item] assert isinstance(sliced_sequence, NDCubeSequence) - assert int(sliced_sequence.dimensions[0]) == tuple_item[0].stop - tuple_item[0].start - assert (sliced_sequence[0].dimensions == expected_cube0_dims).all() + assert int(sliced_sequence.shape[0]) == tuple_item[0].stop - tuple_item[0].start + assert (sliced_sequence[0].shape == expected_cube0_dims).all() @pytest.mark.parametrize("ndc, item", @@ -56,7 +56,7 @@ def test_extract_ndcube(ndc, item): tuple_item = item if isinstance(item, tuple) else (item,) expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].shape, tuple_item) assert isinstance(cube, NDCube) - assert (cube.dimensions == expected_cube_dims).all() + assert (cube.shape == expected_cube_dims).all() @pytest.mark.parametrize("ndc, item, expected_common_axis", @@ -143,7 +143,7 @@ def test_explode_along_axis_common_axis_changed(ndc): indirect=("ndc",)) def test_dimensions(ndc, expected_dimensions): unit_tester = unittest.TestCase() - unit_tester.assertEqual(ndc.dimensions, expected_dimensions) + unit_tester.assertEqual(ndc.shape, expected_dimensions) @pytest.mark.parametrize("ndc, expected_dimensions", diff --git a/ndcube/utils/collection.py b/ndcube/utils/collection.py index 895028046..fb1d2e91d 100644 --- a/ndcube/utils/collection.py +++ b/ndcube/utils/collection.py @@ -11,8 +11,8 @@ def _sanitize_aligned_axes(keys, data, aligned_axes): # If aligned_axes set to "all", assume all axes are aligned in order. elif isinstance(aligned_axes, str) and aligned_axes.lower() == "all": # Check all cubes are of same shape - cube0_dims = data[0].dimensions - cubes_same_shape = all([all([d.dimensions[i] == dim for i, dim in enumerate(cube0_dims)]) + cube0_dims = data[0].shape + cubes_same_shape = all([all([d.shape[i] == dim for i, dim in enumerate(cube0_dims)]) for d in data]) if cubes_same_shape is not True: raise ValueError( @@ -39,10 +39,10 @@ def _sanitize_user_aligned_axes(data, aligned_axes): """ aligned_axes_error_message = ("aligned_axes must contain ints or " "a tuple of ints for each element in data.") - if isinstance(data[0].dimensions, tuple): - cube0_dims = np.array(data[0].dimensions, dtype=object)[np.array(aligned_axes[0])] + if isinstance(data[0].shape, tuple): + cube0_dims = np.array(data[0].shape, dtype=object)[np.array(aligned_axes[0])] else: - cube0_dims = data[0].dimensions[np.array(aligned_axes[0])] + cube0_dims = data[0].shape[np.array(aligned_axes[0])] # If user entered a single int or string, convert to length 1 tuple of int. if isinstance(aligned_axes, numbers.Integral): aligned_axes = (aligned_axes,) @@ -76,7 +76,7 @@ def _sanitize_user_aligned_axes(data, aligned_axes): for i in range(n_cubes): # Check each cube has at least as many dimensions as there are aligned axes # and that all cubes have enough dimensions to accommodate aligned axes. - n_cube_dims = len(data[i].dimensions) + n_cube_dims = len(data[i].shape) max_aligned_axis = max(aligned_axes[i]) if n_cube_dims < max([max_aligned_axis, n_aligned_axes]): raise ValueError( @@ -90,7 +90,7 @@ def _sanitize_user_aligned_axes(data, aligned_axes): cube_lengths_equal = [False] * n_aligned_axes for j, axis in enumerate(aligned_axes[i]): subtuple_types[j] = isinstance(axis, numbers.Integral) - cube_lengths_equal[j] = data[i].dimensions[axis] == cube0_dims[j] + cube_lengths_equal[j] = data[i].shape[axis] == cube0_dims[j] subtuples_are_ints[i] = all(subtuple_types) aligned_axes_same_lengths[i] = all(cube_lengths_equal) if not all(subtuples_are_ints): @@ -101,7 +101,7 @@ def _sanitize_user_aligned_axes(data, aligned_axes): raise ValueError(aligned_axes_error_message) # Ensure all aligned axes are of same length. - check_dimensions = set([len(set([cube.dimensions[cube_aligned_axes[j]] + check_dimensions = set([len(set([cube.shape[cube_aligned_axes[j]] for cube, cube_aligned_axes in zip(data, aligned_axes)])) for j in range(n_aligned_axes)]) if check_dimensions != {1}: diff --git a/ndcube/visualization/mpl_plotter.py b/ndcube/visualization/mpl_plotter.py index c44847781..c015cb1af 100644 --- a/ndcube/visualization/mpl_plotter.py +++ b/ndcube/visualization/mpl_plotter.py @@ -73,7 +73,7 @@ def plot(self, axes=None, plot_axes=None, axes_coordinates=None, # Check kwargs are in consistent formats and set default values if not done so by user. plot_axes, axes_coordinates, axes_units = utils.prep_plot_kwargs( - len(self._ndcube.dimensions), plot_wcs, plot_axes, axes_coordinates, axes_units) + len(self._ndcube.shape), plot_wcs, plot_axes, axes_coordinates, axes_units) with warnings.catch_warnings(): warnings.simplefilter('ignore', AstropyUserWarning) @@ -230,7 +230,7 @@ def _as_mpl_axes(self): axes. See https://wcsaxes.readthedocs.io for more information. """ kwargs = {'wcs': self._ndcube.wcs} - n_dim = len(self._ndcube.dimensions) + n_dim = len(self._ndcube.shape) if n_dim > 2: kwargs['slices'] = ['x', 'y'] + [None] * (n_dim - 2) return WCSAxes, kwargs diff --git a/ndcube/visualization/mpl_sequence_plotter.py b/ndcube/visualization/mpl_sequence_plotter.py index ef907b1d2..5fff46939 100644 --- a/ndcube/visualization/mpl_sequence_plotter.py +++ b/ndcube/visualization/mpl_sequence_plotter.py @@ -29,7 +29,7 @@ def plot(self, sequence_axis_coords=None, sequence_axis_unit=None, **kwargs): sequence_axis_unit: `str` or `astropy.units.Unit`, optional The unit in which to display the sequence_axis_coords. """ - sequence_dims = self._ndcube.dimensions + sequence_dims = self._ndcube.shape if len(sequence_dims) == 2: raise NotImplementedError("Visualizing sequences of 1-D cubes not currently supported.") else: @@ -95,7 +95,7 @@ def __init__(self, sequence, sequence_axis_coords=None, sequence_axis_unit=None, axes_units = kwargs.pop("axes_units", None) self._data_unit = kwargs.pop("data_unit", None) init_idx = 0 - n_cube_dims = len(self._cubes[init_idx].dimensions) + n_cube_dims = len(self._cubes[init_idx].shape) init_wcs = self._cubes[init_idx].wcs self._plot_axes, self._axes_coordinates, self._axes_units = prep_plot_kwargs( n_cube_dims, init_wcs, plot_axes, axes_coordinates, axes_units) From ba18115fc71e01094bb60d91561d65d1a8c9063b Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 14:04:04 +0200 Subject: [PATCH 29/35] First set of changes --- docs/explaining_ndcube/coordinates.rst | 4 +- docs/explaining_ndcube/data_classes.rst | 41 ++++++----- docs/explaining_ndcube/slicing.rst | 39 +++++----- .../extra_coords/tests/test_extra_coords.py | 16 ++--- ndcube/mixins/ndslicing.py | 2 +- ndcube/ndcube.py | 12 ++-- ndcube/ndcube_sequence.py | 26 +++++-- ndcube/tests/test_ndcollection.py | 6 +- ndcube/tests/test_ndcube.py | 10 +-- ndcube/tests/test_ndcubesequence.py | 72 +++++++------------ 10 files changed, 113 insertions(+), 115 deletions(-) diff --git a/docs/explaining_ndcube/coordinates.rst b/docs/explaining_ndcube/coordinates.rst index f048fb5e4..dd5a7336c 100644 --- a/docs/explaining_ndcube/coordinates.rst +++ b/docs/explaining_ndcube/coordinates.rst @@ -82,8 +82,8 @@ For example: .. code-block:: python >>> celestial = my_cube.axis_world_coords('lon')[0] # Must extract object from returned tuple with [0] - >>> my_cube.dimensions - + >>> my_cube.shape + (4, 4, 5) >>> celestial.shape (4, 4) >>> celestial diff --git a/docs/explaining_ndcube/data_classes.rst b/docs/explaining_ndcube/data_classes.rst index a78aeb301..865ebb9ad 100644 --- a/docs/explaining_ndcube/data_classes.rst +++ b/docs/explaining_ndcube/data_classes.rst @@ -110,21 +110,21 @@ To instantiate a more complex `~ndcube.NDCube` with metadata, a data unit, uncer Attaching coordinates in addition to those described by ``.wcs`` via `~ndcube.ExtraCoords` and `~ndcube.GlobalCoords` is discussed in the :ref:`extra_coords` and :ref:`global_coords` sections. -Dimensions and Physical Types ------------------------------ +Shape and physical types +------------------------ -`~ndcube.NDCube` has useful properties for inspecting its axes: `~ndcube.NDCube.dimensions` and `~ndcube.NDCube.array_axis_physical_types`. +`~ndcube.NDCube` has useful properties for inspecting its axes: `~ndcube.NDCube.shape` and `~ndcube.NDCube.array_axis_physical_types`. .. code-block:: python - >>> my_cube.dimensions - + >>> my_cube.shape + (4, 4, 5) >>> my_cube.array_axis_physical_types [('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)] -`~ndcube.NDCube.dimensions` returns a `~astropy.units.Quantity` in pixel units giving the length of each dimension in the `~ndcube.NDCube`. +`~ndcube.NDCube.shape` returns a tuple giving the length of each dimension in the `~ndcube.NDCube`. `~ndcube.NDCube.array_axis_physical_types` returns tuples of strings denoting the types of physical properties represented by each array axis. The tuples are arranged in array axis order, while the physical types inside each tuple are returned in world order. As more than one physical type can be associated with an array axis, the length of each tuple can be greater than 1. @@ -151,8 +151,8 @@ This returns an `~ndcube.NDCubeSequence` where the sequence axis acts as the wav .. code-block:: python - >>> exploded.dimensions - (, , ) + >>> exploded.shape + (5, 4, 4) >>> exploded.array_axis_physical_types [('meta.obs.sequence',), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), @@ -268,23 +268,22 @@ Let's reinstantiate the `~ndcube.NDCubeSequence` with the common axis as the fir .. _dimensions: -Dimensions and physical types ------------------------------ +Shape and physical types +------------------------ -Analogous to `ndcube.NDCube.dimensions`, there is also a `ndcube.NDCubeSequence.dimensions` property for easily inspecting the shape of an `~ndcube.NDCubeSequence` instance. +Analogous to `ndcube.NDCube.shape`, there is also a `ndcube.NDCubeSequence.shape` property for easily inspecting the shape of an `~ndcube.NDCubeSequence` instance. .. code-block:: python - >>> my_sequence.dimensions - (, , , ) + >>> my_sequence.shape + (4, 4, 4, 5) -Slightly differently to `ndcube.NDCube.dimensions`, `ndcube.NDCubeSequence.dimensions` returns a tuple of `astropy.units.Quantity` instances in pixel units, giving the length of each axis. -To see the dimensionality of the sequence in the cube-like paradigm, i.e. taking into account the common axis, use the `ndcube.NDCubeSequence.cube_like_dimensions` property. +To see the dimensionality of the sequence in the cube-like paradigm, i.e. taking into account the common axis, use the `ndcube.NDCubeSequence.cube_like_shape` property. .. code-block:: python - >>> my_sequence.cube_like_dimensions - + >>> my_sequence.cube_like_shape + [16, 4, 5] Equivalent to `ndcube.NDCube.array_axis_physical_types`, `ndcube.NDCubeSequence.array_axis_physical_types` returns a list of tuples of physical axis types. The same `IVOA UCD1+ controlled words `__ are used for the cube axes. @@ -327,10 +326,10 @@ So to explode along the wavelength axis, we should use an array axis index of `` >>> exploded_sequence = my_sequence.explode_along_axis(2) >>> # Check old and new shapes of the sequence - >>> my_sequence.dimensions - (, , , ) - >>> exploded_sequence.dimensions - (, , ) + >>> my_sequence.shape + (4, 4, 4, 5) + >>> exploded_sequence.shape + (20, 4, ,4) Note that an `~ndcube.NDCubeSequence` can be exploded along any axis. A common axis need not be defined and if one is it need not be the axis along which the `~ndcube.NDCubeSequence` is exploded. diff --git a/docs/explaining_ndcube/slicing.rst b/docs/explaining_ndcube/slicing.rst index 7f0fcbd6e..1c3aa17e9 100644 --- a/docs/explaining_ndcube/slicing.rst +++ b/docs/explaining_ndcube/slicing.rst @@ -16,7 +16,7 @@ Slicing NDCubes The `~ndcube.NDCube` slicing infrastructure returns a new `~ndcube.NDCube` with all relevant information consistently altered. This includes the data, WCS transformations, `~ndcube.ExtraCoords`, uncertainty and mask. This is achieved by applying the standard slicing API to the `~ndcube.NDCube`. -Because many of the inspection properties such `~ndcube.NDCube.dimensions` and `~ndcube.NDCube.array_axis_physical_types` are calculated on the fly, the information they return after slicing is also consistent with the sliced `~ndcube.NDCube`. +Because many of the inspection properties such `~ndcube.NDCube.shape` and `~ndcube.NDCube.array_axis_physical_types` are calculated on the fly, the information they return after slicing is also consistent with the sliced `~ndcube.NDCube`. This makes `~ndcube.NDCube`'s slicing infrastructure very powerful. To slice ``my_cube``, simply do something like: @@ -208,12 +208,12 @@ The arrays (blue) in each cube have been sliced and the coordinate objects (red/ :width: 800 :alt: Schematic of an NDCubeSequence after slicing. -We can confirm the dimensionality and physical types of the new sequence by checking the ``.dimensions`` and ``.array_axis_physical_types`` properties. +We can confirm the dimensionality and physical types of the new sequence by checking the ``.shape`` and ``.array_axis_physical_types`` properties. .. code-block:: python - >>> my_sequence_roi.dimensions - (, , , ) + >>> my_sequence_roi.shape + (3, 2, 2, 2) >>> my_sequence_roi.array_axis_physical_types [('meta.obs.sequence',), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)] @@ -222,8 +222,8 @@ If we want our region of interest to only apply to a single sub-cube, and we ind .. code-block:: python >>> single_cube_roi = my_sequence[1, 0, 1:3, 1:4] - >>> single_cube_roi.dimensions - + >>> single_cube_roi.shape + (2, 3) >>> single_cube_roi.array_axis_physical_types [('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)] @@ -234,8 +234,8 @@ This sequence will still represent the same region of interest from the same sin .. code-block:: python >>> roi_length1_sequence = my_sequence[0:1, 0, 1:3, 1:4] - >>> roi_length1_sequence.dimensions - (, , ) + >>> roi_length1_sequence.shape + (1, 2, 3) >>> roi_length1_sequence.array_axis_physical_types [('meta.obs.sequence',), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), @@ -252,13 +252,13 @@ Note that if a common axis is set, we do not have to slice this way. Instead, we simply have the option of using regular slicing or `ndcube.NDCubeSequence.index_as_cube`. In the above example, we set the common axis to ``0``. -Recall that, ``my_sequence`` has a shape of ``(, , , )``. -Therefore it has ``cube-like`` dimensions of ``(, , )`` where the first sub-cube extends along the 0th cube-like axis from 0 to 4, the second from 4 to 8 and the third from 8 to 12, and the fourth from 12 to 16. +Recall that, ``my_sequence`` has a shape of ``(4, 4, 4, 5)``. +Therefore it has ``cube-like`` dimensions of ``[16, 4, 5]`` where the first sub-cube extends along the 0th cube-like axis from 0 to 4, the second from 4 to 8 and the third from 8 to 12, and the fourth from 12 to 16. .. code-block:: python - >>> my_sequence.cube_like_dimensions - + >>> my_sequence.cube_like_shape + [16, 4, 5] Now say we want to extract the same region of interest as above, i.e. ``my_sequence[1, 0, 1:3, 1:4]``. This can be achieved by entering: @@ -266,8 +266,8 @@ This can be achieved by entering: .. code-block:: python >>> single_cube_roi = my_sequence.index_as_cube[4, 1:3, 1:4] - >>> single_cube_roi.dimensions - + >>> single_cube_roi.shape + (2, 3) >>> single_cube_roi.array_axis_physical_types [('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)] @@ -279,8 +279,8 @@ As before, the same region of interest from the same sub-cube is represented, ju .. code-block:: python >>> roi_length1_sequence = my_sequence.index_as_cube[4:5, 1:3, 1:4] - >>> roi_length1_sequence.dimensions - (, , , ) + >>> roi_length1_sequence.shape + (1, 1, 2, 3) >>> roi_length1_sequence.array_axis_physical_types [('meta.obs.sequence',), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), @@ -295,11 +295,8 @@ In cube-like indexing this corresponds to slices 3 to 9 along to their 1st cube .. code-block:: python >>> roi_across_cubes = my_sequence.index_as_cube[3:9, 1:3, 1:4] - >>> roi_across_cubes.dimensions - (, - , - , - ) + >>> roi_across_cubes.shape + (3, [1, 4, 1], 2, 3) >>> roi_across_cubes.array_axis_physical_types [('meta.obs.sequence',), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), diff --git a/ndcube/extra_coords/tests/test_extra_coords.py b/ndcube/extra_coords/tests/test_extra_coords.py index 88dcd4d1c..b104b20f3 100644 --- a/ndcube/extra_coords/tests/test_extra_coords.py +++ b/ndcube/extra_coords/tests/test_extra_coords.py @@ -140,7 +140,7 @@ def test_wcs_1d(wcs_1d_l): @pytest.fixture def extra_coords_wave(wave_lut): cube = MagicMock() - cube.dimensions = [10] * u.pix + cube.shape = [10] ec = ExtraCoords(cube) ec.add("wave", 0, wave_lut) @@ -162,7 +162,7 @@ def test_single_from_lut(extra_coords_wave): def test_two_1d_from_lut(time_lut): cube = MagicMock() - cube.dimensions = [10] * u.pix + cube.shape = [10] ec = ExtraCoords(cube) exposure_lut = range(10) * u.s @@ -193,7 +193,7 @@ def test_two_1d_from_lookup_tables(time_lut): [time_lut, exposure_lut], pt) # This has created an "orphan" extra_coords with no NDCube connected. - with pytest.raises(AttributeError, match=r"'NoneType' object has no attribute 'dimensions'"): + with pytest.raises(AttributeError, match=r"'NoneType' object has no attribute 'shape'"): assert ec.mapping == (0, 0) assert len(ec._lookup_tables) == 2 @@ -207,7 +207,7 @@ def test_two_1d_from_lookup_tables(time_lut): def test_skycoord(skycoord_1d_lut): cube = MagicMock() - cube.dimensions = [10, 10] * u.pix + cube.shape = [10, 10] ec = ExtraCoords(cube) ec.add(("lat", "lon"), (0, 1), skycoord_1d_lut, mesh=True) @@ -221,7 +221,7 @@ def test_skycoord(skycoord_1d_lut): def test_skycoord_1_pixel(skycoord_1d_lut): cube = MagicMock() - cube.dimensions = [10] * u.pix + cube.shape = [10] ec = ExtraCoords(cube) ec.add(("lon", "lat"), 0, skycoord_1d_lut, mesh=False) @@ -242,7 +242,7 @@ def test_skycoord_1_pixel(skycoord_1d_lut): def test_skycoord_mesh_false(skycoord_2d_lut): cube = MagicMock() - cube.dimensions = [10, 10] * u.pix + cube.shape = [10, 10] ec = ExtraCoords(cube) ec.add(("lat", "lon"), (0, 1), skycoord_2d_lut, mesh=False) @@ -256,7 +256,7 @@ def test_skycoord_mesh_false(skycoord_2d_lut): def test_extra_coords_index(skycoord_2d_lut, time_lut): cube = MagicMock() - cube.dimensions = [10, 10] * u.pix + cube.shape = [10, 10] ec = ExtraCoords(cube) ec.add(("lat", "lon"), (0, 1), skycoord_2d_lut, mesh=False) @@ -481,7 +481,7 @@ def test_resample(time_lut, wave_lut, skycoord_1d_lut, ndcube_4d_ln_lt_l_t): ec.add(("lon", "lat"), 1, skycoord_1d_lut) # Add coord that will not be sliced a = 2 - energy_lut = range(int(cube.shape[a].value)) * u.keV + energy_lut = range(int(cube.shape[a])) * u.keV ec.add("hello", a, energy_lut) # Call resample. diff --git a/ndcube/mixins/ndslicing.py b/ndcube/mixins/ndslicing.py index 7efbc9635..d2953363f 100644 --- a/ndcube/mixins/ndslicing.py +++ b/ndcube/mixins/ndslicing.py @@ -19,7 +19,7 @@ def __getitem__(self, item): if item is None or (isinstance(item, tuple) and None in item): raise IndexError("None indices not supported") - item = tuple(sanitize_slices(item, len(self.dimensions))) + item = tuple(sanitize_slices(item, len(self.data.shape))) sliced_cube = super().__getitem__(item) sliced_cube._global_coords._internal_coords = self.global_coords._internal_coords diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 3d0754df9..05a57a9ba 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -414,6 +414,10 @@ def combined_wcs(self): CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) ) + @property + def shape(self): + return self.data.shape + @property def dimensions(self): return u.Quantity(self.data.shape, unit=u.pix) @@ -631,7 +635,7 @@ def __str__(self): return textwrap.dedent(f"""\ NDCube ------ - Dimensions: {self.dimensions} + Shape: {self.shape} Physical Types of Axes: {self.array_axis_physical_types} Unit: {self.unit} Data Type: {self.data.dtype}""") @@ -652,9 +656,9 @@ def explode_along_axis(self, axis): ------- result : `ndcube.NDCubeSequence` """ - # If axis is -ve then calculate the axis from the length of the dimensions of one cube + # If axis is -ve then calculate the axis from the length of the shape of one cube if axis < 0: - axis = len(self.dimensions) + axis + axis = len(self.shape) + axis # To store the resultant cube result_cubes = [] # All slices are initially initialised as slice(None, None, None) @@ -1118,7 +1122,7 @@ def my_propagate(uncertainty, data, mask, **kwargs): return self # Ensure bin_size has right number of entries and each entry is an # integer fraction of the array shape in each dimension. - data_shape = self.dimensions.value.astype(int) + data_shape = self.shape.astype(int) naxes = len(data_shape) if len(bin_shape) != naxes: raise ValueError("bin_shape must have an entry for each array axis.") diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 669d7b3ef..5415ef007 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -8,7 +8,7 @@ import astropy.units as u from ndcube import utils -from ndcube.utils.exceptions import NDCubeDeprecationWarning +from ndcube.utils.exceptions import NDCubeDeprecationWarning, warn_deprecated from ndcube.visualization.descriptor import PlotterDescriptor @@ -57,7 +57,7 @@ def shape(self): @property def _shape(self): - dimensions = [len(self.data)] + list(self.data[0].shape) + dimensions = [len(self.data)] + list(self.data[0].data.shape) if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not # be the same. Therefore if the lengths are different, @@ -82,10 +82,11 @@ def cube_like_dimensions(self): """ The length of each array axis as if all cubes were concatenated along the common axis. """ + warn_deprecated("Replaced by cube_like_shape") if not isinstance(self._common_axis, int): raise TypeError("Common axis must be set.") dimensions = list(self._dimensions) - cube_like_dimensions = list(self._dimensions[1:]) + cube_like_dimensions = list(self._shape[1:]) if dimensions[self._common_axis + 1].isscalar: cube_like_dimensions[self._common_axis] = u.Quantity( dimensions[0].value * dimensions[self._common_axis + 1].value, unit=u.pix) @@ -95,6 +96,21 @@ def cube_like_dimensions(self): cube_like_dimensions = u.Quantity(cube_like_dimensions, unit=u.pix) return cube_like_dimensions + @property + def cube_like_shape(self): + """ + The length of each array axis as if all cubes were concatenated along the common axis. + """ + if not isinstance(self._common_axis, int): + raise TypeError("Common axis must be set.") + dimensions = list(self.shape) + cube_like_shape = list(self._shape[1:]) + if isinstance(dimensions[self._common_axis + 1], numbers.Integral): + cube_like_shape[self._common_axis] = dimensions[0] * dimensions[self._common_axis + 1] + else: + cube_like_shape[self._common_axis] = sum(dimensions[self._common_axis + 1]) + return cube_like_shape + @property def cube_like_array_axis_physical_types(self): """ @@ -473,8 +489,8 @@ def __init__(self, seq): def __getitem__(self, item): common_axis = self.seq._common_axis - common_axis_lengths = [int(cube.shape[common_axis].value) for cube in self.seq.data] - n_cube_dims = len(self.seq.cube_like_dimensions) + common_axis_lengths = [int(cube.shape[common_axis]) for cube in self.seq.data] + n_cube_dims = len(self.seq.cube_like_shape) n_uncommon_cube_dims = n_cube_dims - 1 # If item is iint or slice, turn into a tuple, filling in items # for unincluded axes with slice(None). This ensures it is diff --git a/ndcube/tests/test_ndcollection.py b/ndcube/tests/test_ndcollection.py index 86424f29a..dda2624f1 100644 --- a/ndcube/tests/test_ndcollection.py +++ b/ndcube/tests/test_ndcollection.py @@ -146,10 +146,10 @@ def test_collection_update_without_aligned_axes(): @pytest.mark.parametrize("collection, expected_aligned_dimensions", [ - (cube_collection, [4, 5]*u.pix), - (seq_collection, np.array([2*u.pix, 3*u.pix, 4*u.pix, 5*u.pix], dtype=object))]) + (cube_collection, [4, 5]), + (seq_collection, [2, 3, 4, 5])]) def test_aligned_dimensions(collection, expected_aligned_dimensions): - assert all(collection.aligned_dimensions == expected_aligned_dimensions) + assert np.all(collection.aligned_dimensions == expected_aligned_dimensions) @pytest.mark.parametrize("collection, expected", [ diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 17978e09e..4f3352b62 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1184,11 +1184,11 @@ def test_to_dask(ndcube_2d_dask): def test_squeeze(ndcube_4d_ln_l_t_lt): - assert np.array_equal(ndcube_4d_ln_l_t_lt.squeeze().dimensions, ndcube_4d_ln_l_t_lt.dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze().dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze(2).dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze([1,2]).dimensions) - assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0:1,0,:].dimensions, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze(2).dimensions) + assert np.array_equal(ndcube_4d_ln_l_t_lt.squeeze().shape, ndcube_4d_ln_l_t_lt.shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].shape, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze().shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,:,0,:].shape, ndcube_4d_ln_l_t_lt[:,:,0:1,:].squeeze(2).shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0,0,:].shape, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze([1,2]).shape) + assert np.array_equal(ndcube_4d_ln_l_t_lt[:,0:1,0,:].shape, ndcube_4d_ln_l_t_lt[:,0:1,0:1,:].squeeze(2).shape) def test_squeeze_error(ndcube_4d_ln_l_t_lt): diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index a631c7d5b..afc95d0d2 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -19,9 +19,8 @@ def derive_sliced_cube_dims(orig_cube_dims, tuple_item): if isinstance(s, int): del expected_cube_dims[i] else: - expected_cube_dims[i] = float(s.stop - s.start) * u.pix - expected_cube_dims *= u.pix - return expected_cube_dims + expected_cube_dims[i] = float(s.stop - s.start) + return tuple(expected_cube_dims) @pytest.mark.parametrize("ndc, item", @@ -40,7 +39,7 @@ def test_slice_sequence_axis(ndc, item): sliced_sequence = ndc[item] assert isinstance(sliced_sequence, NDCubeSequence) assert int(sliced_sequence.shape[0]) == tuple_item[0].stop - tuple_item[0].start - assert (sliced_sequence[0].shape == expected_cube0_dims).all() + assert (sliced_sequence[0].shape == expected_cube0_dims) @pytest.mark.parametrize("ndc, item", @@ -56,7 +55,7 @@ def test_extract_ndcube(ndc, item): tuple_item = item if isinstance(item, tuple) else (item,) expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].shape, tuple_item) assert isinstance(cube, NDCube) - assert (cube.shape == expected_cube_dims).all() + assert (cube.shape == expected_cube_dims) @pytest.mark.parametrize("ndc, item, expected_common_axis", @@ -74,43 +73,26 @@ def test_slice_common_axis(ndc, item, expected_common_axis): @pytest.mark.parametrize("ndc, item, expected_shape", ( - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7], (3 * u.pix, - 2 * u.pix, - [2., 3., 1.] * u.pix, - 4 * u.pix)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7, 0], (3 * u.pix, - [2., 3., 1.] * u.pix, - 4 * u.pix)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[2:4], (2 * u.pix, - 2 * u.pix, - 1 * u.pix, - 4 * u.pix)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:6], (2 * u.pix, - 2 * u.pix, - 3 * u.pix, - 4 * u.pix)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:6, :, 0], (3 * u.pix, - 2 * u.pix, - 4 * u.pix)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0::, 0, 0], (4 * u.pix, - 2 * u.pix)) + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7], (4, 1, 3, 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7, 0], (1, 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[2:4], (4, 0, 3, 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:6], (4, 2, 3, 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:6, :, 0], (4, 2, 3)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0::, 0, 0], (2,)) ), indirect=("ndc",)) def test_index_as_cube(ndc, item, expected_shape): - sliced_sequence = ndc.index_as_cube[item] - sliced_dims = sliced_sequence.shape - for dim, expected_dim in zip(sliced_dims, expected_shape): - (dim == expected_dim).all() + assert (ndc.index_as_cube[item].shape == expected_shape) @pytest.mark.parametrize("ndc, axis, expected_shape", ( - ("ndcubesequence_4c_ln_lt_l", 0, (8 * u.pix, - 3 * u.pix, - 4 * u.pix)), - ("ndcubesequence_4c_ln_lt_l_cax1", 1, (12 * u.pix, - 2 * u.pix, - 4 * u.pix)) + ("ndcubesequence_4c_ln_lt_l", 0, (8, + 3, + 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", 1, (12, + 2, + 4)) ), indirect=("ndc",)) def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape): @@ -122,23 +104,23 @@ def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape): @pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) def test_explode_along_axis_common_axis_same(ndc): exploded_sequence = ndc.explode_along_axis(2) - assert exploded_sequence.shape == (16*u.pix, 2*u.pix, 3*u.pix) + assert exploded_sequence.shape == (16, 2, 3) assert exploded_sequence._common_axis == ndc._common_axis @pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) def test_explode_along_axis_common_axis_changed(ndc): exploded_sequence = ndc.explode_along_axis(0) - assert exploded_sequence.shape == (8*u.pix, 3*u.pix, 4*u.pix) + assert exploded_sequence.shape == (8, 3, 4) assert exploded_sequence._common_axis == ndc._common_axis - 1 @pytest.mark.parametrize("ndc, expected_shape", ( - ("ndcubesequence_4c_ln_lt_l_cax1", (4 * u.pix, - 2. * u.pix, - 3. * u.pix, - 4. * u.pix)), + ("ndcubesequence_4c_ln_lt_l_cax1", (4, + 2., + 3., + 4.)), ), indirect=("ndc",)) def test_shape(ndc, expected_shape): @@ -148,17 +130,17 @@ def test_shape(ndc, expected_shape): @pytest.mark.parametrize("ndc, expected_shape", ( - ("ndcubesequence_4c_ln_lt_l_cax1", [2., 12, 4] * u.pix), + ("ndcubesequence_4c_ln_lt_l_cax1", [2., 12, 4]), ), indirect=("ndc",)) def test_cube_like_shape(ndc, expected_shape): - assert (ndc.cube_like_dimensions == expected_shape).all() + assert (ndc.cube_like_shape == expected_shape) @pytest.mark.parametrize("ndc", (("ndcubesequence_4c_ln_lt_l",)), indirect=("ndc",)) -def test_cube_like_dimensions_error(ndc): +def test_cube_like_shape_error(ndc): with pytest.raises(TypeError): - ndc.cube_like_dimensions + ndc.cube_like_shape @pytest.mark.parametrize("ndc", (("ndcubesequence_3c_l_ln_lt_cax1",)), indirect=("ndc",)) From 494daaa44fb78bf5081ac74c8cd914173a4868b8 Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 14:04:31 +0200 Subject: [PATCH 30/35] Update changelog/684.breaking.rst Co-authored-by: DanRyanIrish --- changelog/684.breaking.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/684.breaking.rst b/changelog/684.breaking.rst index f1dabb66a..d39495708 100644 --- a/changelog/684.breaking.rst +++ b/changelog/684.breaking.rst @@ -1 +1 @@ -"dimensions" property has been depcreated and replaced by "shape" +"dimensions" property on ~ndcube.NDCube` and `~ndcube.NDCubeSequence` have been deprecated and replaced by "shape" From c861ab84557599ddbf10099e918feb4710480755 Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 14:47:20 +0200 Subject: [PATCH 31/35] Second set of changes - Broken like my heart --- .ruff.toml | 2 +- docs/explaining_ndcube/data_classes.rst | 4 +- docs/explaining_ndcube/slicing.rst | 6 +- ndcube/conftest.py | 2 +- .../extra_coords/tests/test_extra_coords.py | 4 +- ndcube/mixins/ndslicing.py | 2 +- ndcube/ndcube.py | 36 ++-- ndcube/ndcube_sequence.py | 11 +- ndcube/tests/test_ndcollection.py | 1 - ndcube/tests/test_ndcube.py | 17 +- ndcube/utils/cube.py | 1 - ndcube/utils/exceptions.py | 19 +- ndcube/utils/wcs_high_level_conversion.py | 187 ------------------ ndcube/visualization/mpl_plotter.py | 6 +- ndcube/wcs/tools.py | 2 +- .../wcs/wrappers/tests/test_resampled_wcs.py | 2 +- 16 files changed, 61 insertions(+), 241 deletions(-) delete mode 100644 ndcube/utils/wcs_high_level_conversion.py diff --git a/.ruff.toml b/.ruff.toml index 5709980c4..ea73580e8 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -10,7 +10,7 @@ exclude = [ [lint] select = [ "E", - #"F", + "F", "W", #"UP", #"PT" diff --git a/docs/explaining_ndcube/data_classes.rst b/docs/explaining_ndcube/data_classes.rst index 865ebb9ad..67bc85508 100644 --- a/docs/explaining_ndcube/data_classes.rst +++ b/docs/explaining_ndcube/data_classes.rst @@ -329,7 +329,7 @@ So to explode along the wavelength axis, we should use an array axis index of `` >>> my_sequence.shape (4, 4, 4, 5) >>> exploded_sequence.shape - (20, 4, ,4) + (20, 4, 4) Note that an `~ndcube.NDCubeSequence` can be exploded along any axis. A common axis need not be defined and if one is it need not be the axis along which the `~ndcube.NDCubeSequence` is exploded. @@ -456,7 +456,7 @@ Because aligned axes must have the same lengths, we can get the lengths of the a .. code-block:: python >>> my_collection.aligned_dimensions - + array([4, 4], dtype=object) Note that this only tells us the lengths of the aligned axes. To see the lengths of the non-aligned axes, e.g. the spectral axis of the ``observations`` object, you must inspect that ND object individually. diff --git a/docs/explaining_ndcube/slicing.rst b/docs/explaining_ndcube/slicing.rst index 1c3aa17e9..7a338cf4b 100644 --- a/docs/explaining_ndcube/slicing.rst +++ b/docs/explaining_ndcube/slicing.rst @@ -296,7 +296,7 @@ In cube-like indexing this corresponds to slices 3 to 9 along to their 1st cube >>> roi_across_cubes = my_sequence.index_as_cube[3:9, 1:3, 1:4] >>> roi_across_cubes.shape - (3, [1, 4, 1], 2, 3) + (3, (1, 4, 1), 2, 3) >>> roi_across_cubes.array_axis_physical_types [('meta.obs.sequence',), ('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), @@ -360,7 +360,7 @@ Thus a self-consistent result is obtained. >>> sliced_collection.keys() dict_keys(['observations', 'linewidths']) >>> sliced_collection.aligned_dimensions - + array([2, 1], dtype=object) This is true even if the aligned axes are not in order. Let's say we axis order of the ``linewidths`` cube was reversed. @@ -383,6 +383,6 @@ Let's say we axis order of the ``linewidths`` cube was reversed. >>> sliced_collection_reversed.keys() dict_keys(['observations', 'linewidths']) >>> sliced_collection_reversed.aligned_dimensions - + array([2, 1], dtype=object) The same result is obtained. diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 797914e28..130941e6a 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -505,7 +505,7 @@ def ndcube_2d_ln_lt_uncert_ec(wcs_2d_lt_ln): cube = NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) cube.extra_coords.add( "time", 0, - Time("2000-01-01 00:00", scale="utc") + Timedelta(np.arange(shape[0])*60, format="sec")) + Time("2000-01-01 00:00", scale="utc") + TimeDelta(np.arange(shape[0])*60, format="sec")) return cube diff --git a/ndcube/extra_coords/tests/test_extra_coords.py b/ndcube/extra_coords/tests/test_extra_coords.py index b104b20f3..5ca2cae13 100644 --- a/ndcube/extra_coords/tests/test_extra_coords.py +++ b/ndcube/extra_coords/tests/test_extra_coords.py @@ -540,11 +540,11 @@ def test_resample_errors(time_lut, wave_lut, ndcube_4d_ln_lt_l_t): # Test error for incorrect number of factor elements. with pytest.raises(ValueError): - output = ec.resample([2], ndcube=cube) + ec.resample([2], ndcube=cube) # Test error for incorrect number of offset elements. with pytest.raises(ValueError): - output = ec.resample(2, [2], ndcube=cube) + ec.resample(2, [2], ndcube=cube) def test_resample_wcs(wcs_1d_l): diff --git a/ndcube/mixins/ndslicing.py b/ndcube/mixins/ndslicing.py index d2953363f..919b4686e 100644 --- a/ndcube/mixins/ndslicing.py +++ b/ndcube/mixins/ndslicing.py @@ -19,7 +19,7 @@ def __getitem__(self, item): if item is None or (isinstance(item, tuple) and None in item): raise IndexError("None indices not supported") - item = tuple(sanitize_slices(item, len(self.data.shape))) + item = tuple(sanitize_slices(item, len(self.shape))) sliced_cube = super().__getitem__(item) sliced_cube._global_coords._internal_coords = self.global_coords._internal_coords diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 05a57a9ba..9ab42754d 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1,7 +1,6 @@ import abc import numbers import textwrap -import warnings from copy import deepcopy from typing import Any from collections import namedtuple @@ -22,14 +21,14 @@ from astropy.wcs import WCS from astropy.wcs.utils import _split_matrix from astropy.wcs.wcsapi import BaseHighLevelWCS, HighLevelWCSWrapper +from astropy.wcs.wcsapi.high_level_api import values_to_high_level_objects from ndcube import utils from ndcube.extra_coords.extra_coords import ExtraCoords, ExtraCoordsABC from ndcube.global_coords import GlobalCoords, GlobalCoordsABC from ndcube.mixins import NDCubeSlicingMixin from ndcube.ndcube_sequence import NDCubeSequence -from ndcube.utils.exceptions import NDCubeDeprecationWarning -from ndcube.utils.wcs_high_level_conversion import values_to_high_level_objects +from ndcube.utils.exceptions import warn_deprecated, warn_user from ndcube.visualization import PlotterDescriptor from ndcube.wcs.wrappers import CompoundLowLevelWCS, ResampledLowLevelWCS @@ -420,6 +419,7 @@ def shape(self): @property def dimensions(self): + warn_deprecated("Replaced by ndcube.NDCube.shape") return u.Quantity(self.data.shape, unit=u.pix) @property @@ -857,9 +857,10 @@ def _as_mpl_axes(self): if hasattr(self.plotter, "_as_mpl_axes"): return self.plotter._as_mpl_axes() else: - warnings.warn(f"The current plotter {self.plotter} does not have a '_as_mpl_axes' method. " - "The default MatplotlibPlotter._as_mpl_axes method will be used instead.", - UserWarning) + warn_user(f"The current plotter {self.plotter} does not have a '_as_mpl_axes' method. " + "The default MatplotlibPlotter._as_mpl_axes method will be used instead.") + + from ndcube.visualization.mpl_plotter import MatplotlibPlotter plotter = MatplotlibPlotter(self) return plotter._as_mpl_axes() @@ -967,12 +968,10 @@ def __pow__(self, value): except ValueError as e: if "unsupported operation" in e.args[0]: new_uncertainty = None - warnings.warn(f"{type(self.uncertainty)} does not support propagation of uncertainties for power. Setting uncertainties to None.", - UserWarning, stacklevel=2) + warn_user(f"{type(self.uncertainty)} does not support propagation of uncertainties for power. Setting uncertainties to None.") elif "does not support uncertainty propagation" in e.args[0]: new_uncertainty = None - warnings.warn(f"{e.args[0]} Setting uncertainties to None.", - UserWarning, stacklevel=2) + warn_user(f"{e.args[0]} Setting uncertainties to None.") else: raise e @@ -1117,12 +1116,11 @@ def my_propagate(uncertainty, data, mask, **kwargs): new_unit = new_unit or self.unit # Make sure the input bin dimensions are integers. bin_shape = np.rint(bin_shape).astype(int) - offsets = (bin_shape - 1) / 2 if all(bin_shape == 1): return self # Ensure bin_size has right number of entries and each entry is an # integer fraction of the array shape in each dimension. - data_shape = self.shape.astype(int) + data_shape = self.shape naxes = len(data_shape) if len(bin_shape) != naxes: raise ValueError("bin_shape must have an entry for each array axis.") @@ -1141,11 +1139,10 @@ def my_propagate(uncertainty, data, mask, **kwargs): break else: masked_type = np.ma.masked_array - warn.warning("data and mask arrays of different or unrecognized types. " - "Casting them into a numpy masked array.") + warn_user("data and mask arrays of different or unrecognized types. Casting them into a numpy masked array.") data = masked_type(self.data, m) - reshape = np.empty(data_shape.size + bin_shape.size, dtype=int) + reshape = np.empty(data_shape + bin_shape, dtype=int) new_shape = (data_shape / bin_shape).astype(int) reshape[0::2] = new_shape reshape[1::2] = bin_shape @@ -1167,17 +1164,17 @@ def my_propagate(uncertainty, data, mask, **kwargs): new_uncertainty = None if propagate_uncertainties: if self.uncertainty is None: - warnings.warn("Uncertainties cannot be propagated as there are no uncertainties, " + warn_user("Uncertainties cannot be propagated as there are no uncertainties, " "i.e., the `uncertainty` keyword was never set on creation of this NDCube.") elif isinstance(self.uncertainty, astropy.nddata.UnknownUncertainty): - warnings.warn("The uncertainty on this NDCube has no known way to propagate forward and so will be dropped. " + warn_user("The uncertainty on this NDCube has no known way to propagate forward and so will be dropped. " "To create an uncertainty that can propagate, please see " "https://docs.astropy.org/en/stable/uncertainty/index.html") elif (not operation_ignores_mask and (self.mask is True or (self.mask is not None and not isinstance(self.mask, bool) and self.mask.all()))): - warnings.warn("Uncertainties cannot be propagated as all values are masked and " + warn_user("Uncertainties cannot be propagated as all values are masked and " "operation_ignores_mask is False.") else: if propagate_uncertainties is True: @@ -1218,9 +1215,6 @@ def my_propagate(uncertainty, data, mask, **kwargs): new_cube._global_coords = self._global_coords # Reconstitute extra coords if not self.extra_coords.is_empty: - new_array_grids = [None if bin_shape[i] == 1 else - np.arange(offsets[i], data_shape[i] + offsets[i], bin_shape[i]) - for i in range(naxes)] new_cube._extra_coords = self.extra_coords.resample(bin_shape, ndcube=new_cube) return new_cube diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 5415ef007..50f8ae747 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -1,14 +1,13 @@ import copy import numbers import textwrap -import warnings import numpy as np import astropy.units as u from ndcube import utils -from ndcube.utils.exceptions import NDCubeDeprecationWarning, warn_deprecated +from ndcube.utils.exceptions import warn_deprecated from ndcube.visualization.descriptor import PlotterDescriptor @@ -48,7 +47,7 @@ def dimensions(self): """ The length of each axis including the sequence axis. """ - warnings.warn("Replaced by ndcube.NDCubeSequence.shape", NDCubeDeprecationWarning) + warn_deprecated("Replaced by ndcube.NDCubeSequence.shape") return tuple([d * u.pix for d in self._shape]) @property @@ -82,7 +81,7 @@ def cube_like_dimensions(self): """ The length of each array axis as if all cubes were concatenated along the common axis. """ - warn_deprecated("Replaced by cube_like_shape") + warn_deprecated("Replaced by ndcube.NDCubeSequence.cube_like_shape") if not isinstance(self._common_axis, int): raise TypeError("Common axis must be set.") dimensions = list(self._dimensions) @@ -174,8 +173,6 @@ def common_axis_coords(self): """ common_axis = self._common_axis # Get coordinate objects associated with the common axis in all cubes. - common_axis_names = set.intersection(*[set(cube.array_axis_physical_types[common_axis]) - for cube in self.data]) common_coords = [] mappings = [] for i, cube in enumerate(self.data): @@ -289,7 +286,7 @@ def crop(self, *points, wcses=None): item = self._get_sequence_crop_item(*points, wcses=wcses) return self[item] - def crop_by_values(self, lower_corner, upper_corner, units=None, wcses=None): + def crop_by_values(self, *points, units=None, wcses=None): """ Crop cubes in sequence to smallest pixel-space bounding box containing the input points. diff --git a/ndcube/tests/test_ndcollection.py b/ndcube/tests/test_ndcollection.py index dda2624f1..8ec12630f 100644 --- a/ndcube/tests/test_ndcollection.py +++ b/ndcube/tests/test_ndcollection.py @@ -2,7 +2,6 @@ import numpy as np import pytest -import astropy.units as u import astropy.wcs from ndcube import NDCollection, NDCube, NDCubeSequence diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 4f3352b62..b962e0339 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -19,6 +19,7 @@ from ndcube import ExtraCoords, NDCube from ndcube.tests import helpers +from ndcube.utils.exceptions import NDCubeWarning def generate_data(shape): @@ -790,7 +791,7 @@ def test_wcs_type_after_init(ndcube_3d_ln_lt_l, wcs_3d_l_lt_ln): def test_rebin(ndcube_3d_l_ln_lt_ectime): cube = ndcube_3d_l_ln_lt_ectime[:, 1:] bin_shape = (10, 2, 1) - with pytest.warns(UserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): + with pytest.warns(NDCubeWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True) output_sc, output_spec = output.axis_world_coords(wcs=output.wcs) output_time, = output.axis_world_coords(wcs=output.extra_coords) @@ -845,7 +846,7 @@ def test_rebin_no_ec(ndcube_3d_l_ln_lt_ectime): cube = ndcube_3d_l_ln_lt_ectime[:, 1:] cube._extra_coords = ExtraCoords(cube) bin_shape = (10, 2, 1) - with pytest.warns(UserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): + with pytest.warns(NDCubeWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): output = cube.rebin(bin_shape, operation=np.mean, propagate_uncertainties=True) assert output.extra_coords.is_empty @@ -929,20 +930,20 @@ def test_rebin_no_propagate(ndcube_2d_ln_lt_mask_uncert): bin_shape = (2, 4) cube._mask[:] = True - with pytest.warns(UserWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): + with pytest.warns(NDCubeWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True, operation_ignores_mask=False) assert output.uncertainty is None cube._mask = True - with pytest.warns(UserWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): + with pytest.warns(NDCubeWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True, operation_ignores_mask=False) assert output.uncertainty is None cube._mask = False cube._uncertainty = UnknownUncertainty(cube.data * 0.1) - with pytest.warns(UserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): + with pytest.warns(NDCubeWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True) assert output.uncertainty is None @@ -1111,7 +1112,7 @@ def test_cube_arithmetic_rdivide(ndcube_2d_ln_lt_units, value): @pytest.mark.parametrize('value', [1, 2, -1]) def test_cube_arithmetic_rdivide_uncertainty(ndcube_4d_unit_uncertainty, value): cube_quantity = u.Quantity(ndcube_4d_unit_uncertainty.data, ndcube_4d_unit_uncertainty.unit) - with pytest.warns(UserWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): + with pytest.warns(NDCubeWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): with np.errstate(divide='ignore'): new_cube = value / ndcube_4d_unit_uncertainty check_arithmetic_value_and_units(new_cube, value / cube_quantity) @@ -1150,7 +1151,7 @@ def test_cube_arithmetic_power(ndcube_2d_ln_lt, power): @pytest.mark.parametrize('power', [2, -2, 10, 0.5]) def test_cube_arithmetic_power_unknown_uncertainty(ndcube_4d_unit_uncertainty, power): cube_quantity = u.Quantity(ndcube_4d_unit_uncertainty.data, ndcube_4d_unit_uncertainty.unit) - with pytest.warns(UserWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): + with pytest.warns(NDCubeWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): with np.errstate(divide='ignore'): new_cube = ndcube_4d_unit_uncertainty ** power check_arithmetic_value_and_units(new_cube, cube_quantity**power) @@ -1159,7 +1160,7 @@ def test_cube_arithmetic_power_unknown_uncertainty(ndcube_4d_unit_uncertainty, p @pytest.mark.parametrize('power', [2, -2, 10, 0.5]) def test_cube_arithmetic_power_std_uncertainty(ndcube_2d_ln_lt_uncert, power): cube_quantity = u.Quantity(ndcube_2d_ln_lt_uncert.data, ndcube_2d_ln_lt_uncert.unit) - with pytest.warns(UserWarning, match=r" does not support propagation of uncertainties for power. Setting uncertainties to None."): + with pytest.warns(NDCubeWarning, match=r" does not support propagation of uncertainties for power. Setting uncertainties to None."): with np.errstate(divide='ignore'): new_cube = ndcube_2d_ln_lt_uncert ** power check_arithmetic_value_and_units(new_cube, cube_quantity**power) diff --git a/ndcube/utils/cube.py b/ndcube/utils/cube.py index cdaee114b..dc29a978e 100644 --- a/ndcube/utils/cube.py +++ b/ndcube/utils/cube.py @@ -272,7 +272,6 @@ def propagate_rebin_uncertainties(uncertainty, data, mask, operation, operation_ # If there is no mask and operation is not nan-type, build generator # so non-mask can still be iterated. n_pix_per_bin = data.shape[flat_axis] - new_shape = data.shape[1:] mask1 = (False for i in range(1, n_pix_per_bin)) else: # Mask uncertainties corresponding to nan data if operation is nantype. diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py index 2ddac0ba1..ad85d1c92 100644 --- a/ndcube/utils/exceptions.py +++ b/ndcube/utils/exceptions.py @@ -6,7 +6,7 @@ """ import warnings -__all__ = ["NDCubeWarning", "NDCubeDeprecationWarning", "warn_deprecated"] +__all__ = ["NDCubeWarning", "NDCubeDeprecationWarning", "warn_user", "warn_deprecated"] class NDCubeWarning(UserWarning): @@ -19,6 +19,23 @@ class NDCubeDeprecationWarning(FutureWarning, NDCubeWarning): A warning class to indicate a deprecated feature. """ + +def warn_user(msg, stacklevel=1): + """ + Raise a `NDCubeWarning`. + + Parameters + ---------- + msg : str + Warning message. + stacklevel : int + This is interpreted relative to the call to this function, + e.g. ``stacklevel=1`` (the default) sets the stack level in the + code that calls this function. + """ + warnings.warn(msg, NDCubeWarning , stacklevel + 1) + + def warn_deprecated(msg, stacklevel=1): """ Raise a `NDCubeDeprecationWarning`. diff --git a/ndcube/utils/wcs_high_level_conversion.py b/ndcube/utils/wcs_high_level_conversion.py deleted file mode 100644 index 2f3c026be..000000000 --- a/ndcube/utils/wcs_high_level_conversion.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -This module provides helpers introduced in https://github.com/astropy/astropy/pull/11950. -""" -from collections import OrderedDict, defaultdict - -from astropy.wcs.wcsapi.high_level_api import default_order, rec_getattr - -__all__ = ['values_to_high_level_objects', 'high_level_objects_to_values'] - -try: - from astropy.wcs.wcsapi.high_level_api import high_level_objects_to_values, values_to_high_level_objects - -except ImportError: - - def high_level_objects_to_values(*world_objects, low_level_wcs): - """ - Convert the input high level object to low level values. - - This function uses the information in ``wcs.world_axis_object_classes`` and - ``wcs.world_axis_object_components`` to convert the high level objects - (such as `~.SkyCoord`) to low level "values" `~.Quantity` objects. - - This is used in `.HighLevelWCSMixin.world_to_pixel`, but provided as a - separate function for use in other places where needed. - - Parameters - ---------- - *world_objects: object - High level coordinate objects. - - low_level_wcs: `.BaseLowLevelWCS` - The WCS object to use to interpret the coordinates. - """ - # Cache the classes and components since this may be expensive - serialized_classes = low_level_wcs.world_axis_object_classes - components = low_level_wcs.world_axis_object_components - - # Deserialize world_axis_object_classes using the default order - classes = OrderedDict() - for key in default_order(components): - if low_level_wcs.serialized_classes: - classes[key] = deserialize_class(serialized_classes[key], - construct=False) - else: - classes[key] = serialized_classes[key] - - # Check that the number of classes matches the number of inputs - if len(world_objects) != len(classes): - raise ValueError(f"Number of world inputs ({len(world_objects)}) does not match " - f"expected ({len(classes)})") - - # Determine whether the classes are uniquely matched, that is we check - # whether there is only one of each class. - world_by_key = {} - unique_match = True - for w in world_objects: - matches = [] - for key, (klass, *_) in classes.items(): - if isinstance(w, klass): - matches.append(key) - if len(matches) == 1: - world_by_key[matches[0]] = w - else: - unique_match = False - break - - # If the match is not unique, the order of the classes needs to match, - # whereas if all classes are unique, we can still intelligently match - # them even if the order is wrong. - - objects = {} - - if unique_match: - - for key, (klass, args, kwargs, *rest) in classes.items(): - - if len(rest) == 0: - klass_gen = klass - elif len(rest) == 1: - klass_gen = rest[0] - else: - raise ValueError("Tuples in world_axis_object_classes should have length 3 or 4") - - # FIXME: For now SkyCoord won't auto-convert upon initialization - # https://github.com/astropy/astropy/issues/7689 - from astropy.coordinates import SkyCoord - if isinstance(world_by_key[key], SkyCoord): - if 'frame' in kwargs: - objects[key] = world_by_key[key].transform_to(kwargs['frame']) - else: - objects[key] = world_by_key[key] - else: - objects[key] = klass_gen(world_by_key[key], *args, **kwargs) - - else: - - for ikey, key in enumerate(classes): - - klass, args, kwargs, *rest = classes[key] - - if len(rest) == 0: - klass_gen = klass - elif len(rest) == 1: - klass_gen = rest[0] - else: - raise ValueError("Tuples in world_axis_object_classes should have length 3 or 4") - - w = world_objects[ikey] - if not isinstance(w, klass): - raise ValueError("Expected the following order of world " - "arguments: {}".format(', '.join([k.__name__ for (k, _, _) in classes.values()]))) - - # FIXME: For now SkyCoord won't auto-convert upon initialization - # https://github.com/astropy/astropy/issues/7689 - from astropy.coordinates import SkyCoord - if isinstance(w, SkyCoord): - if 'frame' in kwargs: - objects[key] = w.transform_to(kwargs['frame']) - else: - objects[key] = w - else: - objects[key] = klass_gen(w, *args, **kwargs) - - # We now extract the attributes needed for the world values - world = [] - for key, _, attr in components: - if callable(attr): - world.append(attr(objects[key])) - else: - world.append(rec_getattr(objects[key], attr)) - - return world - - def values_to_high_level_objects(*world_values, low_level_wcs): - """ - Convert low level values into high level objects. - - This function uses the information in ``wcs.world_axis_object_classes`` and - ``wcs.world_axis_object_components`` to convert the high level objects - (such as `~.SkyCoord`) to low level "values" `~.Quantity` objects. - - This is used in `.HighLevelWCSMixin.pixel_to_world`, but provided as a - separate function for use in other places where needed. - - Parameters - ---------- - *world_values: object - Low level, "values" representations of the world coordinates. - - low_level_wcs: `.BaseLowLevelWCS` - The WCS object to use to interpret the coordinates. - """ - # Cache the classes and components since this may be expensive - components = low_level_wcs.world_axis_object_components - classes = low_level_wcs.world_axis_object_classes - - # Deserialize classes - if low_level_wcs.serialized_classes: - classes_new = {} - for key, value in classes.items(): - classes_new[key] = deserialize_class(value, construct=False) - classes = classes_new - - args = defaultdict(list) - kwargs = defaultdict(dict) - - for i, (key, attr, _) in enumerate(components): - if isinstance(attr, str): - kwargs[key][attr] = world_values[i] - else: - while attr > len(args[key]) - 1: - args[key].append(None) - args[key][attr] = world_values[i] - - result = [] - - for key in default_order(components): - klass, ar, kw, *rest = classes[key] - if len(rest) == 0: - klass_gen = klass - elif len(rest) == 1: - klass_gen = rest[0] - else: - raise ValueError("Tuples in world_axis_object_classes should have length 3 or 4") - result.append(klass_gen(*args[key], *ar, **kwargs[key], **kw)) - - return result diff --git a/ndcube/visualization/mpl_plotter.py b/ndcube/visualization/mpl_plotter.py index c015cb1af..3bedbf510 100644 --- a/ndcube/visualization/mpl_plotter.py +++ b/ndcube/visualization/mpl_plotter.py @@ -7,6 +7,7 @@ from astropy.utils.exceptions import AstropyUserWarning from astropy.visualization.wcsaxes import WCSAxes +from ndcube.utils.exceptions import warn_user from . import plotting_utils as utils from .base import BasePlotter from .descriptor import MISSING_ANIMATORS_ERROR_MSG @@ -253,10 +254,9 @@ def _prep_animate_args(self, wcs, plot_axes, axes_units, data_unit): # TODO: Add support for transposing the array. if 'y' in plot_axes and plot_axes.index('y') < plot_axes.index('x'): - warnings.warn( + warn_user( "Animating a NDCube does not support transposing the array. The world axes " - "may not display as expected because the array will not be transposed.", - UserWarning + "may not display as expected because the array will not be transposed." ) plot_axes = [p if p is not None else 0 for p in plot_axes] diff --git a/ndcube/wcs/tools.py b/ndcube/wcs/tools.py index a74579a55..4924bd1e3 100644 --- a/ndcube/wcs/tools.py +++ b/ndcube/wcs/tools.py @@ -48,7 +48,7 @@ def unwrap_wcs_to_fitswcs(wcs): if hasattr(low_level_wrapper, "low_level_wcs"): low_level_wrapper = low_level_wrapper.low_level_wcs if not isinstance(low_level_wrapper, WCS): - raise TypeError(f"Base-level WCS must be type {type(WCS)}. Found: {type(low_level_wcs)}") + raise TypeError(f"Base-level WCS must be type {type(WCS)}. Found: {type(low_level_wrapper)}") fitswcs = low_level_wrapper dropped_data_axes = np.zeros(fitswcs.naxis, dtype=bool) # Unwrap each wrapper in reverse order and edit fitswcs. diff --git a/ndcube/wcs/wrappers/tests/test_resampled_wcs.py b/ndcube/wcs/wrappers/tests/test_resampled_wcs.py index 933c6bc44..5d08cda1c 100644 --- a/ndcube/wcs/wrappers/tests/test_resampled_wcs.py +++ b/ndcube/wcs/wrappers/tests/test_resampled_wcs.py @@ -144,7 +144,7 @@ def test_factor_wrong_length_error(celestial_wcs): indirect=True) def test_scalar_wrong_length_error(celestial_wcs): with pytest.raises(ValueError): - wcs = ResampledLowLevelWCS(celestial_wcs, 2, offset=[1] * 3) + ResampledLowLevelWCS(celestial_wcs, 2, offset=[1] * 3) @pytest.mark.parametrize('celestial_wcs', From 26605d2d54058087522ad7bb9763f7365cb6987e Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 15:02:49 +0200 Subject: [PATCH 32/35] Third set of changes --- ndcube/ndcube.py | 2 +- ndcube/tests/helpers.py | 2 -- ndcube/tests/test_ndcube.py | 18 +++++++++--------- ndcube/tests/test_ndcubesequence.py | 12 +++++------- ndcube/utils/exceptions.py | 17 +++++++++++++---- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 9ab42754d..56589d74c 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1142,7 +1142,7 @@ def my_propagate(uncertainty, data, mask, **kwargs): warn_user("data and mask arrays of different or unrecognized types. Casting them into a numpy masked array.") data = masked_type(self.data, m) - reshape = np.empty(data_shape + bin_shape, dtype=int) + reshape = np.empty(len(data_shape) + len(bin_shape), dtype=int) new_shape = (data_shape / bin_shape).astype(int) reshape[0::2] = new_shape reshape[1::2] = bin_shape diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 37008bf9b..c03b3e20f 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -2,7 +2,6 @@ """ Helpers for testing ndcube. """ -import unittest from pathlib import Path from functools import wraps @@ -97,7 +96,6 @@ def assert_metas_equal(test_input, expected_output): def assert_cubes_equal(test_input, expected_cube): - unittest.TestCase() assert isinstance(test_input, type(expected_cube)) assert np.all(test_input.mask == expected_cube.mask) assert_wcs_are_equal(test_input.wcs, expected_cube.wcs) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index b962e0339..56d14084f 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -19,7 +19,7 @@ from ndcube import ExtraCoords, NDCube from ndcube.tests import helpers -from ndcube.utils.exceptions import NDCubeWarning +from ndcube.utils.exceptions import NDCubeUserWarning def generate_data(shape): @@ -791,7 +791,7 @@ def test_wcs_type_after_init(ndcube_3d_ln_lt_l, wcs_3d_l_lt_ln): def test_rebin(ndcube_3d_l_ln_lt_ectime): cube = ndcube_3d_l_ln_lt_ectime[:, 1:] bin_shape = (10, 2, 1) - with pytest.warns(NDCubeWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): + with pytest.warns(NDCubeUserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True) output_sc, output_spec = output.axis_world_coords(wcs=output.wcs) output_time, = output.axis_world_coords(wcs=output.extra_coords) @@ -846,7 +846,7 @@ def test_rebin_no_ec(ndcube_3d_l_ln_lt_ectime): cube = ndcube_3d_l_ln_lt_ectime[:, 1:] cube._extra_coords = ExtraCoords(cube) bin_shape = (10, 2, 1) - with pytest.warns(NDCubeWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): + with pytest.warns(NDCubeUserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): output = cube.rebin(bin_shape, operation=np.mean, propagate_uncertainties=True) assert output.extra_coords.is_empty @@ -930,20 +930,20 @@ def test_rebin_no_propagate(ndcube_2d_ln_lt_mask_uncert): bin_shape = (2, 4) cube._mask[:] = True - with pytest.warns(NDCubeWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): + with pytest.warns(NDCubeUserWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True, operation_ignores_mask=False) assert output.uncertainty is None cube._mask = True - with pytest.warns(NDCubeWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): + with pytest.warns(NDCubeUserWarning, match="Uncertainties cannot be propagated as all values are masked and operation_ignores_mask is False."): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True, operation_ignores_mask=False) assert output.uncertainty is None cube._mask = False cube._uncertainty = UnknownUncertainty(cube.data * 0.1) - with pytest.warns(NDCubeWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): + with pytest.warns(NDCubeUserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"): output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True) assert output.uncertainty is None @@ -1112,7 +1112,7 @@ def test_cube_arithmetic_rdivide(ndcube_2d_ln_lt_units, value): @pytest.mark.parametrize('value', [1, 2, -1]) def test_cube_arithmetic_rdivide_uncertainty(ndcube_4d_unit_uncertainty, value): cube_quantity = u.Quantity(ndcube_4d_unit_uncertainty.data, ndcube_4d_unit_uncertainty.unit) - with pytest.warns(NDCubeWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): + with pytest.warns(NDCubeUserWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): with np.errstate(divide='ignore'): new_cube = value / ndcube_4d_unit_uncertainty check_arithmetic_value_and_units(new_cube, value / cube_quantity) @@ -1151,7 +1151,7 @@ def test_cube_arithmetic_power(ndcube_2d_ln_lt, power): @pytest.mark.parametrize('power', [2, -2, 10, 0.5]) def test_cube_arithmetic_power_unknown_uncertainty(ndcube_4d_unit_uncertainty, power): cube_quantity = u.Quantity(ndcube_4d_unit_uncertainty.data, ndcube_4d_unit_uncertainty.unit) - with pytest.warns(NDCubeWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): + with pytest.warns(NDCubeUserWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."): with np.errstate(divide='ignore'): new_cube = ndcube_4d_unit_uncertainty ** power check_arithmetic_value_and_units(new_cube, cube_quantity**power) @@ -1160,7 +1160,7 @@ def test_cube_arithmetic_power_unknown_uncertainty(ndcube_4d_unit_uncertainty, p @pytest.mark.parametrize('power', [2, -2, 10, 0.5]) def test_cube_arithmetic_power_std_uncertainty(ndcube_2d_ln_lt_uncert, power): cube_quantity = u.Quantity(ndcube_2d_ln_lt_uncert.data, ndcube_2d_ln_lt_uncert.unit) - with pytest.warns(NDCubeWarning, match=r" does not support propagation of uncertainties for power. Setting uncertainties to None."): + with pytest.warns(NDCubeUserWarning, match=r" does not support propagation of uncertainties for power. Setting uncertainties to None."): with np.errstate(divide='ignore'): new_cube = ndcube_2d_ln_lt_uncert ** power check_arithmetic_value_and_units(new_cube, cube_quantity**power) diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index afc95d0d2..e33ca5d02 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -1,4 +1,3 @@ -import unittest import numpy as np import pytest @@ -39,7 +38,7 @@ def test_slice_sequence_axis(ndc, item): sliced_sequence = ndc[item] assert isinstance(sliced_sequence, NDCubeSequence) assert int(sliced_sequence.shape[0]) == tuple_item[0].stop - tuple_item[0].start - assert (sliced_sequence[0].shape == expected_cube0_dims) + assert np.all(sliced_sequence[0].shape == expected_cube0_dims) @pytest.mark.parametrize("ndc, item", @@ -55,7 +54,7 @@ def test_extract_ndcube(ndc, item): tuple_item = item if isinstance(item, tuple) else (item,) expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].shape, tuple_item) assert isinstance(cube, NDCube) - assert (cube.shape == expected_cube_dims) + assert np.all(cube.shape == expected_cube_dims) @pytest.mark.parametrize("ndc, item, expected_common_axis", @@ -97,7 +96,7 @@ def test_index_as_cube(ndc, item, expected_shape): indirect=("ndc",)) def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape): exploded_sequence = ndc.explode_along_axis(axis) - assert exploded_sequence.shape == expected_shape + assert np.all(exploded_sequence.shape == expected_shape) assert exploded_sequence._common_axis is None @@ -124,8 +123,7 @@ def test_explode_along_axis_common_axis_changed(ndc): ), indirect=("ndc",)) def test_shape(ndc, expected_shape): - unit_tester = unittest.TestCase() - unit_tester.assertEqual(ndc.shape, expected_shape) + np.testing.assert_array_equal(ndc.shape, expected_shape) @pytest.mark.parametrize("ndc, expected_shape", @@ -134,7 +132,7 @@ def test_shape(ndc, expected_shape): ), indirect=("ndc",)) def test_cube_like_shape(ndc, expected_shape): - assert (ndc.cube_like_shape == expected_shape) + assert np.all(ndc.cube_like_shape == expected_shape) @pytest.mark.parametrize("ndc", (("ndcubesequence_4c_ln_lt_l",)), indirect=("ndc",)) diff --git a/ndcube/utils/exceptions.py b/ndcube/utils/exceptions.py index ad85d1c92..3e951c692 100644 --- a/ndcube/utils/exceptions.py +++ b/ndcube/utils/exceptions.py @@ -6,14 +6,23 @@ """ import warnings -__all__ = ["NDCubeWarning", "NDCubeDeprecationWarning", "warn_user", "warn_deprecated"] +__all__ = ["NDCubeWarning", "NDCubeUserWarning", "NDCubeDeprecationWarning", "warn_user", "warn_deprecated"] -class NDCubeWarning(UserWarning): +class NDCubeWarning(Warning): """ - A general NDCube warning + The base warning class from which all ndcube warnings should inherit. """ + +class NDCubeUserWarning(UserWarning, NDCubeWarning): + """ + The primary warning class for ndcube. + + Use this if you do not need a specific type of warning. + """ + + class NDCubeDeprecationWarning(FutureWarning, NDCubeWarning): """ A warning class to indicate a deprecated feature. @@ -33,7 +42,7 @@ def warn_user(msg, stacklevel=1): e.g. ``stacklevel=1`` (the default) sets the stack level in the code that calls this function. """ - warnings.warn(msg, NDCubeWarning , stacklevel + 1) + warnings.warn(msg, NDCubeUserWarning , stacklevel + 1) def warn_deprecated(msg, stacklevel=1): From ec430bd0c713fc30c21d998a11ee53c6b7667d8d Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 15:11:54 +0200 Subject: [PATCH 33/35] Final set of changes --- ndcube/tests/test_ndcube.py | 6 +++--- ndcube/utils/collection.py | 16 ++++++---------- ndcube/utils/tests/test_utils_collection.py | 10 ++++------ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 56d14084f..e85a6c156 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -816,9 +816,9 @@ def test_rebin(ndcube_3d_l_ln_lt_ectime): expected_time.format = "fits" # Confirm output is as expected. - assert (output.shape == np.array([1, 2, 8])).all() - assert (output.data == expected_data).all() - assert (output.mask == expected_mask).all() + assert np.all(output.shape == np.array([1, 2, 8])) + assert np.all(output.data == expected_data) + assert np.all(output.mask == expected_mask) assert output.uncertainty == expected_uncertainty assert output.unit == expected_unit assert output.meta == expected_meta diff --git a/ndcube/utils/collection.py b/ndcube/utils/collection.py index fb1d2e91d..452344c8d 100644 --- a/ndcube/utils/collection.py +++ b/ndcube/utils/collection.py @@ -139,29 +139,25 @@ def assert_aligned_axes_compatible(data_dimensions1, data_dimensions2, data_axes Parameters ---------- - data_dimensions1: sequence of ints + data_dimensions1: tuple of ints The dimension lengths of data cube 1. - - data_dimensions2: sequence of ints + data_dimensions2: tuple of ints The dimension lengths of data cube 2. - data_axes1: `tuple` of `int` The aligned axes of data cube 1. - data_axes2: `tuple` of `int` The aligned axes of data cube 2. - """ # If one set of aligned axes is None and the other isn't, they are defined as not compatible. if (data_axes1 is None and data_axes2 is not None) or (data_axes1 is not None and data_axes2 is None): raise ValueError("Both collections must use aligned_axes or both axes must not use aligned_axes." f"Currently {data_axes1} != {data_axes2}") - if data_axes1 is not None and data_axes2 is not None: # aligned_axes are being used for both collections + # Aligned_axes are being used for both collections + if data_axes1 is not None: # Confirm same number of aligned axes. if len(data_axes1) != len(data_axes2): - raise ValueError("Number of aligned axes must be equal: " - f"{len(data_axes1)} != {len(data_axes2)}") + raise ValueError(f"Number of aligned axes must be equal: {len(data_axes1)} != {len(data_axes2)}") # Confirm dimension lengths of each aligned axis is the same. - if not all(data_dimensions1[np.array(data_axes1)] == data_dimensions2[np.array(data_axes2)]): + if not all(np.array(data_dimensions1)[np.array(data_axes1)] == np.array(data_dimensions2)[np.array(data_axes2)]): raise ValueError("All corresponding aligned axes between cubes must be of same length.") diff --git a/ndcube/utils/tests/test_utils_collection.py b/ndcube/utils/tests/test_utils_collection.py index cf4d3ebb5..63e7d7000 100644 --- a/ndcube/utils/tests/test_utils_collection.py +++ b/ndcube/utils/tests/test_utils_collection.py @@ -1,13 +1,11 @@ import pytest -import astropy.units as u - from ndcube.utils import collection as collection_utils @pytest.mark.parametrize("data_dimensions1,data_dimensions2,data_axes1,data_axes2", [ - ([3., 4., 5.]*u.pix, [3., 5., 15.]*u.pix, (0, 2), (0, 1))]) + ([3., 4., 5.], [3., 5., 15.], (0, 2), (0, 1))]) def test_assert_aligned_axes_compatible(data_dimensions1, data_dimensions2, data_axes1, data_axes2): collection_utils.assert_aligned_axes_compatible(data_dimensions1, data_dimensions2, @@ -15,9 +13,9 @@ def test_assert_aligned_axes_compatible(data_dimensions1, data_dimensions2, @pytest.mark.parametrize("data_dimensions1,data_dimensions2,data_axes1,data_axes2", [ - ([3., 4., 5.]*u.pix, [3., 5., 15.]*u.pix, (0, 1), (0, 1)), - ([3., 4., 5.]*u.pix, [3., 5., 15.]*u.pix, (0, 1), (0, 1, 2)), - ([3., 4., 5.]*u.pix, [3., 5., 15.]*u.pix, (0, 1), None)]) + ([3., 4., 5.], [3., 5., 15.], (0, 1), (0, 1)), + ([3., 4., 5.], [3., 5., 15.], (0, 1), (0, 1, 2)), + ([3., 4., 5.], [3., 5., 15.], (0, 1), None)]) def test_assert_aligned_axes_compatible_error(data_dimensions1, data_dimensions2, data_axes1, data_axes2): with pytest.raises(ValueError): From d5eb23cdc7ec842addf93517aa92d81f9c568400 Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 15:27:57 +0200 Subject: [PATCH 34/35] Bugfixes due to review --- docs/reference/utils.rst | 2 -- ndcube/ndcube_sequence.py | 4 ++-- ndcube/tests/test_ndcubesequence.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/reference/utils.rst b/docs/reference/utils.rst index 59fdc5595..39c60d875 100644 --- a/docs/reference/utils.rst +++ b/docs/reference/utils.rst @@ -13,6 +13,4 @@ utils (`ndcube.utils`) .. automodapi:: ndcube.utils.sequence -.. automodapi:: ndcube.utils.wcs_high_level_conversion - .. automodapi:: ndcube.utils.wcs diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 50f8ae747..35d541c7c 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -318,10 +318,10 @@ def crop_by_values(self, *points, units=None, wcses=None): Returns ------- - : `~ndcube.NDCubeSequence` + `~ndcube.NDCubeSequence` The cropped sequence. """ - item = self._get_sequence_crop_item(*points, wcses=wcses, crop_by_values=True, unit=units) + item = self._get_sequence_crop_item(*points, wcses=wcses, crop_by_values=True, units=units) return self[item] def _get_sequence_crop_item(self, *points, wcses=None, crop_by_values=False, units=None): diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index e33ca5d02..dde533c6f 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -180,3 +180,19 @@ def test_crop(ndcubesequence_4c_ln_lt_l): expected = seq[:, 1:3, 0:2, 0:3] output = seq.crop(lower_corner, upper_corner) helpers.assert_cubesequences_equal(output, expected) + + +def test_crop_by_values(ndcubesequence_4c_ln_lt_l): + seq = ndcubesequence_4c_ln_lt_l + intervals = seq[0].wcs.array_index_to_world_values([1, 2], [0, 1], [0, 2]) + units = [u.m, u.deg, u.deg] + lower_corner = [coord[0] * unit for coord, unit in zip(intervals, units)] + upper_corner = [coord[-1] * unit for coord, unit in zip(intervals, units)] + # Ensure some quantities are in units different from each other + # and those stored in the WCS. + lower_corner[0] = lower_corner[0].to(units[0]) + lower_corner[-1] = lower_corner[-1].to(units[-1]) + upper_corner[-1] = upper_corner[-1].to(units[-1]) + expected = seq[:, 1:3, 0:2, 0:3] + output = seq.crop_by_values(lower_corner, upper_corner) + helpers.assert_cubesequences_equal(output, expected) From 8ed4e5f731409476e36ea2e63c4a27259ffc794c Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 24 Apr 2024 16:40:44 +0200 Subject: [PATCH 35/35] Apply suggestions from code review Co-authored-by: DanRyanIrish --- ndcube/tests/test_ndcubesequence.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index dde533c6f..1dee3e096 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -72,12 +72,11 @@ def test_slice_common_axis(ndc, item, expected_common_axis): @pytest.mark.parametrize("ndc, item, expected_shape", ( - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7], (4, 1, 3, 4)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7, 0], (1, 4)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[2:4], (4, 0, 3, 4)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:6], (4, 2, 3, 4)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:6, :, 0], (4, 2, 3)), - ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0::, 0, 0], (2,)) + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 1:7], (3, 2, (2, 3, 1), 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0, 1:7], (3, (2, 3, 1), 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 2:4], (2, 2, 1, 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 0:6], (2, 2, 3, 4)), + ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0, 0:6], (2, 3, 4)), ), indirect=("ndc",)) def test_index_as_cube(ndc, item, expected_shape):