From 3a6d64d93372c055b6042a86192b8e83f462e71b Mon Sep 17 00:00:00 2001 From: Kyu Hyun Lee Date: Wed, 26 Jun 2024 15:41:59 -0700 Subject: [PATCH 1/2] Minimal fix of SGRawIO channel id --- neo/rawio/spikegadgetsrawio.py | 43 +++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/neo/rawio/spikegadgetsrawio.py b/neo/rawio/spikegadgetsrawio.py index cbe9215f6..59af9b10c 100644 --- a/neo/rawio/spikegadgetsrawio.py +++ b/neo/rawio/spikegadgetsrawio.py @@ -79,6 +79,26 @@ def __init__(self, filename="", selected_streams=None): def _source_name(self): return self.filename + def _produce_ephys_channel_ids(self, n_total_channels, n_channels_per_chip): + """Compute the channel ID labels + The ephys channels in the .rec file are stored in the following order: + hwChan ID of channel 0 of first chip, hwChan ID of channel 0 of second chip, ..., hwChan ID of channel 0 of Nth chip, + hwChan ID of channel 1 of first chip, hwChan ID of channel 1 of second chip, ..., hwChan ID of channel 1 of Nth chip, + ... + So if there are 32 channels per chip and 128 channels (4 chips), then the channel IDs are: + 0, 32, 64, 96, 1, 33, 65, 97, ..., 128 + See also: https://github.com/NeuralEnsemble/python-neo/issues/1215 + """ + x = [] + for k in range(n_channels_per_chip): + x.append( + [ + k + i * n_channels_per_chip + for i in range(int(n_total_channels / n_channels_per_chip)) + ] + ) + return [item for sublist in x for item in sublist] + def _parse_header(self): # parse file until "" header_size = None @@ -103,7 +123,21 @@ def _parse_header(self): self._sampling_rate = float(hconf.attrib["samplingRate"]) num_ephy_channels = int(hconf.attrib["numChannels"]) - + + # check for agreement with number of channels in xml + sconf_channels = np.sum([len(x) for x in sconf]) + if sconf_channels < num_ephy_channels: + num_ephy_channels = sconf_channels + if sconf_channels > num_ephy_channels: + raise ValueError( + "SpikeGadgets: the number of channels in the spike configuration is larger than the number of channels in the hardware configuration" + ) + + try: + num_chan_per_chip = int(sconf.attrib["chanPerChip"]) + except KeyError: + num_chan_per_chip = 32 # default value for Intan chips + # explore sub stream and count packet size # first bytes is 0x55 packet_size = 1 @@ -174,6 +208,9 @@ def _parse_header(self): signal_streams.append((stream_name, stream_id)) self._mask_channels_bytes[stream_id] = [] + channel_ids = self._produce_ephys_channel_ids( + num_ephy_channels, num_chan_per_chip + ) chan_ind = 0 self.is_scaleable = "spikeScalingToUv" in sconf[0].attrib if not self.is_scaleable: @@ -190,8 +227,8 @@ def _parse_header(self): units = "" for schan in trode: - name = "trode" + trode.attrib["id"] + "chan" + schan.attrib["hwChan"] - chan_id = schan.attrib["hwChan"] + chan_id = str(channel_ids[chan_ind]) + name = "hwChan" + chan_id offset = 0.0 signal_channels.append( From 38a012b56e96113df80ae128b3436a9860b7b939 Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 10 Jul 2024 08:43:19 -0400 Subject: [PATCH 2/2] clean up PR --- neo/rawio/spikegadgetsrawio.py | 35 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/neo/rawio/spikegadgetsrawio.py b/neo/rawio/spikegadgetsrawio.py index 59af9b10c..8c348d850 100644 --- a/neo/rawio/spikegadgetsrawio.py +++ b/neo/rawio/spikegadgetsrawio.py @@ -20,6 +20,10 @@ Author: Samuel Garcia """ +import numpy as np + +from xml.etree import ElementTree + from .baserawio import ( BaseRawIO, _signal_channel_dtype, @@ -27,10 +31,7 @@ _spike_channel_dtype, _event_channel_dtype, ) - -import numpy as np - -from xml.etree import ElementTree +from neo.core import NeoReadWriteError class SpikeGadgetsRawIO(BaseRawIO): @@ -89,15 +90,13 @@ def _produce_ephys_channel_ids(self, n_total_channels, n_channels_per_chip): 0, 32, 64, 96, 1, 33, 65, 97, ..., 128 See also: https://github.com/NeuralEnsemble/python-neo/issues/1215 """ - x = [] - for k in range(n_channels_per_chip): - x.append( - [ - k + i * n_channels_per_chip - for i in range(int(n_total_channels / n_channels_per_chip)) - ] - ) - return [item for sublist in x for item in sublist] + ephys_channel_ids_list = [] + for hw_channel in range(n_channels_per_chip): + hw_channel_list = [ + hw_channel + chip * n_channels_per_chip for chip in range(int(n_total_channels / n_channels_per_chip)) + ] + ephys_channel_ids_list.append(hw_channel_list) + return [channel for channel_list in ephys_channel_ids_list for channel in channel_list] def _parse_header(self): # parse file until "" @@ -123,13 +122,13 @@ def _parse_header(self): self._sampling_rate = float(hconf.attrib["samplingRate"]) num_ephy_channels = int(hconf.attrib["numChannels"]) - + # check for agreement with number of channels in xml sconf_channels = np.sum([len(x) for x in sconf]) if sconf_channels < num_ephy_channels: num_ephy_channels = sconf_channels if sconf_channels > num_ephy_channels: - raise ValueError( + raise NeoReadWriteError( "SpikeGadgets: the number of channels in the spike configuration is larger than the number of channels in the hardware configuration" ) @@ -137,7 +136,7 @@ def _parse_header(self): num_chan_per_chip = int(sconf.attrib["chanPerChip"]) except KeyError: num_chan_per_chip = 32 # default value for Intan chips - + # explore sub stream and count packet size # first bytes is 0x55 packet_size = 1 @@ -208,9 +207,7 @@ def _parse_header(self): signal_streams.append((stream_name, stream_id)) self._mask_channels_bytes[stream_id] = [] - channel_ids = self._produce_ephys_channel_ids( - num_ephy_channels, num_chan_per_chip - ) + channel_ids = self._produce_ephys_channel_ids(num_ephy_channels, num_chan_per_chip) chan_ind = 0 self.is_scaleable = "spikeScalingToUv" in sconf[0].attrib if not self.is_scaleable: