Skip to content

Commit

Permalink
Merge pull request #1386 from apdavison/matlabio
Browse files Browse the repository at this point in the history
Improved support for saving/loading Groups in NeoMatlabIO
  • Loading branch information
alejoe91 authored Feb 2, 2024
2 parents c18479b + ac3f8ec commit 03a04f7
Show file tree
Hide file tree
Showing 9 changed files with 489 additions and 237 deletions.
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

0 comments on commit 03a04f7

Please sign in to comment.