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

Minimal fix of SpikeGadgetsRawIO channel ID issue #1496

Merged
merged 3 commits into from
Jul 24, 2024
Merged
Changes from 1 commit
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
43 changes: 40 additions & 3 deletions neo/rawio/spikegadgetsrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
zm711 marked this conversation as resolved.
Show resolved Hide resolved

def _parse_header(self):
# parse file until "</Configuration>"
header_size = None
Expand All @@ -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:
Copy link

@jnjnnjzch jnjnnjzch Nov 30, 2024

Choose a reason for hiding this comment

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

Sorry to interrupt, but is that necessarry to add a comparing that num_chan_per_chip > num_ephy_channels?
I got a recording recently and found that num_chan_per_chip is ocasionally read as 339134394.
I manage to extract the xml file and see
<SpikeConfiguration categories="" chanPerChip="339134394" device="intan">
which implies some error...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do you mean the workspace file you use to collect data has chanPerChip="339134394"?

Copy link

@jnjnnjzch jnjnnjzch Dec 6, 2024

Choose a reason for hiding this comment

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

No, we are using the workspace file with chanPerChip="32", but when we check the output .rec file, it becomes chanPerChip="339134394". We're still trying to figure out what's going on...

Update:
We find out this is a software bug for Trodes. The workspace file should be saved and re-opened before recording. A direct creation and use of workspace file causes this error.

raise ValueError(
zm711 marked this conversation as resolved.
Show resolved Hide resolved
"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
Copy link
Contributor

Choose a reason for hiding this comment

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

Intan also has 64 and 16 channel headstages. Where is this 32 coming from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

most spikegadgets products use 64 channel chips, but a 64 channel chip is just two 32 channel chips sharing an output pin so this is a reasonable default for most cases.

Copy link

@jnjnnjzch jnjnnjzch Nov 30, 2024

Choose a reason for hiding this comment

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

most spikegadgets products use 64 channel chips, but a 64 channel chip is just two 32 channel chips sharing an output pin so this is a reasonable default for most cases.

Again, sorry to interrupt you, @khl02007 ... Is this still work on signal channel level? I mean in practice, does it mean there is no need to change num_chan_per_chip manually into 64 even we know we are using a 64 channel chip, but the header_text xml extracts as sconf.attrib["chanPerChip"] = 32?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jnjnnjzch if you are using this product then yes, no need to change num_chan_per_chip. If you're using some other product, I'm actually not sure what would happen here...

Choose a reason for hiding this comment

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

Thanks! we are exactly using this product! And your opinion is validated again by checking the signal synchronization of the extracted trace. Thanks a lot!


# explore sub stream and count packet size
# first bytes is 0x55
packet_size = 1
Expand Down Expand Up @@ -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:
Expand All @@ -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(
Expand Down
Loading