Skip to content

Commit a8e4321

Browse files
authored
Merge pull request #4075 from zm711/append-concat
Add append mode to IntanSplitFile building on #4070
2 parents 7a4e8eb + 88b43f8 commit a8e4321

5 files changed

Lines changed: 120 additions & 0 deletions

File tree

doc/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ NEO-based
105105
.. autofunction:: read_blackrock
106106
.. autofunction:: read_ced
107107
.. autofunction:: read_intan
108+
.. autofunction:: read_split_intan_files
108109
.. autofunction:: read_maxwell
109110
.. autofunction:: read_mearec
110111
.. autofunction:: read_mcsraw

doc/modules/extractors.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ For raw recording formats, we currently support:
127127
* **EDF** :py:func:`~spikeinterface.extractors.read_edf()`
128128
* **IBL streaming** :py:func:`~spikeinterface.extractors.read_ibl_recording()`
129129
* **Intan** :py:func:`~spikeinterface.extractors.read_intan()`
130+
* **Intan split files** :py:func:`~spikeinterface.extractors.read_split_intan_files()`
130131
* **MaxWell** :py:func:`~spikeinterface.extractors.read_maxwell()`
131132
* **MCS H5** :py:func:`~spikeinterface.extractors.read_mcsh5()`
132133
* **MCS RAW** :py:func:`~spikeinterface.extractors.read_mcsraw()`

src/spikeinterface/extractors/extractor_classes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# sorting/recording/event from neo
2121
from .neoextractors import *
2222
from .neoextractors import read_neuroscope
23+
from .neoextractors.intan import read_split_intan_files, IntanSplitFilesRecordingExtractor
2324

2425
# non-NEO objects implemented in neo folder
2526
# keep for reference Currently pulling from neoextractor __init__
@@ -200,5 +201,6 @@
200201
"read_binary", # convenience function for binary formats
201202
"read_zarr",
202203
"read_neuroscope", # convenience function for neuroscope
204+
"read_split_intan_files", # convenience function for segmented intan files
203205
]
204206
)

src/spikeinterface/extractors/neoextractors/intan.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
2+
from typing import Literal
23
from pathlib import Path
34

45
import numpy as np
56

67
from spikeinterface.core.core_tools import define_function_from_class
8+
from spikeinterface.core.segmentutils import ConcatenateSegmentRecording, AppendSegmentRecording
79
from .neobaseextractor import NeoBaseRecordingExtractor
810

911

@@ -104,3 +106,106 @@ def _add_channel_groups(self):
104106

105107

106108
read_intan = define_function_from_class(source_class=IntanRecordingExtractor, name="read_intan")
109+
110+
111+
class IntanSplitFilesRecordingExtractor(ConcatenateSegmentRecording, AppendSegmentRecording):
112+
"""
113+
Class for reading Intan traditional format split files from a folder and
114+
concatenating/appending them in temporal order.
115+
116+
Intan traditional format creates multiple files with time-based naming when recording
117+
for extended periods. This class automatically sorts the files by filename and concatenates
118+
them to create a continuous recording (monosegment) or appends them to a multisegment recording.
119+
120+
Parameters
121+
----------
122+
folder_path : str or Path
123+
Path to the folder containing split Intan files (.rhd or .rhs extensions)
124+
mode : "concatenate" | "append": default: "concatenate"
125+
The determines whether to concatenate intan files to make a monosegment or to append them
126+
to make a multisegment recording
127+
stream_id : str, default: None
128+
If there are several streams, specify the stream id you want to load.
129+
stream_name : str, default: None
130+
If there are several streams, specify the stream name you want to load.
131+
all_annotations : bool, default: False
132+
Load exhaustively all annotations from neo.
133+
use_names_as_ids : bool, default: False
134+
Determines the format of the channel IDs used by the extractor. If set to True, the channel IDs will be the
135+
names from NeoRawIO. If set to False, the channel IDs will be the ids provided by NeoRawIO.
136+
ignore_integrity_checks : bool, default: False
137+
If True, data that violates integrity assumptions will be loaded. At the moment the only integrity
138+
check we perform is that timestamps are continuous. Setting this to True will ignore this check and set
139+
the attribute `discontinuous_timestamps` to True in the underlying neo object.
140+
141+
Examples
142+
--------
143+
>>> from spikeinterface.extractors import IntanSplitFilesRecordingExtractor
144+
>>> recording = IntanSplitFilesRecordingExtractor("/path/to/intan/folder")
145+
"""
146+
147+
def __init__(
148+
self,
149+
folder_path,
150+
mode: Literal["append", "concatenate"] = "concatenate",
151+
stream_id=None,
152+
stream_name=None,
153+
all_annotations=False,
154+
use_names_as_ids=False,
155+
ignore_integrity_checks: bool = False,
156+
):
157+
158+
if mode not in ("append", "concatenate"):
159+
mode_error = (
160+
"Possible options for the `mode` argument are 'concatenate' or 'append', you have entered " f"{mode}."
161+
)
162+
raise ValueError(mode_error)
163+
164+
folder_path = Path(folder_path)
165+
166+
if not folder_path.exists() or not folder_path.is_dir():
167+
raise ValueError(f"Folder path {folder_path} does not exist or is not a directory")
168+
169+
# Find all Intan files
170+
file_path_list = [p for p in folder_path.iterdir() if p.suffix.lower() in [".rhd", ".rhs"]]
171+
172+
if not file_path_list:
173+
raise ValueError(f"No Intan files (.rhd or .rhs) found in {folder_path}")
174+
175+
# Sort files by filename (natural sort)
176+
file_path_list.sort(key=lambda x: x.name)
177+
178+
# Read each file and create recording list
179+
recording_list = []
180+
for file_path in file_path_list:
181+
recording = read_intan(
182+
file_path,
183+
stream_id=stream_id,
184+
stream_name=stream_name,
185+
all_annotations=all_annotations,
186+
use_names_as_ids=use_names_as_ids,
187+
ignore_integrity_checks=ignore_integrity_checks,
188+
)
189+
recording_list.append(recording)
190+
191+
# Initialize the parent class with the recording list
192+
if mode == "concatenate":
193+
ConcatenateSegmentRecording.__init__(self, recording_list)
194+
elif mode == "append":
195+
AppendSegmentRecording.__init__(self, recording_list)
196+
197+
# Update kwargs to include our specific parameters
198+
self._kwargs = dict(
199+
folder_path=str(Path(folder_path).resolve()),
200+
mode=mode,
201+
stream_id=stream_id,
202+
stream_name=stream_name,
203+
all_annotations=all_annotations,
204+
use_names_as_ids=use_names_as_ids,
205+
ignore_integrity_checks=ignore_integrity_checks,
206+
)
207+
208+
209+
read_split_intan_files = define_function_from_class(
210+
source_class=IntanSplitFilesRecordingExtractor, name="read_split_intan_files"
211+
)

src/spikeinterface/extractors/tests/test_neoextractors.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
AxonRecordingExtractor,
4444
)
4545

46+
from spikeinterface.extractors.neoextractors.intan import IntanSplitFilesRecordingExtractor
47+
4648
from spikeinterface.extractors.extractor_classes import KiloSortSortingExtractor
4749

4850
from spikeinterface.extractors.tests.common_tests import (
@@ -184,6 +186,15 @@ class IntanRecordingTestMultipleFilesFormat(RecordingCommonTestSuite, unittest.T
184186
]
185187

186188

189+
class IntanSplitFilesRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
190+
ExtractorClass = IntanSplitFilesRecordingExtractor
191+
downloads = ["intan"]
192+
entities = [
193+
("intan/test_tetrode_240502_162925", {"mode": "concatenate"}),
194+
("intan/test_tetrode_240502_162925", {"mode": "append"}),
195+
]
196+
197+
187198
class NeuroScopeRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
188199
ExtractorClass = NeuroScopeRecordingExtractor
189200
downloads = ["neuroscope"]

0 commit comments

Comments
 (0)