diff --git a/doc/api.rst b/doc/api.rst index 2e9fc1567a..7a72ead33f 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -91,6 +91,7 @@ NEO-based .. autofunction:: read_mcsraw .. autofunction:: read_neuralynx .. autofunction:: read_neuralynx_sorting + .. autofunction:: read_neuroexplorer .. autofunction:: read_neuroscope .. autofunction:: read_nix .. autofunction:: read_openephys @@ -102,6 +103,7 @@ NEO-based .. autofunction:: read_spikeglx .. autofunction:: read_tdt + Non-NEO-based ~~~~~~~~~~~~~ .. automodule:: spikeinterface.extractors diff --git a/src/spikeinterface/extractors/neoextractors/__init__.py b/src/spikeinterface/extractors/neoextractors/__init__.py index 3360b76147..0b11b72b2a 100644 --- a/src/spikeinterface/extractors/neoextractors/__init__.py +++ b/src/spikeinterface/extractors/neoextractors/__init__.py @@ -16,6 +16,7 @@ read_neuroscope_sorting, read_neuroscope, ) +from .neuroexplorer import NeuroExplorerRecordingExtractor, read_neuroexplorer from .nix import NixRecordingExtractor, read_nix from .openephys import ( OpenEphysLegacyRecordingExtractor, @@ -62,6 +63,7 @@ SpikeGadgetsRecordingExtractor, SpikeGLXRecordingExtractor, TdtRecordingExtractor, + NeuroExplorerRecordingExtractor, ] neo_sorting_extractors_list = [ diff --git a/src/spikeinterface/extractors/neoextractors/neuroexplorer.py b/src/spikeinterface/extractors/neoextractors/neuroexplorer.py new file mode 100644 index 0000000000..2c8603cb9c --- /dev/null +++ b/src/spikeinterface/extractors/neoextractors/neuroexplorer.py @@ -0,0 +1,66 @@ +from pathlib import Path + +from spikeinterface.core.core_tools import define_function_from_class + +from .neobaseextractor import NeoBaseRecordingExtractor + + +class NeuroExplorerRecordingExtractor(NeoBaseRecordingExtractor): + """ + Class for reading NEX (NeuroExplorer data format) files. + + Based on :py:class:`neo.rawio.NeuroExplorerRawIO` + + Importantly, at the moment, this recorder only extracts one channel of the recording. + This is because the NeuroExplorerRawIO class does not support multi-channel recordings + as in the NeuroExplorer format they might have different sampling rates. + + Consider extracting all the channels and then concatenating them with the aggregate_channels function. + + >>> from spikeinterface.extractors.neoextractors.neuroexplorer import NeuroExplorerRecordingExtractor + >>> from spikeinterface.core import aggregate_channels + >>> + >>> file_path="/the/path/to/your/nex/file.nex" + >>> + >>> streams = NeuroExplorerRecordingExtractor.get_streams(file_path=file_path) + >>> stream_names = streams[0] + >>> + >>> your_signal_stream_names = "Here goes the logic to filter from stream names the ones that you know have the same sampling rate and you want to aggregate" + >>> + >>> recording_list = [NeuroExplorerRecordingExtractor(file_path=file_path, stream_name=stream_name) for stream_name in your_signal_stream_names] + >>> recording = aggregate_channels(recording_list) + + + + 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. + For this neo reader streams are defined by their sampling frequency. + 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 = "NeuroExplorerRawIO" + name = "neuroexplorer" + + def __init__(self, file_path, stream_id=None, stream_name=None, all_annotations=False): + neo_kwargs = {"filename": str(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(Path(file_path).absolute())}) + self.extra_requirements.append("neo[edf]") + + @classmethod + def map_to_neo_kwargs(cls, file_path): + neo_kwargs = {"filename": str(file_path)} + return neo_kwargs + + +read_neuroexplorer = define_function_from_class(source_class=NeuroExplorerRecordingExtractor, name="read_neuroexplorer") diff --git a/src/spikeinterface/extractors/tests/test_neoextractors.py b/src/spikeinterface/extractors/tests/test_neoextractors.py index ce2703d382..257c1d566a 100644 --- a/src/spikeinterface/extractors/tests/test_neoextractors.py +++ b/src/spikeinterface/extractors/tests/test_neoextractors.py @@ -142,6 +142,17 @@ class NeuroScopeRecordingTest(RecordingCommonTestSuite, unittest.TestCase): ] +class NeuroExplorerRecordingTest(RecordingCommonTestSuite, unittest.TestCase): + ExtractorClass = NeuroExplorerRecordingExtractor + downloads = ["neuroexplorer"] + entities = [ + ("neuroexplorer/File_neuroexplorer_1.nex", {"stream_name": "ContChannel01"}), + ("neuroexplorer/File_neuroexplorer_1.nex", {"stream_name": "ContChannel02"}), + ("neuroexplorer/File_neuroexplorer_2.nex", {"stream_name": "ContChannel01"}), + ("neuroexplorer/File_neuroexplorer_2.nex", {"stream_name": "ContChannel02"}), + ] + + class NeuroScopeSortingTest(SortingCommonTestSuite, unittest.TestCase): ExtractorClass = NeuroScopeSortingExtractor downloads = ["neuroscope"]