Skip to content

Commit 7e47c76

Browse files
committed
Merge remote-tracking branch 'upstream/master' into nev-22-spikes-above-255
# Conflicts: # neo/test/rawiotest/test_blackrockrawio.py
2 parents 12c2991 + 540abbe commit 7e47c76

22 files changed

Lines changed: 1146 additions & 302 deletions

‎.github/workflows/core-test.yml‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,10 @@ jobs:
2525
fail-fast: true
2626
matrix:
2727
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
28-
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
28+
python-version: ['3.11', '3.12', '3.13', '3.14']
2929
numpy-version: ['1.25.2', '1.26.4', '2.0.2','2.1.3', '2.2.6', '2.3.3', '2.4.4']
3030
# 1.25: 3.11, 1.26: 3.12
3131
exclude:
32-
- python-version: '3.10'
33-
numpy-version: '2.3.3'
34-
- python-version: '3.10'
35-
numpy-version: '2.4.4'
3632
- python-version: '3.12'
3733
numpy-version: '1.25.2'
3834
- python-version: '3.13'
@@ -54,12 +50,12 @@ jobs:
5450

5551
steps:
5652
- name: Set up Python ${{ matrix.python-version }}
57-
uses: actions/setup-python@v5
53+
uses: actions/setup-python@v7
5854
with:
5955
python-version: ${{ matrix.python-version }}
6056

6157
- name: Checkout repository
62-
uses: actions/checkout@v4
58+
uses: actions/checkout@v7
6359

6460
- name: Install numpy ${{ matrix.numpy-version }}
6561
run: |

‎.github/workflows/io-test.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ jobs:
1818
strategy:
1919
fail-fast: true
2020
matrix:
21-
python-version: ['3.10', '3.14']
21+
python-version: ['3.11', '3.14']
2222
numpy-version: ['1.26', '2.3.3']
2323
exclude:
24-
- python-version: '3.10'
24+
- python-version: '3.11'
2525
numpy-version: '2.3.3'
2626
- python-version: '3.14'
2727
numpy-version: '1.26'
@@ -32,7 +32,7 @@ jobs:
3232
steps:
3333

3434
- name: Checkout repository
35-
uses: actions/checkout@v4
35+
uses: actions/checkout@v7
3636

3737
- name: Get current year-month
3838
id: date

‎.github/workflows/publish-to-pypi-test.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ jobs:
1010
runs-on: ubuntu-latest
1111

1212
steps:
13-
- uses: actions/checkout@v4
14-
- name: Set up Python 3.10
15-
uses: actions/setup-python@v4
13+
- uses: actions/checkout@v7
14+
- name: Set up Python 3.14
15+
uses: actions/setup-python@v7
1616
with:
17-
python-version: "3.10"
17+
python-version: "3.14"
1818
- name: Install Tools
1919
run: |
2020
python -m pip install --upgrade pip

‎.github/workflows/publish-to-pypi.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ jobs:
99
runs-on: ubuntu-latest
1010

1111
steps:
12-
- uses: actions/checkout@v4
13-
- name: Set up Python 3.10
14-
uses: actions/setup-python@v4
12+
- uses: actions/checkout@v7
13+
- name: Set up Python 3.14
14+
uses: actions/setup-python@v7
1515
with:
16-
python-version: "3.10"
16+
python-version: "3.14"
1717
- name: Install Tools
1818
run: |
1919
python -m pip install --upgrade pip

‎doc/source/authors.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ and may not be the current affiliation of a contributor.
106106
* Aditya Singh (github)
107107
* Stanley C.
108108
* Axel Coiffard-Donaty
109+
* Anthony Mercer
110+
* Aaron Kanzer [53]
109111

110112
1. Centre de Recherche en Neuroscience de Lyon, CNRS UMR5292 - INSERM U1028 - Université Claude Bernard Lyon 1
111113
2. Unité de Neuroscience, Information et Complexité, CNRS UPR 3293, Gif-sur-Yvette, France
@@ -159,6 +161,7 @@ and may not be the current affiliation of a contributor.
159161
50. Ludwig-Maximilians-Universität München, Munich, Germany
160162
51. Stony Brook University, Stony Brook, NY, USA
161163
52. University of Sussex, Brighton, UK
164+
53. Scigantic, USA
162165

163166

164167

‎neo/core/container.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def _set_object_list(self, name, value):
226226
elif isinstance(value, ObjectList): # from __iadd__
227227
setattr(self, name, value)
228228
else:
229-
TypeError("value must be a list or an ObjectList")
229+
raise TypeError("value must be a list or an ObjectList")
230230

231231
@property
232232
def _child_objects(self):

‎neo/core/objectlist.py‎

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def __init__(self, allowed_contents, parent=None):
2727
self.parent = parent
2828

2929
def _handle_append(self, obj):
30+
self._check_allowed(obj)
31+
if self._contains(obj):
32+
raise ValueError("Cannot add this object because it is already contained within the list")
33+
self._set_parent(obj)
34+
35+
def _check_allowed(self, obj):
3036
if not (
3137
isinstance(obj, self.allowed_contents)
3238
or ( # also allow proxy objects of the correct type
@@ -35,9 +41,7 @@ def _handle_append(self, obj):
3541
):
3642
raise TypeError(f"Object is a {type(obj)}. It should be one of {self.allowed_contents}.")
3743

38-
if self._contains(obj):
39-
raise ValueError("Cannot add this object because it is already contained within the list")
40-
44+
def _set_parent(self, obj):
4145
# set the child-parent relationship
4246
if self.parent:
4347
relationship_name = self.parent.__class__.__name__.lower()
@@ -49,11 +53,8 @@ def _handle_append(self, obj):
4953
setattr(obj, relationship_name, self.parent)
5054

5155
def _contains(self, obj):
52-
if self._items is None:
53-
obj_ids = []
54-
else:
55-
obj_ids = [id(item) for item in self._items]
56-
return id(obj) in obj_ids
56+
# membership is by identity
57+
return self._items is not None and any(item is obj for item in self._items)
5758

5859
def __str__(self):
5960
return str(self._items)
@@ -75,12 +76,10 @@ def __radd__(self, objects):
7576
return objects + self._items
7677

7778
def __contains__(self, key):
78-
return key in self._items
79+
return self._contains(key)
7980

8081
def __iadd__(self, objects):
81-
for obj in objects:
82-
self._handle_append(obj)
83-
self._items.extend(objects)
82+
self.extend(objects)
8483
return self
8584

8685
def __iter__(self):
@@ -93,25 +92,55 @@ def __len__(self):
9392
return len(self._items)
9493

9594
def __setitem__(self, key, value):
96-
self._items[key] = value
95+
if isinstance(key, slice):
96+
values = list(value)
97+
for obj in values:
98+
self._check_allowed(obj)
99+
# assign to a copy first, so that nothing changes if the assignment or the checks fail
100+
new_items = self._items[:]
101+
new_items[key] = values
102+
if len({id(obj) for obj in new_items}) != len(new_items):
103+
raise ValueError("Cannot add this object because it is already contained within the list")
104+
for obj in values:
105+
self._set_parent(obj)
106+
self._items = new_items
107+
else:
108+
if value is self._items[key]:
109+
return
110+
self._handle_append(value)
111+
self._items[key] = value
97112

98113
def append(self, obj):
99114
self._handle_append(obj)
100115
self._items.append(obj)
101116

102117
def extend(self, objects):
118+
# objects may be an iterator that can only be consumed once (e.g., a generator), and we need to iterate twice
119+
objects = list(objects)
120+
# check everything before changing anything, so that a failure leaves the list unchanged.
121+
# Building the set of ids once, rather than calling _contains() for each object, keeps this O(n + m)
122+
seen = {id(item) for item in self._items or ()}
123+
for obj in objects:
124+
self._check_allowed(obj)
125+
if id(obj) in seen:
126+
raise ValueError("Cannot add this object because it is already contained within the list")
127+
seen.add(id(obj))
103128
for obj in objects:
104-
self._handle_append(obj)
129+
self._set_parent(obj)
105130
self._items.extend(objects)
106131

107132
def clear(self):
108133
self._items = []
109134

110135
def count(self, value):
111-
return self._items.count(value)
136+
return int(self._contains(value))
112137

113138
def index(self, value, start=0, stop=sys.maxsize):
114-
return self._items.index(value, start, stop)
139+
items = self._items or []
140+
for i in range(*slice(start, stop).indices(len(items))):
141+
if items[i] is value:
142+
return i
143+
raise ValueError("object is not in list")
115144

116145
def insert(self, index, obj):
117146
self._handle_append(obj)
@@ -121,10 +150,10 @@ def pop(self, index=-1):
121150
return self._items.pop(index)
122151

123152
def remove(self, value):
124-
return self._items.remove(value)
153+
del self._items[self.index(value)]
125154

126155
def reverse(self):
127-
raise self._items.reverse()
156+
self._items.reverse()
128157

129158
def sort(self, *args, key=None, reverse=False):
130159
self._items.sort(*args, key=key, reverse=reverse)

‎neo/core/spiketrainlist.py‎

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,13 @@ def __init__(self, items=None, parent=None):
105105
if items is None:
106106
self._items = items
107107
else:
108+
items = list(items)
108109
for item in items:
109110
if not is_spiketrain_or_proxy(item):
110111
raise ValueError("`items` can only contain SpikeTrain objects or proxy objects")
111-
self._items = list(items)
112+
if len({id(item) for item in items}) != len(items):
113+
raise ValueError("`items` contains the same object more than once")
114+
self._items = items
112115
self._spike_time_array = None
113116
self._channel_id_array = None
114117
self._all_channel_ids = None
@@ -144,7 +147,7 @@ def __getitem__(self, i):
144147
def __setitem__(self, i, value):
145148
if self._items is None:
146149
self._spiketrains_from_array()
147-
self._items[i] = value
150+
super().__setitem__(i, value)
148151

149152
def __str__(self):
150153
"""Return str(self)"""
@@ -183,7 +186,7 @@ def _add_spiketrainlists(self, other, in_place=False):
183186
if other._items is None:
184187
other._spiketrains_from_array()
185188
if in_place:
186-
self._items.extend(other._items)
189+
self.extend(other._items)
187190
return self
188191
else:
189192
return self.__class__(items=self._items[:] + other._items)
@@ -232,11 +235,7 @@ def __iadd__(self, other):
232235
if isinstance(other, self.__class__):
233236
return self._add_spiketrainlists(other, in_place=True)
234237
elif other and is_spiketrain_or_proxy(other[0]):
235-
for obj in other:
236-
self._handle_append(obj)
237-
if self._items is None:
238-
self._spiketrains_from_array()
239-
self._items.extend(other)
238+
self.extend(other)
240239
return self
241240
else:
242241
raise TypeError("Can only add a SpikeTrainList or a list of SpikeTrains in place")
@@ -246,11 +245,7 @@ def __radd__(self, other):
246245
if isinstance(other, self.__class__):
247246
return other._add_spiketrainlists(self)
248247
elif other and is_spiketrain_or_proxy(other[0]):
249-
for obj in other:
250-
obj.segment = self.segment
251-
if self._items is None:
252-
self._spiketrains_from_array()
253-
self._items.extend(other)
248+
self.extend(other)
254249
return self
255250
elif len(other) == 0:
256251
return self
@@ -281,8 +276,7 @@ def append(self, obj):
281276
raise ValueError("Can only append SpikeTrain objects")
282277
if self._items is None:
283278
self._spiketrains_from_array()
284-
self._handle_append(obj)
285-
self._items.append(obj)
279+
super().append(obj)
286280

287281
def extend(self, iterable):
288282
"""Extends the SpikeTrainList with additional SpikeTrain's from an iterable
@@ -306,9 +300,7 @@ def extend(self, iterable):
306300

307301
if self._items is None:
308302
self._spiketrains_from_array()
309-
for obj in iterable:
310-
self._handle_append(obj)
311-
self._items.extend(iterable)
303+
super().extend(iterable)
312304

313305
@classmethod
314306
def from_spike_time_array(

‎neo/rawio/__init__.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* :attr:`AxographRawIO`
1717
* :attr:`AxonaRawIO`
1818
* :attr:`AxonRawIO`
19+
* :attr:`BCI2000RawIO`
1920
* :attr:`BiocamRawIO`
2021
* :attr:`BlackrockRawIO`
2122
* :attr:`BrainVisionRawIO`
@@ -63,6 +64,10 @@
6364
6465
.. autoattribute:: extensions
6566
67+
.. autoclass:: neo.rawio.BCI2000RawIO
68+
69+
.. autoattribute:: extensions
70+
6671
.. autoclass:: neo.rawio.BiocamRawIO
6772
6873
.. autoattribute:: extensions
@@ -188,6 +193,7 @@
188193
from neo.rawio.axographrawio import AxographRawIO
189194
from neo.rawio.axonarawio import AxonaRawIO
190195
from neo.rawio.axonrawio import AxonRawIO
196+
from neo.rawio.bci2000rawio import BCI2000RawIO
191197
from neo.rawio.biocamrawio import BiocamRawIO
192198
from neo.rawio.blackrockrawio import BlackrockRawIO
193199
from neo.rawio.brainvisionrawio import BrainVisionRawIO
@@ -224,6 +230,7 @@
224230
AxographRawIO,
225231
AxonaRawIO,
226232
AxonRawIO,
233+
BCI2000RawIO,
227234
BiocamRawIO,
228235
BlackrockRawIO,
229236
BrainVisionRawIO,

0 commit comments

Comments
 (0)