Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sliceable MetaData Class #455

Open
wants to merge 90 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
ff9b13e
First draft of a sliceable meta obect.
DanRyanIrish Aug 13, 2021
6d0d254
Refactor Meta. Tests now passing.
DanRyanIrish Aug 14, 2021
9166378
Add overwrite option to Meta.add.
DanRyanIrish Aug 14, 2021
a2248c7
Fixes codestyle.
DanRyanIrish Aug 14, 2021
02cdcbb
Adds #455 changelog.
DanRyanIrish Aug 14, 2021
eb57c05
Codestyle fixes.
DanRyanIrish Aug 14, 2021
96b8ab9
Slice meta if possible when slicing NDCube.
DanRyanIrish Aug 14, 2021
303320a
Add docstrings to Meta object.
DanRyanIrish Aug 14, 2021
3cc208d
Create remove method for Meta.
DanRyanIrish Aug 16, 2021
2b5d507
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Aug 21, 2021
041cd1b
Make Meta more memory efficient.
DanRyanIrish Aug 23, 2021
3bdf4a6
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Aug 25, 2021
cd13518
Add Meta to top-level init imports.
DanRyanIrish Aug 25, 2021
ad88f5f
Enable Meta object's shape to be set without axes being set.
DanRyanIrish Aug 25, 2021
873d5b7
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Oct 15, 2021
1d54ba3
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 3, 2021
f8c59ed
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 4, 2021
4611e1e
Avoid possible indexing error when dropping axes during slicing of Me…
DanRyanIrish Nov 4, 2021
048e0ed
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 10, 2021
aa817fd
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 18, 2021
cf923f9
Make NDCube slice meta if it has a magic attribute.
DanRyanIrish Nov 18, 2021
b0e21be
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Aug 26, 2022
7df22e7
Merge branch 'main' into meta
DanRyanIrish May 7, 2024
bbe6c12
Fixes codestyle.
DanRyanIrish May 7, 2024
86dc366
Reverting unintented changes to __init__.py
DanRyanIrish May 7, 2024
7160dc3
Apply suggestions from code review
DanRyanIrish May 8, 2024
b3d439c
Adds nitpicky exceptions for Meta docstring.
DanRyanIrish May 8, 2024
1f04d62
Try fixing hyperlink to "iterable"
DanRyanIrish May 8, 2024
c27d904
Apply suggestions from code review
nabobalis May 8, 2024
8291189
Apply suggestions from code review
nabobalis May 8, 2024
5604de0
Apply suggestions from code review
nabobalis May 8, 2024
cdece82
Apply suggestions from code review
nabobalis May 8, 2024
51617e6
Update ndcube/tests/test_meta.py
nabobalis May 8, 2024
72bccb0
Change denotation of metadata not associated with axes in Meta class …
DanRyanIrish May 9, 2024
120596d
Make NDCube.rebin remove axis-awareness from metadata associated with…
DanRyanIrish May 9, 2024
f3fc5a2
Merge branch 'meta' of https://github.com/DanRyanIrish/ndcube into meta
DanRyanIrish May 9, 2024
ad39c81
Fix codestyle
DanRyanIrish May 9, 2024
ad0e532
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish May 9, 2024
d01f6f6
Add test of meta handling by NDCube.rebin.
DanRyanIrish May 10, 2024
d5ae2ef
Add rebin capability to Meta.
DanRyanIrish May 13, 2024
181d86d
Fix codestyle.
DanRyanIrish May 13, 2024
f17ba0b
Adds tests for Meta.rebin.
DanRyanIrish May 13, 2024
da8df25
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish May 19, 2024
fb45151
Support scalar axis-aware metadata.
DanRyanIrish May 19, 2024
9726b65
Add support for axis-aware metadata that give a scalar per axis.
DanRyanIrish May 19, 2024
a626602
Clarify behavior of axis-aligned and grid-aligned metadata and fix co…
DanRyanIrish May 20, 2024
e848e9f
Make API of Meta.rebin more flexible.
DanRyanIrish May 20, 2024
500843f
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Jul 2, 2024
754385e
change name for metadata used by Meta class from header to meta
DanRyanIrish Jul 2, 2024
e8f9535
Update ndcube/meta.py
DanRyanIrish Jul 2, 2024
fef0de6
Rename Meta to NDMeta.
DanRyanIrish Jul 2, 2024
28eafbb
Move ndcube_can_slice and ndcube_can_rebin to class attributes of NDM…
DanRyanIrish Jul 2, 2024
40b8b26
Create NDMetaABC.
DanRyanIrish Jul 2, 2024
3ba2d9c
Move slicing by data item in NDMeta to new slice method.
DanRyanIrish Jul 2, 2024
f723445
Change NDMeta.shape to data_shape.
DanRyanIrish Jul 2, 2024
6d84e56
Make NDMeta infer data_shape if not provided.
DanRyanIrish Jul 2, 2024
bf078bd
Refactor NDMeta.rebin to take bin_shape as input.
DanRyanIrish Jul 2, 2024
81b84e5
Remove data_shape kwarg from NDMeta init.
DanRyanIrish Jul 2, 2024
b526e3e
Fix codestyle.
DanRyanIrish Jul 2, 2024
cdc3f8f
Add setter for NDMeta.data_shape.
DanRyanIrish Jul 2, 2024
2373209
Replace NDMeta.remove with __delitem__.
DanRyanIrish Jul 2, 2024
6d38c67
Make NDCube.meta have same shape as NDCube if meta is axis-aware.
DanRyanIrish Jul 2, 2024
fb788f7
Fix codestyle.
DanRyanIrish Jul 3, 2024
f315ca6
Apply suggestions from code review
DanRyanIrish Jul 4, 2024
d81db9e
Change NDMeta.comments to key_comments.
DanRyanIrish Jul 4, 2024
b59cbff
Merge branch 'meta' of https://github.com/DanRyanIrish/ndcube into meta
DanRyanIrish Jul 4, 2024
5776dca
Make NDMeta.original_header read-only.
DanRyanIrish Jul 4, 2024
5076384
First incomplete version of docs on NDMeta.
DanRyanIrish Jul 8, 2024
bebcf1f
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Jul 13, 2024
8127514
First complete draft of metadata narrative docs.
DanRyanIrish Jul 14, 2024
c4a83aa
Enable NDMeta to accept data shape on instantiation.
DanRyanIrish Jul 14, 2024
d79a5e6
Enable NDCubeSequence and NDCollection to support sliceable metadata.
DanRyanIrish Jul 14, 2024
7f9f042
Add tests for meta slicing via NDCubeSequence and NDCollection.
DanRyanIrish Jul 16, 2024
e35c34c
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Jul 20, 2024
a58c705
Fixes to make NDMeta slicing work, especially as part of an NDCollect…
DanRyanIrish Jul 20, 2024
944b0df
Some docs bugfixes relating to NDMeta.
DanRyanIrish Jul 21, 2024
8f9816a
Some old changes that werent committed.
DanRyanIrish Nov 6, 2024
e0b9dbe
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 6, 2024
e4bdf03
Fixes codestyle.
DanRyanIrish Nov 6, 2024
ccf35dc
Fix bug when adding axis-aligned metadata.
DanRyanIrish Nov 6, 2024
143afa1
Fix bug in handling slicing meta through NDCube slicing.
DanRyanIrish Nov 6, 2024
aab969b
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 6, 2024
d1b4b47
Some doc build fixes.
DanRyanIrish Nov 6, 2024
1575a22
More doc fixes.
DanRyanIrish Nov 6, 2024
1845572
More doc fixes.
DanRyanIrish Nov 6, 2024
195fe19
More doc fixes.
DanRyanIrish Nov 6, 2024
672007e
Fix 455 changelog entry.
DanRyanIrish Nov 6, 2024
baab449
Replace bare exception with except Exception.
DanRyanIrish Nov 6, 2024
39d675d
Merge branch 'main' of https://github.com/sunpy/ndcube into meta
DanRyanIrish Nov 6, 2024
5c0e2cb
Merge branch 'main' of github.com:sunpy/ndcube into meta
Cadair Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/455.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Sliceable `~ndcube.meta.Meta` class for axis-associated metadata.
2 changes: 2 additions & 0 deletions docs/nitpick-exceptions
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ py:class a set-like object providing a view on D's keys
py:class an object providing a view on D's values
py:class None. Remove all items from D.
py:class a shallow copy of D
py:class v, remove specified key and return the corresponding value.
py:class None. Update D from dict/iterable E and F.
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion ndcube/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
"""
from .extra_coords.extra_coords import ExtraCoords, ExtraCoordsABC
from .global_coords import GlobalCoords, GlobalCoordsABC
from .meta import Meta
from .ndcollection import NDCollection
from .ndcube import NDCube, NDCubeBase
from .ndcube_sequence import NDCubeSequence, NDCubeSequenceBase
from .version import version as __version__

__all__ = ['NDCube', 'NDCubeSequence', "NDCollection", "ExtraCoords", "GlobalCoords", "ExtraCoordsABC", "GlobalCoordsABC", "NDCubeBase", "NDCubeSequenceBase", "__version__"]
__all__ = ['NDCube', 'NDCubeSequence', "Meta", "NDCollection", "ExtraCoords", "GlobalCoords", "ExtraCoordsABC", "GlobalCoordsABC", "NDCubeBase", "NDCubeSequenceBase", "__version__"]
20 changes: 19 additions & 1 deletion ndcube/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from astropy.time import Time, TimeDelta
from astropy.wcs import WCS

from ndcube import ExtraCoords, GlobalCoords, NDCube, NDCubeSequence
from ndcube import ExtraCoords, GlobalCoords, Meta, NDCube, NDCubeSequence

# Force MPL to use non-gui backends for testing.
try:
Expand Down Expand Up @@ -334,6 +334,24 @@ def ndcube_4d_ln_lt_l_t(wcs_4d_t_l_lt_ln):
return NDCube(data_cube, wcs=wcs_4d_t_l_lt_ln)


@pytest.fixture
def ndcube_4d_axis_aware_meta(wcs_4d_t_l_lt_ln):
shape = (5, 8, 10, 12)
wcs_4d_t_l_lt_ln.array_shape = shape
data_cube = data_nd(shape, dtype=int)
meta = Meta({"a": "scalar",
"slit position": np.arange(shape[0], dtype=int),
"pixel label": np.arange(np.prod(shape[:2])).reshape(shape[:2]),
"line": ["Si IV"] * shape[2],
"exposure time": ([2] * shape[-1]) * u.s},
data_shape=shape,
axes={"slit position": 0,
"pixel label": (0, 1),
"line": (2,),
"exposure time": 3})
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
return NDCube(data_cube, wcs=wcs_4d_t_l_lt_ln, meta=meta)


@pytest.fixture
def ndcube_4d_uncertainty(wcs_4d_t_l_lt_ln):
shape = (5, 8, 10, 12)
Expand Down
281 changes: 281 additions & 0 deletions ndcube/meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import copy
import numbers
import collections.abc

import numpy as np

__all__ = ["Meta"]


class Meta(dict):
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
"""
A sliceable object for storing metadata.

Metadata can be linked to a data axis which causes it to be sliced when the
standard Python numeric slicing API is applied to the object.
Specific pieces of metadata can be obtain using the dict-like string slicing API.
Metadata associated with an axis/axes must have the same length/shape as those axes.

Parameters
----------
header: dict-like
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
The names and values of metadata.

nabobalis marked this conversation as resolved.
Show resolved Hide resolved
comments: dict-like, optional
Comments associated with any of the above pieces of metadata.

axes: dict-like, optional
The axis/axes associated with the metadata denoted by the keys.
Metadata not included are considered not to be associated with any axis.
Each axis value must be an iterable of `int`. An `int` itself is also
acceptable if the metadata is associated with a single axis. An empty
iterable also means the metadata is not associated with any axes.

data_shape: iterator of `int`, optional
The shape of the data with which this metadata is associated.
Must be set if axes input is set.
"""
def __init__(self, header=None, comments=None, axes=None, data_shape=None):
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
self.__ndcube_can_slice__ = True
self.__ndcube_can_rebin__ = True
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
self.original_header = header

Check warning on line 41 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L39-L41

Added lines #L39 - L41 were not covered by tests
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used header as the nomenclature with sunpy.map.Map in mind. But this tends to imply a FITS header, which is over-specific. Perhaps this should be changed to meta and original_meta. Thoughts @ayshih @Cadair?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favour of meta rather than header

DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved

if header is None:
header = {}

Check warning on line 44 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L43-L44

Added lines #L43 - L44 were not covered by tests
nabobalis marked this conversation as resolved.
Show resolved Hide resolved
else:
header = dict(header)
super().__init__(header.items())
header_keys = header.keys()

Check warning on line 48 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L46-L48

Added lines #L46 - L48 were not covered by tests
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved

if comments is None:
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
self._comments = dict()

Check warning on line 51 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L50-L51

Added lines #L50 - L51 were not covered by tests
else:
comments = dict(comments)
if not set(comments.keys()).issubset(set(header_keys)):
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(

Check warning on line 55 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L53-L55

Added lines #L53 - L55 were not covered by tests
"All comments must correspond to a value in header under the same key.")
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
self._comments = comments

Check warning on line 57 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L57

Added line #L57 was not covered by tests

if data_shape is None:
self._data_shape = data_shape

Check warning on line 60 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L59-L60

Added lines #L59 - L60 were not covered by tests
else:
self._data_shape = np.asarray(data_shape, dtype=int)

Check warning on line 62 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L62

Added line #L62 was not covered by tests

if axes is None:
self._axes = dict()

Check warning on line 65 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L64-L65

Added lines #L64 - L65 were not covered by tests
else:
if not (isinstance(data_shape, collections.abc.Iterable) and

Check warning on line 67 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L67

Added line #L67 was not covered by tests
all([isinstance(i, numbers.Integral) for i in data_shape])):
raise TypeError("If axes is set, data_shape must be an iterable giving "

Check warning on line 69 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L69

Added line #L69 was not covered by tests
"the length of each axis of the associated cube.")
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
axes = dict(axes)
if not set(axes.keys()).issubset(set(header_keys)):
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(

Check warning on line 73 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L71-L73

Added lines #L71 - L73 were not covered by tests
"All axes must correspond to a value in header under the same key.")
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
self._axes = dict([(key, self._sanitize_axis_value(axis, header[key], key))

Check warning on line 75 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L75

Added line #L75 was not covered by tests
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
for key, axis in axes.items()])

def _sanitize_axis_value(self, axis, value, key):
if isinstance(axis, numbers.Integral):
axis = (axis,)
if len(axis) == 0:
return tuple()
if self.shape is None:
raise TypeError("Meta instance does not have a shape so new metadata "

Check warning on line 84 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L79-L84

Added lines #L79 - L84 were not covered by tests
"cannot be assigned to an axis.")
# Verify each entry in axes is an iterable of ints.
nabobalis marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(axis, numbers.Integral):
axis = (axis,)
if not (isinstance(axis, collections.abc.Iterable) and all([isinstance(i, numbers.Integral)

Check warning on line 89 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L87-L89

Added lines #L87 - L89 were not covered by tests
for i in axis])):
raise TypeError("Values in axes must be an integer or iterable of integers giving "

Check warning on line 91 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L91

Added line #L91 was not covered by tests
"the data axis/axes associated with the metadata.")
axis = np.asarray(axis)

Check warning on line 93 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L93

Added line #L93 was not covered by tests

shape_error_msg = (f"{key} must have shape {tuple(self.shape[axis])} "

Check warning on line 95 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L95

Added line #L95 was not covered by tests
f"as it is associated with axes {axis}")
if len(axis) == 1:
if not hasattr(value, "__len__"):
raise TypeError(shape_error_msg)
meta_shape = (len(value),)

Check warning on line 100 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L97-L100

Added lines #L97 - L100 were not covered by tests
else:
if not hasattr(value, "shape"):
raise TypeError(shape_error_msg)
meta_shape = value.shape
if not all(meta_shape == self.shape[axis]):
raise ValueError(shape_error_msg)

Check warning on line 106 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L102-L106

Added lines #L102 - L106 were not covered by tests

return axis

Check warning on line 108 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L108

Added line #L108 was not covered by tests

@property
def comments(self):
return self._comments

Check warning on line 112 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L112

Added line #L112 was not covered by tests

@property
def axes(self):
return self._axes

Check warning on line 116 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L116

Added line #L116 was not covered by tests

@property
def shape(self):
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
return self._data_shape

Check warning on line 120 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L120

Added line #L120 was not covered by tests

def add(self, name, value, comment=None, axis=None, overwrite=False):
"""Add a new piece of metadata to instance.
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
name: `str`
The name/label of the metadata.

value: Any
The value of the metadata. If axes input is not None, this must have the
same length/shape as those axes as defined by ``self.shape``.

comment: `str` or `None`
Any comment associated with this metadata. Set to None if no comment desired.

axis: `int`, iterable of `int`, or `None`
The axis/axes with which the metadata is linked. If not associated with any
axis, set this to None.

overwrite: `bool`, optional
If True, overwrites the entry of the name name if already present.
"""
if name in self.keys() and overwrite is not True:
raise KeyError(f"'{name}' already exists. "

Check warning on line 145 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L144-L145

Added lines #L144 - L145 were not covered by tests
"To update an existing metadata entry set overwrite=True.")
if comment is not None:
self._comments[name] = comment
if axis is not None:
axis = self._sanitize_axis_value(axis, value, name)
self._axes[name] = axis
elif name in self._axes:
del self._axes[name]

Check warning on line 153 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L147-L153

Added lines #L147 - L153 were not covered by tests
# This must be done after updating self._axes otherwise it may error.
self.__setitem__(name, value)

Check warning on line 155 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L155

Added line #L155 was not covered by tests

def remove(self, name):
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
if name in self._comments:
del self._comments[name]
if name in self._axes:
del self._axes[name]
del self[name]

Check warning on line 162 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L158-L162

Added lines #L158 - L162 were not covered by tests

def __setitem__(self, key, val):
axis = self.axes.get(key, None)
if axis is not None:
recommendation = "We recommend using the 'add' method to set values."
if len(axis) == 1:
if not (hasattr(val, "__len__") and len(val) == self.shape[axis[0]]):
raise TypeError(f"{key} must have same length as associated axis, "

Check warning on line 170 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L165-L170

Added lines #L165 - L170 were not covered by tests
f"i.e. axis {axis[0]}: {self.shape[axis[0]]}\n"
f"{recommendation}")
else:
if not (hasattr(val, "shape") and all(val.shape == self.shape[axis])):
raise TypeError(f"{key} must have same shape as associated axes, "

Check warning on line 175 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L174-L175

Added lines #L174 - L175 were not covered by tests
f"i.e axes {axis}: {self.shape[axis]}\n"
f"{recommendation}")
super().__setitem__(key, val)

Check warning on line 178 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L178

Added line #L178 was not covered by tests

def __getitem__(self, item):
# There are two ways to slice:
# by key, or
# by typical python numeric slicing API,
# i.e. slice the each piece of metadata associated with an axes.

if isinstance(item, str):
return super().__getitem__(item)

Check warning on line 187 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L186-L187

Added lines #L186 - L187 were not covered by tests
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved

elif self.shape is None:
raise TypeError("Meta object does not have a shape and so cannot be sliced.")

Check warning on line 190 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L189-L190

Added lines #L189 - L190 were not covered by tests

else:
new_meta = copy.deepcopy(self)
if isinstance(item, (numbers.Integral, slice)):
item = [item]
item = np.array(list(item) + [slice(None)] * (len(self.shape) - len(item)),

Check warning on line 196 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L193-L196

Added lines #L193 - L196 were not covered by tests
dtype=object)

# Edit data shape and calculate which axis will be dropped.
dropped_axes = np.zeros(len(self.shape), dtype=bool)
new_shape = new_meta.shape
for i, axis_item in enumerate(item):
if isinstance(axis_item, numbers.Integral):
dropped_axes[i] = True
elif isinstance(axis_item, slice):
start = axis_item.start
if start is None:
start = 0
if start < 0:
start = self.shape[i] - start
stop = axis_item.stop
if stop is None:
stop = self.shape[i]
if stop < 0:
stop = self.shape[i] - stop
new_shape[i] = stop - start

Check warning on line 216 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L200-L216

Added lines #L200 - L216 were not covered by tests
else:
raise TypeError("Unrecognized slice type. "

Check warning on line 218 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L218

Added line #L218 was not covered by tests
"Must be an int, slice and tuple of the same.")
new_meta._data_shape = new_shape[np.invert(dropped_axes)]

Check warning on line 220 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L220

Added line #L220 was not covered by tests

cumul_dropped_axes = np.cumsum(dropped_axes)

Check warning on line 222 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L222

Added line #L222 was not covered by tests

# Slice all metadata associated with axes.
for key, value in self.items():
axis = self.axes.get(key, None)
if axis is not None:
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
new_item = tuple(item[axis])
if len(new_item) == 1:
new_value = value[new_item[0]]

Check warning on line 230 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L225-L230

Added lines #L225 - L230 were not covered by tests
else:
new_value = value[new_item]
new_axis = np.array([-1 if isinstance(i, numbers.Integral) else a

Check warning on line 233 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L232-L233

Added lines #L232 - L233 were not covered by tests
for i, a in zip(new_item, axis)])
new_axis -= cumul_dropped_axes[axis]
new_axis = new_axis[new_axis >= 0]
if len(new_axis) == 0:
new_axis = None
new_meta.add(key, new_value, self.comments.get(key, None), new_axis,

Check warning on line 239 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L235-L239

Added lines #L235 - L239 were not covered by tests
overwrite=True)

return new_meta

Check warning on line 242 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L242

Added line #L242 was not covered by tests

def rebin(self, bin_shape):
"""
Adjusts axis-aware metadata to stay consistent with a rebinned `~ndcube.NDCube`.

This is done by simply removing the axis-awareness of metadata associated with
rebinned axes. The metadata itself is not changed or removed. This operation
does not remove axis-awareness from metadata only associated with non-rebinned
axes, i.e. axes whose corresponding entries in ``bin_shape`` are 1.

Parameters
----------
bin_shape: `tuple` or `int`
The new lengths of each axis of the associated data.
"""
# Sanitize input.
data_shape = self.shape
if len(bin_shape) != len(data_shape):
raise ValueError(f"bin_shape must be same length as data shape: "

Check warning on line 261 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L259-L261

Added lines #L259 - L261 were not covered by tests
f"{len(bin_shape)} != {len(self.shape)}")
if not all([isinstance(dim, numbers.Integral) for dim in bin_shape]):
raise TypeError("bin_shape must contain only integer types.")

Check warning on line 264 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L263-L264

Added lines #L263 - L264 were not covered by tests
# Convert bin_shape to array. Do this after checking types of elements to avoid
# floats being incorrectly rounded down.
bin_shape = np.asarray(bin_shape, dtype=int)
if any(data_shape % bin_shape):
raise ValueError(

Check warning on line 269 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L267-L269

Added lines #L267 - L269 were not covered by tests
"All elements in bin_shape must be a factor of corresponding element"
f" of data shape: data_shape mod bin_shape = {self.shape % bin_shape}")
# Remove axis-awareness from metadata associated with rebinned axes.
rebinned_axes = set(np.where(bin_shape != 1)[0])
new_meta = copy.deepcopy(self)
null_set = set()
for name, axes in self.axes.items():
if set(axes).intersection(rebinned_axes) != null_set:
del new_meta._axes[name]

Check warning on line 278 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L273-L278

Added lines #L273 - L278 were not covered by tests
# Update data shape.
new_meta._data_shape = (data_shape / bin_shape).astype(int)
return new_meta

Check warning on line 281 in ndcube/meta.py

View check run for this annotation

Codecov / codecov/patch

ndcube/meta.py#L280-L281

Added lines #L280 - L281 were not covered by tests
4 changes: 4 additions & 0 deletions ndcube/mixins/ndslicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@
sliced_cube._global_coords._internal_coords = self.global_coords._internal_coords
sliced_cube._extra_coords = self.extra_coords[item]

# Slice metadata if possible.
if hasattr(self.meta, "__ndcube_can_slice__") and self.meta.__ndcube_can_slice__:
sliced_cube.meta = self.meta[item]

Check warning on line 30 in ndcube/mixins/ndslicing.py

View check run for this annotation

Codecov / codecov/patch

ndcube/mixins/ndslicing.py#L30

Added line #L30 was not covered by tests

return sliced_cube
8 changes: 7 additions & 1 deletion ndcube/ndcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,13 +1207,19 @@
# Resample WCS
new_wcs = ResampledLowLevelWCS(self.wcs.low_level_wcs, bin_shape[::-1])

# If meta is axis-aware, drop axis-awareness for metadata associated with rebinned axes.
if hasattr(self.meta, "__ndcube_can_rebin__") and self.meta.__ndcube_can_rebin__:
new_meta = self.meta.rebin(bin_shape)

Check warning on line 1212 in ndcube/ndcube.py

View check run for this annotation

Codecov / codecov/patch

ndcube/ndcube.py#L1211-L1212

Added lines #L1211 - L1212 were not covered by tests
else:
new_meta = self.meta

Check warning on line 1214 in ndcube/ndcube.py

View check run for this annotation

Codecov / codecov/patch

ndcube/ndcube.py#L1214

Added line #L1214 was not covered by tests
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved

# Reform NDCube.
new_cube = type(self)(
data=new_data,
wcs=new_wcs,
uncertainty=new_uncertainty,
mask=new_mask,
meta=self.meta,
meta=new_meta,
unit=new_unit
)
new_cube._global_coords = self._global_coords
Expand Down
Loading
Loading