Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/caches_cron_job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ jobs:
git config --global user.name "neo CI"
python -m pip install -U pip # Official recommended way
pip install datalad-installer
datalad-installer --sudo ok git-annex --method datalad/packages
# TODO: remove the pin once datalad uploads the git-annex 10.20261006 package (its latest-version points to a missing file)
datalad-installer --sudo ok git-annex=10.20260901 --method datalad/packages
pip install datalad
git config --global filter.annex.process "git-annex filter-process" # recommended for efficiency

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/io-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ jobs:
shell: bash
run: |
pip install datalad-installer
datalad-installer --sudo ok git-annex --method datalad/packages
# TODO: remove the pin once datalad uploads the git-annex 10.20261006 package (its latest-version points to a missing file)
datalad-installer --sudo ok git-annex=10.20260901 --method datalad/packages
git config --global filter.annex.process "git-annex filter-process" # recommended for efficiency

- name: Configure git
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/plexon2-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ jobs:
shell: bash
run: |
pip install datalad-installer
datalad-installer --sudo ok git-annex --method datalad/packages
# TODO: remove the pin once datalad uploads the git-annex 10.20261006 package (its latest-version points to a missing file)
datalad-installer --sudo ok git-annex=10.20260901 --method datalad/packages
git config --global filter.annex.process "git-annex filter-process" # recommended for efficiency

- name: Configure git
Expand Down
2 changes: 1 addition & 1 deletion neo/rawio/plexonrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ def read_as_dict(fid, dtype, offset=None):
DataBlockHeader = [
("Type", "uint16"),
("UpperByteOf5ByteTimestamp", "uint16"),
("TimeStamp", "int32"),
("TimeStamp", "uint32"),
("Channel", "uint16"),
("Unit", "uint16"),
("NumberOfWaveforms", "uint16"),
Expand Down
38 changes: 38 additions & 0 deletions neo/test/rawiotest/test_plexonrawio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import unittest

import numpy as np

from neo.rawio.plexonrawio import PlexonRawIO

from neo.test.rawiotest.common_rawio_test import BaseTestRawIO
Expand All @@ -16,8 +18,44 @@ class TestPlexonRawIO(
"plexon/File_plexon_2.plx",
"plexon/File_plexon_3.plx",
"plexon/4chDemoPLX.plx",
"plexon/plx/timestamp_clock/one_megahertz_forty_bit_timestamps/sorted_spikes_and_strobed_events.plx",
]

def test_timestamps_with_lower_word_above_two_to_the_31(self):
"""The lower 32 bits of the 40-bit block timestamp must be read unsigned.

A PLX block stores its 40-bit timestamp in two fields of its header:

+-------------+------------------------------------+
| upper byte | lower word |
| (8 bits) | (32 bits) |
+-------------+------------------------------------+
how many ticks since the last wrap
wraps (0 to 2 ** 32 - 1, then back to 0)

timestamp = upper * 2 ** 32 + lower
e.g. upper = 1, lower = 4_294_797_782 -> 8_589_765_078 ticks (8589.77 s at 1 MHz)

The reader used to read the lower word as a signed int32, so a lower word at or above 2 ** 31 came out
negative and the timestamp landed 2 ** 32 ticks early. At the usual 40 kHz clock that only happens after
about 15 hours, but this file has a 1 MHz clock (an Offline Sorter import of a Neuralynx recording),
where it starts after 36 minutes and each affected timestamp comes out about 72 minutes early.

The expected values are the spikes of one unit on both sides of the second wrap, at 2 * 2 ** 32 ticks,
decoded from the block headers. The two before the wrap have lower words above 2 ** 31 and are the
ones the signed read moved; the two after it have small lower words and were always right.
"""
filename = self.get_local_path(
"plexon/plx/timestamp_clock/one_megahertz_forty_bit_timestamps/sorted_spikes_and_strobed_events.plx"
)
reader = PlexonRawIO(filename=filename)
reader.parse_header()

ids = list(reader.header["spike_channels"]["id"])
spike_timestamps = reader.get_spike_timestamps(spike_channel_index=ids.index("ch33#1"))
around_second_wrap = spike_timestamps[(spike_timestamps > 8_589_760_000) & (spike_timestamps < 8_590_600_000)]
np.testing.assert_array_equal(around_second_wrap, [8_589_765_078, 8_589_773_845, 8_590_497_111, 8_590_594_911])


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