Skip to content

Commit af3c631

Browse files
authored
Merge pull request #1652 from h-mayorquin/utility_method_for_times_on_intan
Add utility method to get timestamps on Intan base
2 parents c283c23 + cc050bd commit af3c631

File tree

1 file changed

+55
-19
lines changed

1 file changed

+55
-19
lines changed

neo/rawio/intanrawio.py

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,57 @@ def _demultiplex_digital_data(self, raw_digital_data, channel_ids, i_start, i_st
530530
output[:, channel_index] = demultiplex_data[i_start:i_stop].flatten()
531531

532532
return output
533+
534+
def get_intan_timestamps(self, i_start=None, i_stop=None):
535+
"""
536+
Retrieves the sample indices from the Intan raw data within a specified range.
537+
538+
Note that sample indices are called timestamps in the Intan format but they are
539+
in fact just sample indices. This function extracts the sample index timestamps
540+
from Intan files, which represent relative time points in sample units (not absolute time).
541+
These indices can be particularly useful when working with recordings that have discontinuities.
542+
543+
Parameters
544+
----------
545+
i_start : int, optional
546+
The starting index from which to retrieve sample indices. If None, starts from 0.
547+
i_stop : int, optional
548+
The stopping index up to which to retrieve sample indices (exclusive).
549+
If None, retrieves all available indices from i_start onward.
550+
551+
Returns
552+
-------
553+
timestamps : ndarray
554+
The flattened array of sample indices within the specified range.
555+
556+
Notes
557+
-----
558+
- Sample indices can be converted to seconds by dividing by the sampling rate of the amplifier stream.
559+
- The function automatically handles different file formats:
560+
* header-attached: Timestamps are extracted directly from the timestamp field
561+
* one-file-per-signal: Timestamps are read from the timestamp stream
562+
* one-file-per-channel: Timestamps are read from the first channel in the timestamp stream
563+
- When recordings have discontinuities (indicated by the `discontinuous_timestamps`
564+
attribute being True), these indices allow for proper temporal alignment of the data.
565+
"""
566+
if i_start is None:
567+
i_start = 0
568+
569+
# Get the timestamps based on file format
570+
if self.file_format == "header-attached":
571+
timestamps = self._raw_data["timestamp"]
572+
elif self.file_format == "one-file-per-signal":
573+
timestamps = self._raw_data["timestamp"]
574+
elif self.file_format == "one-file-per-channel":
575+
timestamps = self._raw_data["timestamp"][0]
576+
577+
# TODO if possible ensure that timestamps memmaps are always of correct shape to avoid memory copy here.
578+
timestamps = timestamps.flatten() if timestamps.ndim > 1 else timestamps
579+
580+
if i_stop is None:
581+
return timestamps[i_start:]
582+
else:
583+
return timestamps[i_start:i_stop]
533584

534585
def _assert_timestamp_continuity(self):
535586
"""
@@ -545,26 +596,11 @@ def _assert_timestamp_continuity(self):
545596
NeoReadWriteError
546597
If timestamps are not continuous and `ignore_integrity_checks` is False.
547598
The error message includes a table detailing the discontinuities found.
548-
549-
Notes
550-
-----
551-
The method extracts timestamps from the raw data based on the file format:
552-
553-
* **header-attached:** Timestamps are extracted from a 'timestamp' field in the raw data.
554-
* **one-file-per-signal:** Timestamps are taken from the last stream.
555-
* **one-file-per-channel:** Timestamps are retrieved from the first channel of the last stream.
556599
"""
557600
# check timestamp continuity
558-
if self.file_format == "header-attached":
559-
timestamp = self._raw_data["timestamp"].flatten()
560-
561-
# timestamps are always last stream for headerless binary files
562-
elif self.file_format == "one-file-per-signal":
563-
timestamp = self._raw_data["timestamp"]
564-
elif self.file_format == "one-file-per-channel":
565-
timestamp = self._raw_data["timestamp"][0]
601+
timestamps = self.get_intan_timestamps()
566602

567-
discontinuous_timestamps = np.diff(timestamp) != 1
603+
discontinuous_timestamps = np.diff(timestamps) != 1
568604
timestamps_are_not_contiguous = np.any(discontinuous_timestamps)
569605
if timestamps_are_not_contiguous:
570606
# Mark a flag that can be checked after parsing the header to see if the timestamps are continuous or not
@@ -582,8 +618,8 @@ def _assert_timestamp_continuity(self):
582618

583619
amplifier_sampling_rate = self._global_info["sampling_rate"]
584620
for discontinuity_index in np.where(discontinuous_timestamps)[0]:
585-
prev_ts = timestamp[discontinuity_index]
586-
next_ts = timestamp[discontinuity_index + 1]
621+
prev_ts = timestamps[discontinuity_index]
622+
next_ts = timestamps[discontinuity_index + 1]
587623
time_diff = (next_ts - prev_ts) / amplifier_sampling_rate
588624

589625
error_msg += (

0 commit comments

Comments
 (0)