Skip to content

Commit 7b91ac2

Browse files
authored
Merge branch 'master' into neuronexus
2 parents 707cf9d + 7856ad1 commit 7b91ac2

File tree

4 files changed

+90
-20
lines changed

4 files changed

+90
-20
lines changed

neo/rawio/__init__.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
Functions:
99
10-
.. autofunction:: neo.rawio.get_rawio_class
10+
.. autofunction:: neo.rawio.get_rawio
1111
1212
1313
Classes:
@@ -181,7 +181,8 @@
181181
182182
"""
183183

184-
import os
184+
from pathlib import Path
185+
from collections import Counter
185186

186187
from neo.rawio.alphaomegarawio import AlphaOmegaRawIO
187188
from neo.rawio.axographrawio import AxographRawIO
@@ -262,23 +263,58 @@ def get_rawio_class(filename_or_dirname):
262263

263264
import warnings
264265

265-
warnings.warn("get_rawio_class is deprecated. In the future please use get_rawio")
266+
warnings.warn(
267+
"get_rawio_class is deprecated and will be removed in 0.15.0. " "In the future please use `get_rawio`"
268+
)
266269

267270
return get_rawio(filename_or_dirname)
268271

269272

270-
def get_rawio(filename_or_dirname):
273+
def get_rawio(filename_or_dirname, exclusive_rawio: bool = True):
271274
"""
272275
Return a neo.rawio class guess from file extension.
276+
277+
Parameters
278+
----------
279+
filename_or_dirname : str | Path
280+
The filename or directory name to check for file suffixes that
281+
can be read by Neo. This can also be used to check whether a
282+
rawio could read a not-yet written file
283+
exclusive_rawio: bool, default: True
284+
Whether to return a rawio if there is only one rawio capable of
285+
reading the file. If this doesn't exist will return None.
286+
If set to False it will return all possible rawios organized
287+
by the most likely rawio.
288+
289+
Returns
290+
-------
291+
possibles: neo.RawIO | None | list[neo.RawIO]
292+
If exclusive_rawio is True, returns the single RawIO that
293+
can read a file/set of files or None. If exclusive_rawio is
294+
False it will return all possible RawIOs (organized by most likely)
295+
that could read the file or files.
273296
"""
274-
_, ext = os.path.splitext(filename_or_dirname)
275-
ext = ext[1:]
297+
filename_or_dirname = Path(filename_or_dirname)
298+
299+
# if filename_or_dirname doesn't exist then user may just be checking if
300+
# neo can read their file or they give a real file
301+
if not filename_or_dirname.exists() or filename_or_dirname.is_file():
302+
ext = Path(filename_or_dirname).suffix
303+
ext_list = [ext[1:]]
304+
else:
305+
ext_list = list({filename.suffix[1:] for filename in filename_or_dirname.glob('*') if filename.is_file()})
306+
276307
possibles = []
277-
for rawio in rawiolist:
278-
if any(ext.lower() == ext2.lower() for ext2 in rawio.extensions):
279-
possibles.append(rawio)
308+
for ext in ext_list:
309+
for rawio in rawiolist:
310+
if any(ext.lower() == ext2.lower() for ext2 in rawio.extensions):
311+
possibles.append(rawio)
280312

281-
if len(possibles) == 1:
313+
if len(possibles) == 1 and exclusive_rawio:
282314
return possibles[0]
283-
else:
315+
elif exclusive_rawio:
284316
return None
317+
else:
318+
possibles = [io[0] for io in Counter(possibles).most_common()]
319+
return possibles
320+

neo/rawio/neuralynxrawio/neuralynxrawio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def _parse_header(self):
213213
event_annotations = []
214214

215215
if self.rawmode == "one-dir":
216-
filenames = os.listdir(self.dirname)
216+
filenames = sorted(os.listdir(self.dirname))
217217
else:
218218
filenames = self.include_filenames
219219

neo/rawio/rawbinarysignalrawio.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class RawBinarySignalRawIO(BaseRawIO):
3636
Parameters
3737
----------
3838
filename: str, default: ''
39-
The *.raw or *.bin binary file to load
39+
The *.raw, *.bin, or *.dat binary file to load
4040
dtype: np.dtype, default: 'int16'
4141
The dtype that the data is stored with. Must be acceptable by the numpy.dtype constructor
4242
sampling_rate: float, default: 10000.0
@@ -51,7 +51,7 @@ class RawBinarySignalRawIO(BaseRawIO):
5151
The offset for the bytes
5252
"""
5353

54-
extensions = ["raw", "bin"]
54+
extensions = ["raw", "bin", "dat"]
5555
rawmode = "one-file"
5656

5757
def __init__(

neo/rawio/spikegadgetsrawio.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@
2020
Author: Samuel Garcia
2121
"""
2222

23+
import numpy as np
24+
25+
from xml.etree import ElementTree
26+
2327
from .baserawio import (
2428
BaseRawIO,
2529
_signal_channel_dtype,
2630
_signal_stream_dtype,
2731
_spike_channel_dtype,
2832
_event_channel_dtype,
2933
)
30-
31-
import numpy as np
32-
33-
from xml.etree import ElementTree
34+
from neo.core import NeoReadWriteError
3435

3536

3637
class SpikeGadgetsRawIO(BaseRawIO):
@@ -79,6 +80,24 @@ def __init__(self, filename="", selected_streams=None):
7980
def _source_name(self):
8081
return self.filename
8182

83+
def _produce_ephys_channel_ids(self, n_total_channels, n_channels_per_chip):
84+
"""Compute the channel ID labels
85+
The ephys channels in the .rec file are stored in the following order:
86+
hwChan ID of channel 0 of first chip, hwChan ID of channel 0 of second chip, ..., hwChan ID of channel 0 of Nth chip,
87+
hwChan ID of channel 1 of first chip, hwChan ID of channel 1 of second chip, ..., hwChan ID of channel 1 of Nth chip,
88+
...
89+
So if there are 32 channels per chip and 128 channels (4 chips), then the channel IDs are:
90+
0, 32, 64, 96, 1, 33, 65, 97, ..., 128
91+
See also: https://github.com/NeuralEnsemble/python-neo/issues/1215
92+
"""
93+
ephys_channel_ids_list = []
94+
for hw_channel in range(n_channels_per_chip):
95+
hw_channel_list = [
96+
hw_channel + chip * n_channels_per_chip for chip in range(int(n_total_channels / n_channels_per_chip))
97+
]
98+
ephys_channel_ids_list.append(hw_channel_list)
99+
return [channel for channel_list in ephys_channel_ids_list for channel in channel_list]
100+
82101
def _parse_header(self):
83102
# parse file until "</Configuration>"
84103
header_size = None
@@ -104,6 +123,20 @@ def _parse_header(self):
104123
self._sampling_rate = float(hconf.attrib["samplingRate"])
105124
num_ephy_channels = int(hconf.attrib["numChannels"])
106125

126+
# check for agreement with number of channels in xml
127+
sconf_channels = np.sum([len(x) for x in sconf])
128+
if sconf_channels < num_ephy_channels:
129+
num_ephy_channels = sconf_channels
130+
if sconf_channels > num_ephy_channels:
131+
raise NeoReadWriteError(
132+
"SpikeGadgets: the number of channels in the spike configuration is larger than the number of channels in the hardware configuration"
133+
)
134+
135+
try:
136+
num_chan_per_chip = int(sconf.attrib["chanPerChip"])
137+
except KeyError:
138+
num_chan_per_chip = 32 # default value for Intan chips
139+
107140
# explore sub stream and count packet size
108141
# first bytes is 0x55
109142
packet_size = 1
@@ -174,6 +207,7 @@ def _parse_header(self):
174207
signal_streams.append((stream_name, stream_id))
175208
self._mask_channels_bytes[stream_id] = []
176209

210+
channel_ids = self._produce_ephys_channel_ids(num_ephy_channels, num_chan_per_chip)
177211
chan_ind = 0
178212
self.is_scaleable = "spikeScalingToUv" in sconf[0].attrib
179213
if not self.is_scaleable:
@@ -190,8 +224,8 @@ def _parse_header(self):
190224
units = ""
191225

192226
for schan in trode:
193-
name = "trode" + trode.attrib["id"] + "chan" + schan.attrib["hwChan"]
194-
chan_id = schan.attrib["hwChan"]
227+
chan_id = str(channel_ids[chan_ind])
228+
name = "hwChan" + chan_id
195229

196230
offset = 0.0
197231
signal_channels.append(

0 commit comments

Comments
 (0)