Skip to content

Commit

Permalink
Merge pull request #1736 from JuliaSprenger/add/pl2
Browse files Browse the repository at this point in the history
Add plexon2 recording, sorting and event support
  • Loading branch information
samuelgarcia authored Sep 13, 2023
2 parents 31f679f + e73cf7e commit d050bb4
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 5 deletions.
21 changes: 21 additions & 0 deletions .github/actions/install-wine/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Install packages
description: This action installs the package and its dependencies for testing

inputs:
python-version:
description: 'Python version to set up'
required: false
os:
description: 'Operating system to set up'
required: false

runs:
using: "composite"
steps:
- name: Install wine (needed for Plexon2)
run: |
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
sudo dpkg --add-architecture i386
sudo apt-get update -qq
sudo apt-get install -yqq --allow-downgrades libc6:i386 libgcc-s1:i386 libstdc++6:i386 wine
shell: bash
7 changes: 7 additions & 0 deletions .github/workflows/full-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ jobs:
echo "Extractors changed"
echo "EXTRACTORS_CHANGED=true" >> $GITHUB_OUTPUT
fi
if [[ $file == *"plexon2"* ]]; then
echo "Plexon2 changed"
echo "PLEXON2_CHANGED=true" >> $GITHUB_OUTPUT
fi
if [[ $file == *"/preprocessing/"* ]]; then
echo "Preprocessing changed"
echo "PREPROCESSING_CHANGED=true" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -122,6 +126,9 @@ jobs:
done
- name: Set execute permissions on run_tests.sh
run: chmod +x .github/run_tests.sh
- name: Install Wine (Plexon2)
if: ${{ steps.modules-changed.outputs.PLEXON2_CHANGED == 'true' }}
uses: ./.github/actions/install-wine
- name: Test core
run: ./.github/run_tests.sh core
- name: Test extractors
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ extractors = [
"ONE-api>=1.19.1",
"ibllib>=2.21.0",
"pymatreader>=0.0.32", # For cell explorer matlab files
"zugbruecke>=0.2; sys_platform!='win32'", # For plexon2
]

streaming_extractors = [
Expand Down
18 changes: 16 additions & 2 deletions src/spikeinterface/extractors/neoextractors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
read_openephys_event,
)
from .plexon import PlexonRecordingExtractor, PlexonSortingExtractor, read_plexon, read_plexon_sorting
from .plexon2 import (
Plexon2SortingExtractor,
Plexon2RecordingExtractor,
Plexon2EventExtractor,
read_plexon2,
read_plexon2_sorting,
read_plexon2_event,
)
from .spike2 import Spike2RecordingExtractor, read_spike2
from .spikegadgets import SpikeGadgetsRecordingExtractor, read_spikegadgets
from .spikeglx import SpikeGLXRecordingExtractor, read_spikeglx
Expand All @@ -49,12 +57,18 @@
OpenEphysBinaryRecordingExtractor,
OpenEphysLegacyRecordingExtractor,
PlexonRecordingExtractor,
Plexon2RecordingExtractor,
Spike2RecordingExtractor,
SpikeGadgetsRecordingExtractor,
SpikeGLXRecordingExtractor,
TdtRecordingExtractor,
]

neo_sorting_extractors_list = [BlackrockSortingExtractor, MEArecSortingExtractor, NeuralynxSortingExtractor]
neo_sorting_extractors_list = [
BlackrockSortingExtractor,
MEArecSortingExtractor,
NeuralynxSortingExtractor,
Plexon2SortingExtractor,
]

neo_event_extractors_list = [AlphaOmegaEventExtractor, OpenEphysBinaryEventExtractor]
neo_event_extractors_list = [AlphaOmegaEventExtractor, OpenEphysBinaryEventExtractor, Plexon2EventExtractor]
103 changes: 103 additions & 0 deletions src/spikeinterface/extractors/neoextractors/plexon2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from spikeinterface.core.core_tools import define_function_from_class

from .neobaseextractor import NeoBaseRecordingExtractor, NeoBaseSortingExtractor, NeoBaseEventExtractor


class Plexon2RecordingExtractor(NeoBaseRecordingExtractor):
"""
Class for reading plexon pl2 files.
Based on :py:class:`neo.rawio.Plexon2RawIO`
Parameters
----------
file_path: str
The file path to load the recordings from.
stream_id: str, optional
If there are several streams, specify the stream id you want to load.
stream_name: str, optional
If there are several streams, specify the stream name you want to load.
all_annotations: bool, default: False
Load exhaustively all annotations from neo.
"""

mode = "file"
NeoRawIOClass = "Plexon2RawIO"
name = "plexon2"

def __init__(self, file_path, stream_id=None, stream_name=None, all_annotations=False):
neo_kwargs = self.map_to_neo_kwargs(file_path)
NeoBaseRecordingExtractor.__init__(
self, stream_id=stream_id, stream_name=stream_name, all_annotations=all_annotations, **neo_kwargs
)
self._kwargs.update({"file_path": str(file_path)})

@classmethod
def map_to_neo_kwargs(cls, file_path):
neo_kwargs = {"filename": str(file_path)}
return neo_kwargs


class Plexon2SortingExtractor(NeoBaseSortingExtractor):
"""
Class for reading plexon spiking data from .pl2 files.
Based on :py:class:`neo.rawio.Plexon2RawIO`
Parameters
----------
file_path: str
The file path to load the recordings from.
sampling_frequency: float, default: None
The sampling frequency of the sorting (required for multiple streams with different sampling frequencies).
"""

mode = "file"
NeoRawIOClass = "Plexon2RawIO"
neo_returns_frames = True
name = "plexon2"

def __init__(self, file_path, sampling_frequency=None):
from neo.rawio import Plexon2RawIO

neo_kwargs = self.map_to_neo_kwargs(file_path)
neo_reader = Plexon2RawIO(**neo_kwargs)
neo_reader.parse_header()
NeoBaseSortingExtractor.__init__(self, sampling_frequency=sampling_frequency, **neo_kwargs)
self._kwargs.update({"file_path": str(file_path), "sampling_frequency": sampling_frequency})

@classmethod
def map_to_neo_kwargs(cls, file_path):
neo_kwargs = {"filename": str(file_path)}
return neo_kwargs


class Plexon2EventExtractor(NeoBaseEventExtractor):
"""
Class for reading plexon spiking data from .pl2 files.
Based on :py:class:`neo.rawio.Plexon2RawIO`
Parameters
----------
folder_path: str
"""

mode = "file"
NeoRawIOClass = "Plexon2RawIO"
name = "plexon2"

def __init__(self, folder_path, block_index=None):
neo_kwargs = self.map_to_neo_kwargs(folder_path)
NeoBaseEventExtractor.__init__(self, block_index=block_index, **neo_kwargs)

@classmethod
def map_to_neo_kwargs(cls, folder_path):
neo_kwargs = {"filename": str(folder_path)}
return neo_kwargs


read_plexon2 = define_function_from_class(source_class=Plexon2RecordingExtractor, name="read_plexon2")
read_plexon2_sorting = define_function_from_class(source_class=Plexon2SortingExtractor, name="read_plexon2_sorting")
read_plexon2_event = define_function_from_class(source_class=Plexon2EventExtractor, name="read_plexon2_event")
65 changes: 62 additions & 3 deletions src/spikeinterface/extractors/tests/test_neoextractors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
from platform import python_version
import platform
import subprocess
from packaging import version

import pytest
Expand All @@ -18,6 +19,38 @@
local_folder = get_global_dataset_folder() / "ephy_testing_data"


def has_plexon2_dependencies():
"""
Check if required Plexon2 dependencies are installed on different OS.
"""

os_type = platform.system()

if os_type == "Windows":
# On Windows, no need for additional dependencies
return True

elif os_type == "Linux":
# Check for 'wine' using dpkg
try:
result_wine = subprocess.run(
["dpkg", "-l", "wine"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
)
except subprocess.CalledProcessError:
return False

# Check for 'zugbruecke' using pip
try:
import zugbruecke

return True
except ImportError:
return False
else:
# Not sure about MacOS
raise ValueError(f"Unsupported OS: {os_type}")


class MearecRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
ExtractorClass = MEArecRecordingExtractor
downloads = ["mearec"]
Expand Down Expand Up @@ -218,7 +251,7 @@ class Spike2RecordingTest(RecordingCommonTestSuite, unittest.TestCase):


@pytest.mark.skipif(
version.parse(python_version()) >= version.parse("3.10"),
version.parse(platform.python_version()) >= version.parse("3.10"),
reason="Sonpy only testing with Python < 3.10!",
)
class CedRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
Expand Down Expand Up @@ -290,6 +323,32 @@ def test_pickling(self):
pass


# We run plexon2 tests only if we have dependencies (wine)
@pytest.mark.skipif(not has_plexon2_dependencies(), reason="Required dependencies not installed")
class Plexon2RecordingTest(RecordingCommonTestSuite, unittest.TestCase):
ExtractorClass = Plexon2RecordingExtractor
downloads = ["plexon"]
entities = [
("plexon/4chDemoPL2.pl2", {"stream_id": "3"}),
]


@pytest.mark.skipif(not has_plexon2_dependencies(), reason="Required dependencies not installed")
class Plexon2EventTest(EventCommonTestSuite, unittest.TestCase):
ExtractorClass = Plexon2EventExtractor
downloads = ["plexon"]
entities = [
("plexon/4chDemoPL2.pl2"),
]


@pytest.mark.skipif(not has_plexon2_dependencies(), reason="Required dependencies not installed")
class Plexon2SortingTest(SortingCommonTestSuite, unittest.TestCase):
ExtractorClass = Plexon2SortingExtractor
downloads = ["plexon"]
entities = [("plexon/4chDemoPL2.pl2", {"sampling_frequency": 40000})]


if __name__ == "__main__":
# test = MearecSortingTest()
# test = SpikeGLXRecordingTest()
Expand All @@ -304,7 +363,7 @@ def test_pickling(self):
# test = PlexonRecordingTest()
# test = PlexonSortingTest()
# test = NeuralynxRecordingTest()
test = BlackrockRecordingTest()
test = Plexon2RecordingTest()
# test = MCSRawRecordingTest()
# test = KiloSortSortingTest()
# test = Spike2RecordingTest()
Expand Down

0 comments on commit d050bb4

Please sign in to comment.