Skip to content

Commit 19b76e3

Browse files
authored
Merge branch 'master' into fix_openephys_stream
2 parents e11e12c + b1f227f commit 19b76e3

File tree

14 files changed

+130
-117
lines changed

14 files changed

+130
-117
lines changed

codemeta.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
"license": "https://spdx.org/licenses/BSD-3-Clause",
55
"codeRepository": "https://github.com/NeuralEnsemble/python-neo",
66
"contIntegration": "https://github.com/NeuralEnsemble/python-neo/actions",
7-
"dateModified": "2024-10-14",
8-
"downloadUrl": "https://files.pythonhosted.org/packages/e0/0d/e973b7e8464b6f1d88022c46040f203d93c0b080af0e33702bb11873dbbb/neo-0.13.4.tar.gz",
7+
"dateModified": "2025-01-20",
8+
"downloadUrl": "https://files.pythonhosted.org/packages/3b/91/c3630d766b3b959f8e604d8d41580c78973ece5f1e070b13bd9755dba60b/neo-0.14.0.tar.gz",
99
"issueTracker": "https://github.com/NeuralEnsemble/python-neo/issues",
1010
"name": "Neo",
11-
"version": "0.13.4",
11+
"version": "0.14.0",
1212
"identifier": "RRID:SCR_000634",
1313
"description": "Neo is a Python package for working with electrophysiology data in Python, together with support for reading a wide range of neurophysiology file formats, including Spike2, NeuroExplorer, AlphaOmega, Axon, Blackrock, Plexon, Tdt, and support for writing to a subset of these formats plus non-proprietary formats including HDF5.\n\nThe goal of Neo is to improve interoperability between Python tools for analyzing, visualizing and generating electrophysiology data by providing a common, shared object model. In order to be as lightweight a dependency as possible, Neo is deliberately limited to represention of data, with no functions for data analysis or visualization.\n\nNeo is used by a number of other software tools, including SpykeViewer (data analysis and visualization), Elephant (data analysis), the G-node suite (databasing), PyNN (simulations), tridesclous_ (spike sorting) and ephyviewer (data visualization).\n\nNeo implements a hierarchical data model well adapted to intracellular and extracellular electrophysiology and EEG data with support for multi-electrodes (for example tetrodes). Neo's data objects build on the quantities package, which in turn builds on NumPy by adding support for physical dimensions. Thus Neo objects behave just like normal NumPy arrays, but with additional metadata, checks for dimensional consistency and automatic unit conversion.",
1414
"applicationCategory": "neuroscience",
15-
"releaseNotes": "https://neo.readthedocs.io/en/latest/releases/0.13.4.html",
15+
"releaseNotes": "https://neo.readthedocs.io/en/latest/releases/0.14.0.html",
1616
"funding": "https://cordis.europa.eu/project/id/945539",
1717
"developmentStatus": "active",
1818
"referencePublication": "https://doi.org/10.3389/fninf.2014.00010",
@@ -113,6 +113,7 @@
113113
{ "@type": "Person", "givenName": "Xin", "familyName": "Niu"},
114114
{ "@type": "Person", "givenName": "Anthony", "familyName": "Pinto"},
115115
{ "@type": "Person", "givenName": "Chris", "familyName": "Heydrick"},
116-
{"@type": "Person", "givenName": "Nikhil", "familyName": "Chandra"}
116+
{"@type": "Person", "givenName": "Nikhil", "familyName": "Chandra"},
117+
{"@type": "Person", "givenName": "Luigi", "familyName": "Petrucco"}
117118
]
118119
}

doc/source/authors.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ and may not be the current affiliation of a contributor.
8989
* Anthony Pinto [41]
9090
* Xin Niu
9191
* Nikhil Chandra [40]
92+
* Luigi Petrucco [42]
9293

9394
1. Centre de Recherche en Neuroscience de Lyon, CNRS UMR5292 - INSERM U1028 - Universite Claude Bernard Lyon 1
9495
2. Unité de Neuroscience, Information et Complexité, CNRS UPR 3293, Gif-sur-Yvette, France
@@ -131,6 +132,7 @@ and may not be the current affiliation of a contributor.
131132
39. Massachusetts General Hospital, Department of Molecular Biology
132133
40. Plexon Inc.
133134
41. Paris Brain Institute
135+
42. Istituto Italiano di Tecnologia (IIT), Italy
134136

135137

136138

doc/source/releases.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Release notes
66
.. toctree::
77
:maxdepth: 1
88

9+
releases/0.14.0.rst
910
releases/0.13.4.rst
1011
releases/0.13.3.rst
1112
releases/0.13.2.rst

doc/source/releases/0.14.0.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
========================
2+
Neo 0.14.0 release notes
3+
========================
4+
5+
17 January 2025
6+
7+
This release of Neo is now compatible with NumPy 2.0 for core and IOs (with the exception of :class:`MedIO`) as well as Python 3.13,
8+
and includes IO bug fixes with an eye toward a 1.0 release.
9+
10+
See all `pull requests`_ included in this release and the `list of closed issues`_.
11+
12+
Updated dependencies
13+
--------------------
14+
15+
Neo now has a limit of NumPy >= 1.22.4
16+
17+
CI Improvements
18+
---------------
19+
20+
To ensure compatiblility between pre- and post- NumPy 2.0 the CI was changed to test on the lowest supported Python (3.9) and
21+
the highest supported Python (3.13) each with NumPy 1.26 as well as NumPy 2.0 for all :code:`RawIO` and :code:`IO` tests.
22+
23+
We also no longer use a cached conda env for testing as we see that there is no speed benefit to caching and we had some issues
24+
with the caches getting corrupted.
25+
26+
Testing of additional Python-NumPy combinations for core tests were added (NumPy 2.0 and 2.1 with their respective Python versions).
27+
28+
Bug fixes and improvements in IO modules
29+
----------------------------------------
30+
31+
Bug fixes and/or improvements have been made to :class:`NeuroNexusIO`, :class:`OpenEphysBinaryIO`, :class:`MicromedIO`, :class:`IntanIO` and :class:`SpikeGLX`.
32+
33+
Acknowledgements
34+
----------------
35+
36+
Thanks to Zach McKenzie, Heberto Mayorquin, Andrew Davison, Luigi Petrucco, Alessio Buccino, and Samuel Garcia.
37+
38+
.. _`pull requests` : https://github.com/NeuralEnsemble/python-neo/pulls?q=is%3Apr+is%3Aclosed+milestone%3A0.14.0
39+
40+
.. _`list of closed issues` : https://github.com/NeuralEnsemble/python-neo/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A0.14.0

examples/plot_igorio.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
2-
IgorProIO Demo
3-
===========================
2+
IgorProIO Demo (BROKEN)
3+
=======================
44
55
"""
66

@@ -17,25 +17,25 @@
1717
# Downloaded from Human Brain Project Collaboratory
1818
# Digital Reconstruction of Neocortical Microcircuitry (nmc-portal)
1919
# http://microcircuits.epfl.ch/#/animal/8ecde7d1-b2d2-11e4-b949-6003088da632
20-
21-
22-
datafile_url = "https://microcircuits.epfl.ch/data/released_data/B95.zip"
23-
filename_zip = "B95.zip"
24-
filename = "grouped_ephys/B95/B95_Ch0_IDRest_107.ibw"
25-
urlretrieve(datafile_url, filename_zip)
26-
27-
zip_ref = zipfile.ZipFile(filename_zip) # create zipfile object
28-
zip_ref.extract(path=".", member=filename) # extract file to dir
29-
zip_ref.close()
30-
31-
######################################################
32-
# Once we have our data we can use `get_io` to find an
33-
# io (Igor in this case). Then we read the analogsignals
34-
# Finally we will make some nice plots
35-
reader = get_io(filename)
36-
signal = reader.read_analogsignal()
37-
plt.plot(signal.times, signal)
38-
plt.xlabel(signal.sampling_period.dimensionality)
39-
plt.ylabel(signal.dimensionality)
40-
41-
plt.show()
20+
# NOTE: this dataset is not found as the link is broken.
21+
22+
# datafile_url = "https://microcircuits.epfl.ch/data/released_data/B95.zip"
23+
# filename_zip = "B95.zip"
24+
# filename = "grouped_ephys/B95/B95_Ch0_IDRest_107.ibw"
25+
# urlretrieve(datafile_url, filename_zip)
26+
27+
# zip_ref = zipfile.ZipFile(filename_zip) # create zipfile object
28+
# zip_ref.extract(path=".", member=filename) # extract file to dir
29+
# zip_ref.close()
30+
31+
# ######################################################
32+
# # Once we have our data we can use `get_io` to find an
33+
# # io (Igor in this case). Then we read the analogsignals
34+
# # Finally we will make some nice plots
35+
# reader = get_io(filename)
36+
# signal = reader.read_analogsignal()
37+
# plt.plot(signal.times, signal)
38+
# plt.xlabel(signal.sampling_period.dimensionality)
39+
# plt.ylabel(signal.dimensionality)
40+
41+
# plt.show()

neo/core/spiketrain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def normalize_times_array(times, units=None, dtype=None, copy=None):
199199
"In order to facilitate the deprecation copy can be set to None but will raise an "
200200
"error if set to True/False since this will silently do nothing. This argument will be completely "
201201
"removed in Neo 0.15.0. Please update your code base as necessary."
202-
)
202+
)
203203

204204
if dtype is None:
205205
if not hasattr(times, "dtype"):

neo/rawio/blackrockrawio.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -681,8 +681,7 @@ def _get_timestamp_slice(self, timestamp, seg_index, t_start, t_stop):
681681
if t_start is None:
682682
t_start = self._seg_t_starts[seg_index]
683683
if t_stop is None:
684-
t_stop = self._seg_t_stops[seg_index] + 1 / float(
685-
self.__nev_basic_header['timestamp_resolution'])
684+
t_stop = self._seg_t_stops[seg_index] + 1 / float(self.__nev_basic_header["timestamp_resolution"])
686685

687686
if t_start is None:
688687
ind_start = None
@@ -715,15 +714,16 @@ def _get_spike_raw_waveforms(self, block_index, seg_index, unit_index, t_start,
715714
)
716715
unit_spikes = all_spikes[mask]
717716

718-
wf_dtype = self.__nev_params('waveform_dtypes')[channel_id]
719-
wf_size = self.__nev_params('waveform_size')[channel_id]
717+
wf_dtype = self.__nev_params("waveform_dtypes")[channel_id]
718+
wf_size = self.__nev_params("waveform_size")[channel_id]
720719
wf_byte_size = np.dtype(wf_dtype).itemsize * wf_size
721720

722721
dt1 = [
723-
('extra', 'S{}'.format(unit_spikes['waveform'].dtype.itemsize - wf_byte_size)),
724-
('ch_waveform', 'S{}'.format(wf_byte_size))]
722+
("extra", "S{}".format(unit_spikes["waveform"].dtype.itemsize - wf_byte_size)),
723+
("ch_waveform", "S{}".format(wf_byte_size)),
724+
]
725725

726-
waveforms = unit_spikes['waveform'].view(dt1)['ch_waveform'].flatten().view(wf_dtype)
726+
waveforms = unit_spikes["waveform"].view(dt1)["ch_waveform"].flatten().view(wf_dtype)
727727

728728
waveforms = waveforms.reshape(int(unit_spikes.size), 1, int(wf_size))
729729

@@ -1365,7 +1365,9 @@ def __match_nsx_and_nev_segment_ids(self, nsx_nb):
13651365

13661366
# Show warning if spikes do not fit any segment (+- 1 sampling 'tick')
13671367
# Spike should belong to segment before
1368-
mask_outside = (ev_ids == i) & (data["timestamp"] < int(seg["timestamp"]) - int(nsx_offset) - int(nsx_period))
1368+
mask_outside = (ev_ids == i) & (
1369+
data["timestamp"] < int(seg["timestamp"]) - int(nsx_offset) - int(nsx_period)
1370+
)
13691371

13701372
if len(data[mask_outside]) > 0:
13711373
warnings.warn(f"Spikes outside any segment. Detected on segment #{i}")
@@ -1995,7 +1997,6 @@ def __get_nsx_param_variant_a(self, nsx_nb):
19951997
else:
19961998
units = "uV"
19971999

1998-
19992000
nsx_parameters = {
20002001
"nb_data_points": int(
20012002
(self.__get_file_size(filename) - bytes_in_headers)

neo/rawio/intanrawio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class IntanRawIO(BaseRawIO):
9292
one long vector, which must be post-processed to extract individual digital channel information.
9393
See the intantech website for more information on performing this post-processing.
9494
95-
95+
9696
Examples
9797
--------
9898
>>> import neo.rawio

neo/rawio/micromedrawio.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ def __init__(self, filename=""):
5252

5353
def _parse_header(self):
5454

55-
5655
with open(self.filename, "rb") as fid:
5756
f = StructFile(fid)
5857

@@ -99,7 +98,6 @@ def _parse_header(self):
9998
if zname != zname2.decode("ascii").strip(" "):
10099
raise NeoReadWriteError("expected the zone name to match")
101100

102-
103101
# "TRONCA" zone define segments
104102
zname2, pos, length = zones["TRONCA"]
105103
f.seek(pos)
@@ -114,7 +112,7 @@ def _parse_header(self):
114112
break
115113
else:
116114
self.info_segments.append((seg_start, trace_offset))
117-
115+
118116
if len(self.info_segments) == 0:
119117
# one unique segment = general case
120118
self.info_segments.append((0, 0))
@@ -152,8 +150,9 @@ def _parse_header(self):
152150
(sampling_rate,) = f.read_f("H")
153151
sampling_rate *= Rate_Min
154152
chan_id = str(c)
155-
signal_channels.append((chan_name, chan_id, sampling_rate, sig_dtype, units, gain, offset, stream_id, buffer_id))
156-
153+
signal_channels.append(
154+
(chan_name, chan_id, sampling_rate, sig_dtype, units, gain, offset, stream_id, buffer_id)
155+
)
157156

158157
signal_channels = np.array(signal_channels, dtype=_signal_channel_dtype)
159158

@@ -166,31 +165,31 @@ def _parse_header(self):
166165
self._sampling_rate = float(np.unique(signal_channels["sampling_rate"])[0])
167166

168167
# memmap traces buffer
169-
full_signal_shape = get_memmap_shape(self.filename, sig_dtype, num_channels=Num_Chan, offset=Data_Start_Offset)
168+
full_signal_shape = get_memmap_shape(
169+
self.filename, sig_dtype, num_channels=Num_Chan, offset=Data_Start_Offset
170+
)
170171
seg_limits = [trace_offset for seg_start, trace_offset in self.info_segments] + [full_signal_shape[0]]
171172
self._t_starts = []
172-
self._buffer_descriptions = {0 :{}}
173+
self._buffer_descriptions = {0: {}}
173174
for seg_index in range(nb_segment):
174175
seg_start, trace_offset = self.info_segments[seg_index]
175176
self._t_starts.append(seg_start / self._sampling_rate)
176177

177178
start = seg_limits[seg_index]
178179
stop = seg_limits[seg_index + 1]
179-
180+
180181
shape = (stop - start, Num_Chan)
181-
file_offset = Data_Start_Offset + ( start * np.dtype(sig_dtype).itemsize * Num_Chan)
182+
file_offset = Data_Start_Offset + (start * np.dtype(sig_dtype).itemsize * Num_Chan)
182183
self._buffer_descriptions[0][seg_index] = {}
183184
self._buffer_descriptions[0][seg_index][buffer_id] = {
184-
"type" : "raw",
185-
"file_path" : str(self.filename),
186-
"dtype" : sig_dtype,
185+
"type": "raw",
186+
"file_path": str(self.filename),
187+
"dtype": sig_dtype,
187188
"order": "C",
188-
"file_offset" : file_offset,
189-
"shape" : shape,
189+
"file_offset": file_offset,
190+
"shape": shape,
190191
}
191192

192-
193-
194193
# Event channels
195194
event_channels = []
196195
event_channels.append(("Trigger", "", "event"))
@@ -217,14 +216,9 @@ def _parse_header(self):
217216
for seg_index in range(nb_segment):
218217
left_lim = seg_limits[seg_index]
219218
right_lim = seg_limits[seg_index + 1]
220-
keep = (
221-
(rawevent["start"] >= left_lim)
222-
& (rawevent["start"] < right_lim)
223-
& (rawevent["start"] != 0)
224-
)
219+
keep = (rawevent["start"] >= left_lim) & (rawevent["start"] < right_lim) & (rawevent["start"] != 0)
225220
self._raw_events[-1].append(rawevent[keep])
226221

227-
228222
# No spikes
229223
spike_channels = []
230224
spike_channels = np.array(spike_channels, dtype=_spike_channel_dtype)

neo/rawio/neuronexusrawio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def __init__(self, filename: str | Path = ""):
7373
* The *.xdat.json metadata file
7474
* The *_data.xdat binary file of all raw data
7575
* The *_timestamps.xdat binary file of the timestamp data
76-
76+
7777
From the metadata the other two files are located within the same directory
7878
and loaded.
7979

0 commit comments

Comments
 (0)