Skip to content

Commit 03a04f7

Browse files
authored
Merge pull request #1386 from apdavison/matlabio
Improved support for saving/loading Groups in NeoMatlabIO
2 parents c18479b + ac3f8ec commit 03a04f7

9 files changed

+489
-237
lines changed

neo/core/baseneo.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class BaseNeo:
180180
class must have. The tuple can have 2-4 elements.
181181
The first element is the attribute name.
182182
The second element is the attribute type.
183-
The third element is the number of dimensions
183+
The third element is the number of dimensions
184184
(only for numpy arrays and quantities).
185185
The fourth element is the dtype of array
186186
(only for numpy arrays and quantities).
@@ -253,6 +253,8 @@ class attributes. :_recommended_attrs: should append
253253
# Attributes that are used for pretty-printing
254254
_repr_pretty_attrs_keys_ = ("name", "description", "annotations")
255255

256+
is_view = False
257+
256258
def __init__(self, name=None, description=None, file_origin=None,
257259
**annotations):
258260
"""

neo/core/container.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,16 +251,19 @@ def _data_child_containers(self):
251251
"""
252252
Containers for child objects that have data and have a single parent.
253253
"""
254-
return tuple([_container_name(child) for child in
255-
self._data_child_objects])
254+
# the following construction removes the duplicate 'regionsofinterest'
255+
# while preserving the child order (which `set()` would not do)
256+
# I don't know if preserving the order is important, but I'm playing it safe
257+
return tuple({_container_name(child): None for child in
258+
self._data_child_objects}.keys())
256259

257260
@property
258261
def _child_containers(self):
259262
"""
260263
Containers for child objects with a single parent.
261264
"""
262-
return tuple([_container_name(child) for child in
263-
self._child_objects])
265+
return tuple({_container_name(child): None for child in
266+
self._child_objects}.keys())
264267

265268
@property
266269
def _single_children(self):

neo/core/regionofinterest.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ class RegionOfInterest(BaseNeo):
1010
_parent_objects = ('Group',)
1111
_parent_attrs = ('group',)
1212
_necessary_attrs = (
13-
('obj', ('ImageSequence', ), 1),
13+
('image_sequence', ('ImageSequence', ), 1),
1414
)
15+
is_view = True
1516

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

26+
def _get_obj(self):
27+
# for consistency with ChannelView
28+
return self.image_sequence
29+
30+
def _set_obj(self, value):
31+
if not isinstance(value, ImageSequence):
32+
raise TypeError(f"Value must be ImageSequence, not of type: {type(value)}")
33+
self.image_sequence = value
34+
35+
obj = property(fget=_get_obj, fset=_set_obj)
36+
2537
def resolve(self):
2638
"""
2739
Return a signal from within this region of the underlying ImageSequence.
@@ -44,6 +56,13 @@ class CircularRegionOfInterest(RegionOfInterest):
4456
Radius of the ROI in pixels
4557
"""
4658

59+
_necessary_attrs = (
60+
('image_sequence', ('ImageSequence', ), 1),
61+
('x', int),
62+
('y', int),
63+
('radius', int)
64+
)
65+
4766
def __init__(self, image_sequence, x, y, radius, name=None, description=None,
4867
file_origin=None, **annotations):
4968
super().__init__(image_sequence, name, description, file_origin, **annotations)
@@ -94,6 +113,14 @@ class RectangularRegionOfInterest(RegionOfInterest):
94113
Height (y-direction) of the ROI in pixels
95114
"""
96115

116+
_necessary_attrs = (
117+
('image_sequence', ('ImageSequence', ), 1),
118+
('x', int),
119+
('y', int),
120+
('width', int),
121+
('height', int)
122+
)
123+
97124
def __init__(self, image_sequence, x, y, width, height, name=None, description=None,
98125
file_origin=None, **annotations):
99126
super().__init__(image_sequence, name, description, file_origin, **annotations)
@@ -139,6 +166,11 @@ class PolygonRegionOfInterest(RegionOfInterest):
139166
of the vertices of the polygon
140167
"""
141168

169+
_necessary_attrs = (
170+
('image_sequence', ('ImageSequence', ), 1),
171+
('vertices', list),
172+
)
173+
142174
def __init__(self, image_sequence, *vertices, name=None, description=None,
143175
file_origin=None, **annotations):
144176
super().__init__(image_sequence, name, description, file_origin, **annotations)

neo/core/spiketrainlist.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ def __getitem__(self, i):
115115
else:
116116
return SpikeTrainList(items=items)
117117

118+
def __setitem__(self, i, value):
119+
if self._items is None:
120+
self._spiketrains_from_array()
121+
self._items[i] = value
122+
118123
def __str__(self):
119124
"""Return str(self)"""
120125
if self._items is None:

neo/core/view.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ class ChannelView(BaseNeo):
3030
Note: Any other additional arguments are assumed to be user-specific
3131
metadata and stored in :attr:`annotations`.
3232
"""
33-
_parent_objects = ('Segment',)
34-
_parent_attrs = ('segment',)
33+
_parent_objects = ('Group',)
34+
_parent_attrs = ('group',)
3535
_necessary_attrs = (
36+
('obj', ('AnalogSignal', 'IrregularlySampledSignal'), 1),
3637
('index', np.ndarray, 1, np.dtype('i')),
37-
('obj', ('AnalogSignal', 'IrregularlySampledSignal'), 1)
3838
)
39+
is_view = True
40+
3941
# "mask" would be an alternative name, proposing "index" for
4042
# backwards-compatibility with ChannelIndex
4143

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

7577
def _get_arr_ann_length(self):
76-
return self.shape[-1]
78+
return self.index.size
7779

7880
def array_annotate(self, **array_annotations):
7981
self.array_annotations.update(array_annotations)

0 commit comments

Comments
 (0)