Skip to content

Commit

Permalink
Merge branch 'master' into numpy-2-0
Browse files Browse the repository at this point in the history
  • Loading branch information
zm711 authored Oct 21, 2024
2 parents 7d83da0 + a21d2ef commit 5930673
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 24 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/core-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ jobs:
strategy:
fail-fast: true
matrix:
os: ["ubuntu-latest", "windows-latest"]
# "macos-latest",
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
numpy-version: ['1.22.4', '1.23.5', '1.24.4', '1.25.1', '1.26.4', '2.0.2','2.1']
# numpy 1.22: 3.10, 1.23: 3.11, 1.24: 3.11, 1.25: 3.11, 1.26: 3.12
exclude:
- python-version: '3.9'
numpy-version: '2.1'
- python-version: '3.11'
numpy-version: '1.22.4'
- python-version: '3.12'
numpy-version: '1.22.4'
- python-version: '3.12'
Expand Down
13 changes: 7 additions & 6 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
"license": "https://spdx.org/licenses/BSD-3-Clause",
"codeRepository": "https://github.com/NeuralEnsemble/python-neo",
"contIntegration": "https://github.com/NeuralEnsemble/python-neo/actions",
"dateModified": "2024-08-28",
"downloadUrl": "https://files.pythonhosted.org/packages/08/4b/c863c6bff783e94c92cb814f6ae821b35e6463c5a66e809b6864d0c66b4e/neo-0.13.3.tar.gz",
"dateModified": "2024-10-14",
"downloadUrl": null,
"issueTracker": "https://github.com/NeuralEnsemble/python-neo/issues",
"name": "Neo",
"version": "0.13.3",
"version": "0.13.4",
"identifier": "RRID:SCR_000634",
"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.",
"applicationCategory": "neuroscience",
"releaseNotes": "https://neo.readthedocs.io/en/stable/releases/0.13.3.html",
"releaseNotes": null,
"funding": "https://cordis.europa.eu/project/id/945539",
"developmentStatus": "active",
"referencePublication": "https://doi.org/10.3389/fninf.2014.00010",
Expand All @@ -23,7 +23,7 @@
"programmingLanguage": ["Python"],
"operatingSystem": ["Linux", "Windows", "macOS"],
"softwareRequirements": [
"Python (version 3.8+)",
"Python (version 3.9+)",
"see https://github.com/NeuralEnsemble/python-neo/blob/master/pyproject.toml"
],
"relatedLink": ["https://neo.readthedocs.io", "https://neuralensemble.org/community"],
Expand Down Expand Up @@ -112,6 +112,7 @@
{ "@type": "Person", "givenName": "Kyu Hun", "familyName": "Lee"},
{ "@type": "Person", "givenName": "Xin", "familyName": "Niu"},
{ "@type": "Person", "givenName": "Anthony", "familyName": "Pinto"},
{ "@type": "Person", "givenName": "Chris", "familyName": "Heydrick"}
{ "@type": "Person", "givenName": "Chris", "familyName": "Heydrick"},
{"@type": "Person", "givenName": "Nikhil", "familyName": "Chandra"}
]
}
1 change: 1 addition & 0 deletions doc/source/authors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ and may not be the current affiliation of a contributor.
* Téo Lohrer
* Anthony Pinto [41]
* Xin Niu
* Nikhil Chandra [40]

1. Centre de Recherche en Neuroscience de Lyon, CNRS UMR5292 - INSERM U1028 - Universite Claude Bernard Lyon 1
2. Unité de Neuroscience, Information et Complexité, CNRS UPR 3293, Gif-sur-Yvette, France
Expand Down
1 change: 1 addition & 0 deletions doc/source/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Release notes
.. toctree::
:maxdepth: 1

releases/0.13.4.rst
releases/0.13.3.rst
releases/0.13.2.rst
releases/0.13.1.rst
Expand Down
111 changes: 111 additions & 0 deletions doc/source/releases/0.13.4.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
========================
Neo 0.13.4 release notes
========================

21 October 2024

This release of Neo contains bug fixes across many IOs, a new IO for :code:`NeuroNexus`, drop of Python 3.8, NumPy 1.20 & 1.21, still with a focus on the planned 1.0 release.
Additionally Neo now supports Quantities >=16.1 which brings us closer to support for NumPy 2.0 +. At the :code:`RawIO` level a new :code:`buffer api` has been introduced
with the goal of better grouping streams of data together. This is an ongoing effort to provide better access to streams of data that are typically analyzed together without
changes to the public API.

This point release will be the last release to not support Python 3.13 and NumPy > 2.0.

See all `pull requests`_ included in this release and the `list of closed issues`_.


Updated dependencies
--------------------

Neo has a limit of NumPy >= 1.22.4, < 2.0.0
Neo now supports Python >= 3.9, <3.13
Neo has a limit of Quantities >= 16.1
Neo has a limit of dhn_med_py < 2.0.0 (for reading MED format)

Deprecations
------------

As Quantities has dropped support for the :code:`copy` argument when making Quantities arrays to be NumPy 2.0 compatible, the :code:`copy` argument
has also been deprecated in all Neo core objects (e.g. :code:`SpikeTrain`, etc.). For this version and the next version the default is now :code:`copy=None`.
If :code:`copy=True` or :code:`copy=False` are used an error will be raised. This also means that some functionality for rescaling and dtype conversion, which
required :code:`copy=True` are no longer possible. Appropriate errors are raised if the user tries these now impossible behaviors at construction.

Additional changes that occurred in Quantities can be viewed at their changelog:
https://github.com/python-quantities/python-quantities/blob/master/CHANGES.txt

Currently acceptable construction patterns can be found in the Neo test folder:
https://github.com/NeuralEnsemble/python-neo/blob/master/neo/test/coretest

Many previous behaviors can still be achieved by using additional lines of code, e.g.:

.. code-block:: python
>>> import quantities as pq
>>> import neo
# if we want to get a spiketrain in seconds, but we entered our times in ms
# we used to be able to autoconvert by specifying units. But now this will
# raise an error!
>>> times = [1,2,3] * 1 * pq.ms
>>> t_stop = 1 * pq.s
>>> spiketrain = neo.SpikeTrain(times, t_stop=t_stop, units='s')
ValueError: cannot rescale and return view
# so instead we need to rescale in a second step
>>> spiketrain = neo.SpikeTrain(times, t_stop=t_stop)
>>> spiketrain
<SpikeTrain(array[1, 2, 3] * ms, [0.0 ms, 1000.0 ms])>
>>> rescaled_spiketrain = spiketrain.rescale('s')
>>> rescaled_spiketrain
<SpikeTrain(array[0.001, 0.002, 0.003] * s, [0.0 s, 10.0 s])>
CI Additions/Changes
--------------------

Neo has sped up the testing suite by ~15% and added additional testing for IOs: :class:`NeuralynxIO` and
:class:`Plexon2IO`.

Testing around :code:`copy` was removed from the core testing, since this argument is no longer possible.


Addition of a New IO module
---------------------------

Neo now has support for reading NeuroNexus :code:`.xdat` files with the new :class:`NeuroNexusIO`.


Bug fixes and improvements in IO modules
----------------------------------------

Bug fixes and/or improvements have been made to :class:`MaxwellIO`, :class:`NeuroNexusIO`,
:class:`IntanIO`, :class:`Plexon2IO`, :class:`IgorIO`, :class:`SpikeGadgetsIO`, :class:`PlexonIO`,
and :class:`BrainVisionRawIO`, and :class:`EDFIO`

Buffer API
----------

The motivation for this :code:`RawIO` was that many IOs have buffers of data (memmaps/hdf5) files, which allow for multiple unrelated streams of data to be packaged
together. This has led to some inconsistencies in how IOs access streams of data. For example, the :code:`PlexonIO` stores WideBand and Filtered versions of the same
data, but the end user likely wouldn't want to analyze them both at the same time as that would be duplication of information. :code:`SpikeGLX` also makes use of a sync
channel which is stored with the electrophysiological channels, but should not be analyzed as an ephys channel. The Buffer API will be an ongoing set of PRs at the
:code:`RawIO` level to better clarify how data enters and is mapped in Neo versus how the end-user might request streams of data. We hope that this process will allow
the end-user better access to the data they want without having unrelated data mixed in. Importantly the public API is not being affected by this process at all. The end-user
will still request their desired stream using :code:`stream_index` argument when interacting with a :code:`RawIO`.

In this release, each IO was divided into whether it would fit with the buffer api requirements or not and the initial :code:`buffer_id` was applied to all IOs. This step
has not changed any behavior in Neo. But the :code:`RawIO.header` information will now have an additional field that will be used in future releases of Neo under-the-hood.

We want to emphasize this is not a public API change and over the next version we hope to fully implement this new schema to allow for better interaction with data at the
:code:`RawIO` and :code:`IO` levels of Neo.

This project has largely been spearheaded by Samuel Garcia and we thank him for his herculean efforts.

Acknowledgements
----------------

Thanks to Zach McKenzie, Heberto Mayorquin, Samuel Garcia, Andrew Davison, Alessio Buccino, Nikhil Chandra, and Peter Steinmetz for their contributions to this release.


.. _`pull requests`: https://github.com/NeuralEnsemble/python-neo/pulls?q=is%3Apr+is%3Aclosed+milestone%3A0.13.4

.. _`list of closed issues`: https://github.com/NeuralEnsemble/python-neo/issues?q=is%3Aissue+is%3Aclosed+milestone%3A0.13.4
35 changes: 22 additions & 13 deletions neo/rawio/edfrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,14 @@ def _parse_header(self):
# or continuous EDF+ files ('EDF+C' in header)
if ("EDF+" in file_version_header) and ("EDF+C" not in file_version_header):
raise ValueError("Only continuous EDF+ files are currently supported.")

self.edf_reader = EdfReader(self.filename)
self._open_reader()
# load headers, signal information and
self.edf_header = self.edf_reader.getHeader()
self.signal_headers = self.edf_reader.getSignalHeaders()

# add annotations to header
annotations = self.edf_reader.readAnnotations()
self.signal_annotations = [[s, d, a] for s, d, a in zip(*annotations)]
self._edf_annotations = self.edf_reader.readAnnotations()
self.signal_annotations = [[s, d, a] for s, d, a in zip(*self._edf_annotations)]

# 1 stream = 1 sampling rate
stream_characteristics = []
Expand Down Expand Up @@ -120,7 +119,7 @@ def _parse_header(self):
signal_channels.append((ch_name, chan_id, sr, dtype, units, gain, offset, stream_id, buffer_id))

# convert channel index lists to arrays for indexing
self.stream_idx_to_chidx = {k: np.array(v) for k, v in self.stream_idx_to_chidx.items()}
self.stream_idx_to_chidx = {k: np.asarray(v) for k, v in self.stream_idx_to_chidx.items()}

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

Expand Down Expand Up @@ -174,6 +173,15 @@ def _parse_header(self):
for array_key in array_keys:
array_anno = {array_key: [h[array_key] for h in self.signal_headers]}
seg_ann["signals"].append({"__array_annotations__": array_anno})

# We store the following attributes for rapid access without needing the reader

self._t_stop = self.edf_reader.datarecord_duration * self.edf_reader.datarecords_in_file
# use sample count of first signal in stream
self._stream_index_samples = {stream_index : self.edf_reader.getNSamples()[chidx][0] for stream_index, chidx in self.stream_idx_to_chidx.items()}
self._number_of_events = len(self.edf_reader.readAnnotations()[0])

self.close()

def _get_stream_channels(self, stream_index):
return self.header["signal_channels"][self.stream_idx_to_chidx[stream_index]]
Expand All @@ -183,14 +191,11 @@ def _segment_t_start(self, block_index, seg_index):
return 0.0 # in seconds

def _segment_t_stop(self, block_index, seg_index):
t_stop = self.edf_reader.datarecord_duration * self.edf_reader.datarecords_in_file
# this must return an float scale in second
return t_stop
return self._t_stop

def _get_signal_size(self, block_index, seg_index, stream_index):
chidx = self.stream_idx_to_chidx[stream_index][0]
# use sample count of first signal in stream
return self.edf_reader.getNSamples()[chidx]
return self._stream_index_samples[stream_index]

def _get_signal_t_start(self, block_index, seg_index, stream_index):
return 0.0 # EDF does not provide temporal offset information
Expand Down Expand Up @@ -219,12 +224,13 @@ def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop, strea

# load data into numpy array buffer
data = []
self._open_reader()
for i, channel_idx in enumerate(selected_channel_idxs):
# use int32 for compatibility with pyedflib
buffer = np.empty(n, dtype=np.int32)
self.edf_reader.read_digital_signal(channel_idx, i_start, n, buffer)
data.append(buffer)

self._close_reader()
# downgrade to int16 as this is what is used in the edf file format
# use fortran (column major) order to be more efficient after transposing
data = np.asarray(data, dtype=np.int16, order="F")
Expand All @@ -247,11 +253,11 @@ def _get_spike_raw_waveforms(self, block_index, seg_index, spike_channel_index,
return None

def _event_count(self, block_index, seg_index, event_channel_index):
return len(self.edf_reader.readAnnotations()[0])
return self._number_of_events

def _get_event_timestamps(self, block_index, seg_index, event_channel_index, t_start, t_stop):
# these time should be already in seconds
timestamps, durations, labels = self.edf_reader.readAnnotations()
timestamps, durations, labels = self._edf_annotations
if t_start is None:
t_start = self.segment_t_start(block_index, seg_index)
if t_stop is None:
Expand Down Expand Up @@ -281,6 +287,9 @@ def _rescale_event_timestamp(self, event_timestamps, dtype, event_channel_index)
def _rescale_epoch_duration(self, raw_duration, dtype, event_channel_index):
return np.asarray(raw_duration, dtype=dtype)

def _open_reader(self):
self.edf_reader = EdfReader(self.filename)

def __enter__(self):
return self

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[project]
name = "neo"
version = "0.14.0.dev0"
version = "0.13.4"
authors = [{name = "Neo authors and contributors"}]
description = "Neo is a package for representing electrophysiology data in Python, together with support for reading a wide range of neurophysiology file formats"
readme = "README.rst"
requires-python = ">=3.9"
requires-python = ">=3.9,<3.13" # 3.13 will require NumPy > 2.0 (Windows issue in CI)
license = {text = "BSD 3-Clause License"}
classifiers = [
"Development Status :: 4 - Beta",
Expand All @@ -14,10 +14,10 @@ classifiers = [
"Natural Language :: English",
"Topic :: Scientific/Engineering",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only",
]

Expand Down

0 comments on commit 5930673

Please sign in to comment.