Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for missing channels in SpikeGadgets #1593

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions neo/rawio/spikegadgetsrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ 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):
def _produce_ephys_channel_ids(self, n_total_channels, n_channels_per_chip, missing_hw_chans):
pauladkisson marked this conversation as resolved.
Show resolved Hide resolved
"""Compute the channel ID labels for subset of spikegadgets recordings
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,
Expand All @@ -94,14 +94,18 @@ def _produce_ephys_channel_ids(self, n_total_channels, n_channels_per_chip):
This doesn't work for all types of spikegadgets
see: https://github.com/NeuralEnsemble/python-neo/issues/1517

If there are any missing hardware channels, they must be specified in missing_hw_chans.
See: https://github.com/NeuralEnsemble/python-neo/issues/1592
"""
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]
for local_hw_channel in range(n_channels_per_chip):
n_chips = int(n_total_channels / n_channels_per_chip)
for chip in range(n_chips):
global_hw_chan = local_hw_channel + chip * n_channels_per_chip
if global_hw_chan in missing_hw_chans:
continue
ephys_channel_ids_list.append(local_hw_channel + chip * n_channels_per_chip)
return ephys_channel_ids_list

def _parse_header(self):
# parse file until "</Configuration>"
Expand All @@ -126,7 +130,8 @@ def _parse_header(self):
sconf = root.find("SpikeConfiguration")

self._sampling_rate = float(hconf.attrib["samplingRate"])
num_ephy_channels = int(hconf.attrib["numChannels"])
num_ephy_channels_xml = int(hconf.attrib["numChannels"])
num_ephy_channels = num_ephy_channels_xml

# check for agreement with number of channels in xml
sconf_channels = np.sum([len(x) for x in sconf])
Expand Down Expand Up @@ -220,7 +225,9 @@ def _parse_header(self):
# we can only produce these channels for a subset of spikegadgets setup. If this criteria isn't
# true then we should just use the raw_channel_ids and let the end user sort everything out
if num_ephy_channels % num_chan_per_chip == 0:
channel_ids = self._produce_ephys_channel_ids(num_ephy_channels, num_chan_per_chip)
all_hw_chans = [int(schan.attrib["hwChan"]) for trode in sconf for schan in trode]
missing_hw_chans = set(range(num_ephy_channels)) - set(all_hw_chans)
channel_ids = self._produce_ephys_channel_ids(num_ephy_channels_xml, num_chan_per_chip, missing_hw_chans)
raw_channel_ids = False
else:
raw_channel_ids = True
Expand Down
33 changes: 33 additions & 0 deletions neo/test/rawiotest/test_spikegadgetsrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from neo.rawio.spikegadgetsrawio import SpikeGadgetsRawIO
from neo.test.rawiotest.common_rawio_test import BaseTestRawIO
from numpy.testing import assert_array_equal


class TestSpikeGadgetsRawIO(
Expand All @@ -16,6 +17,38 @@ class TestSpikeGadgetsRawIO(
"spikegadgets/SpikeGadgets_test_data_2xNpix1.0_20240318_173658.rec",
]

class TestSpikeGadgetsRawIOHeaderOnly(unittest.TestCase):
def setUp(self):
filename = "/Volumes/T7/CatalystNeuro/Jadhav/stubbed_files/SL18_D19_S01_F01_BOX_SLP_20230503_112642_stubbed.rec"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something tells me this line won't work for the CI

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will need to be updated once the test file is up on the gin repo.

self.rawio = SpikeGadgetsRawIO(filename=filename)

def test_parse_header(self):
self.rawio.parse_header()
assert_array_equal(
self.rawio.header['signal_channels']['id'],
[
'ECU_Ain1', 'ECU_Ain2', 'ECU_Ain3', 'ECU_Ain4', 'ECU_Ain5', 'ECU_Ain6',
'ECU_Ain7', 'ECU_Ain8', 'ECU_Aout1', 'ECU_Aout2', 'ECU_Aout3', 'ECU_Aout4', '0',
'32', '96', '160', '192', '224', '1', '33', '65', '97', '161', '193', '225', '2', '34',
'98', '162', '194', '226', '3', '35', '67', '99', '163', '195', '227', '4', '36',
'100', '164', '196', '228', '5', '37', '69', '101', '165', '197', '229', '6', '38',
'102', '166', '198', '230', '7', '39', '71', '103', '167', '199', '231', '8', '40',
'72', '104', '136', '168', '200', '232', '9', '41', '73', '105', '137', '169', '201',
'233', '10', '42', '74', '106', '138', '170', '202', '234', '11', '43', '75', '107',
'139', '171', '203', '235', '12', '44', '76', '108', '140', '172', '204', '236', '13',
'45', '77', '109', '141', '173', '205', '237', '14', '46', '78', '110', '142', '174',
'206', '238', '15', '47', '79', '111', '143', '175', '207', '239', '80', '144', '176',
'208', '240', '17', '49', '81', '145', '177', '209', '241', '82', '146', '178', '210',
'242', '19', '51', '83', '147', '179', '211', '243', '84', '148', '180', '212', '244',
'21', '53', '85', '149', '181', '213', '245', '86', '150', '182', '214', '246', '23',
'55', '87', '151', '183', '215', '247', '24', '56', '88', '152', '184', '216', '248',
'25', '57', '89', '121', '153', '185', '217', '249', '26', '58', '90', '154', '186',
'218', '250', '27', '59', '91', '123', '155', '187', '219', '251', '28', '60', '92',
'156', '188', '220', '252', '29', '61', '93', '125', '157', '189', '221', '253', '30',
'62', '94', '158', '190', '222', '254', '31', '63', '95', '127', '159', '191', '223',
'255',
]
)

if __name__ == "__main__":
unittest.main()