Skip to content

Improved support for saving/loading Groups in NeoMatlabIO #1386

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

Merged
merged 13 commits into from
Feb 2, 2024
4 changes: 3 additions & 1 deletion neo/core/baseneo.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class BaseNeo:
class must have. The tuple can have 2-4 elements.
The first element is the attribute name.
The second element is the attribute type.
The third element is the number of dimensions
The third element is the number of dimensions
(only for numpy arrays and quantities).
The fourth element is the dtype of array
(only for numpy arrays and quantities).
Expand Down Expand Up @@ -253,6 +253,8 @@ class attributes. :_recommended_attrs: should append
# Attributes that are used for pretty-printing
_repr_pretty_attrs_keys_ = ("name", "description", "annotations")

is_view = False

def __init__(self, name=None, description=None, file_origin=None,
**annotations):
"""
Expand Down
11 changes: 7 additions & 4 deletions neo/core/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,19 @@ def _data_child_containers(self):
"""
Containers for child objects that have data and have a single parent.
"""
return tuple([_container_name(child) for child in
self._data_child_objects])
# the following construction removes the duplicate 'regionsofinterest'
# while preserving the child order (which `set()` would not do)
# I don't know if preserving the order is important, but I'm playing it safe
return tuple({_container_name(child): None for child in
self._data_child_objects}.keys())

@property
def _child_containers(self):
"""
Containers for child objects with a single parent.
"""
return tuple([_container_name(child) for child in
self._child_objects])
return tuple({_container_name(child): None for child in
self._child_objects}.keys())

@property
def _single_children(self):
Expand Down
34 changes: 33 additions & 1 deletion neo/core/regionofinterest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class RegionOfInterest(BaseNeo):
_parent_objects = ('Group',)
_parent_attrs = ('group',)
_necessary_attrs = (
('obj', ('ImageSequence', ), 1),
('image_sequence', ('ImageSequence', ), 1),
)
is_view = True

def __init__(self, image_sequence, name=None, description=None, file_origin=None, **annotations):
super().__init__(name=name, description=description,
Expand All @@ -22,6 +23,17 @@ def __init__(self, image_sequence, name=None, description=None, file_origin=None
raise ValueError("Can only take a RegionOfInterest of an ImageSequence")
self.image_sequence = image_sequence

def _get_obj(self):
# for consistency with ChannelView
return self.image_sequence

def _set_obj(self, value):
if not isinstance(value, ImageSequence):
raise TypeError(f"Value must be ImageSequence, not of type: {type(value)}")
self.image_sequence = value

obj = property(fget=_get_obj, fset=_set_obj)

def resolve(self):
"""
Return a signal from within this region of the underlying ImageSequence.
Expand All @@ -44,6 +56,13 @@ class CircularRegionOfInterest(RegionOfInterest):
Radius of the ROI in pixels
"""

_necessary_attrs = (
('image_sequence', ('ImageSequence', ), 1),
('x', int),
('y', int),
('radius', int)
)

def __init__(self, image_sequence, x, y, radius, name=None, description=None,
file_origin=None, **annotations):
super().__init__(image_sequence, name, description, file_origin, **annotations)
Expand Down Expand Up @@ -94,6 +113,14 @@ class RectangularRegionOfInterest(RegionOfInterest):
Height (y-direction) of the ROI in pixels
"""

_necessary_attrs = (
('image_sequence', ('ImageSequence', ), 1),
('x', int),
('y', int),
('width', int),
('height', int)
)

def __init__(self, image_sequence, x, y, width, height, name=None, description=None,
file_origin=None, **annotations):
super().__init__(image_sequence, name, description, file_origin, **annotations)
Expand Down Expand Up @@ -139,6 +166,11 @@ class PolygonRegionOfInterest(RegionOfInterest):
of the vertices of the polygon
"""

_necessary_attrs = (
('image_sequence', ('ImageSequence', ), 1),
('vertices', list),
)

def __init__(self, image_sequence, *vertices, name=None, description=None,
file_origin=None, **annotations):
super().__init__(image_sequence, name, description, file_origin, **annotations)
Expand Down
5 changes: 5 additions & 0 deletions neo/core/spiketrainlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ def __getitem__(self, i):
else:
return SpikeTrainList(items=items)

def __setitem__(self, i, value):
if self._items is None:
self._spiketrains_from_array()
self._items[i] = value

def __str__(self):
"""Return str(self)"""
if self._items is None:
Expand Down
10 changes: 6 additions & 4 deletions neo/core/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ class ChannelView(BaseNeo):
Note: Any other additional arguments are assumed to be user-specific
metadata and stored in :attr:`annotations`.
"""
_parent_objects = ('Segment',)
_parent_attrs = ('segment',)
_parent_objects = ('Group',)
_parent_attrs = ('group',)
_necessary_attrs = (
('obj', ('AnalogSignal', 'IrregularlySampledSignal'), 1),
('index', np.ndarray, 1, np.dtype('i')),
('obj', ('AnalogSignal', 'IrregularlySampledSignal'), 1)
)
is_view = True

# "mask" would be an alternative name, proposing "index" for
# backwards-compatibility with ChannelIndex

Expand Down Expand Up @@ -73,7 +75,7 @@ def shape(self):
return (self.obj.shape[0], self.index.size)

def _get_arr_ann_length(self):
return self.shape[-1]
return self.index.size

def array_annotate(self, **array_annotations):
self.array_annotations.update(array_annotations)
Expand Down
Loading