From a9d94995af0374ea0451b36583ba649c02b4bde8 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Mon, 20 Apr 2026 13:37:53 -0600 Subject: [PATCH 1/5] Make empty first_sample equal 0 --- neo/rawio/spikeglxrawio.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/neo/rawio/spikeglxrawio.py b/neo/rawio/spikeglxrawio.py index ae9a45e9b..1d9911bcc 100644 --- a/neo/rawio/spikeglxrawio.py +++ b/neo/rawio/spikeglxrawio.py @@ -126,6 +126,7 @@ def _source_name(self): def _parse_header(self): self.signals_info_list = scan_files(self.dirname) + _add_first_sample(self.signals_info_list) _add_segment_order(self.signals_info_list) # sort stream_name by higher sampling rate first @@ -274,7 +275,7 @@ def _parse_header(self): for seg_index in range(nb_segment): info = self.signals_info_dict[seg_index, stream_name] - frame_start = float(info["meta"]["firstSample"]) + frame_start = info["first_sample"] sampling_frequency = info["sampling_rate"] t_start = frame_start / sampling_frequency @@ -412,6 +413,30 @@ def scan_files(dirname): return info_list +def _add_first_sample(info_list): + """ + Add ``info["first_sample"]`` for each signal in ``info_list``. + + Reads ``meta["firstSample"]`` (documented in every SpikeGLX phase) and converts + to float. When absent, defaults to 0 with a UserWarning naming the file: + older Phase 3A builds and some third-party rewritten .meta files omit this + field, and 0 is the correct fallback for a binary that starts at the + beginning of its run. + """ + for info in info_list: + meta = info["meta"] + if "firstSample" in meta: + info["first_sample"] = float(meta["firstSample"]) + else: + warn( + f"'firstSample' missing from {info['meta_file']}; " + f"defaulting to 0. t_start for this stream/segment will be 0 s.", + UserWarning, + stacklevel=2, + ) + info["first_sample"] = 0.0 + + def _add_segment_order(info_list): """ Uses gate and trigger numbers to construct a segment index (`seg_index`) for each signal in `info_list`. From a5364084bee671b8bbf53afd10f2931d1536ce81 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 07:44:29 -0600 Subject: [PATCH 2/5] improve warning --- neo/rawio/spikeglxrawio.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/neo/rawio/spikeglxrawio.py b/neo/rawio/spikeglxrawio.py index 1d9911bcc..698807bd4 100644 --- a/neo/rawio/spikeglxrawio.py +++ b/neo/rawio/spikeglxrawio.py @@ -418,10 +418,11 @@ def _add_first_sample(info_list): Add ``info["first_sample"]`` for each signal in ``info_list``. Reads ``meta["firstSample"]`` (documented in every SpikeGLX phase) and converts - to float. When absent, defaults to 0 with a UserWarning naming the file: - older Phase 3A builds and some third-party rewritten .meta files omit this - field, and 0 is the correct fallback for a binary that starts at the - beginning of its run. + to float. When absent, defaults to 0 with a UserWarning naming the file. Zero + is the correct fallback for a binary that starts at the beginning of its run, + which covers the expected causes of a missing tag: pre-2016 SpikeGLX builds + (the tag was introduced in 2016), recordings where the end-of-run write was + interrupted, and `.meta` files modified after acquisition. """ for info in info_list: meta = info["meta"] @@ -429,8 +430,11 @@ def _add_first_sample(info_list): info["first_sample"] = float(meta["firstSample"]) else: warn( - f"'firstSample' missing from {info['meta_file']}; " - f"defaulting to 0. t_start for this stream/segment will be 0 s.", + f"'firstSample' missing from {info['meta_file']}; defaulting to 0. " + f"Zero is correct for files that start at the beginning of their SpikeGLX run, " + f"but wrong for any file that represents a non-initial trigger or that was " + f"derived from a longer recording. Typical causes: pre-2016 SpikeGLX build, " + f"interrupted end-of-run write, or post-acquisition modification of the .meta file.", UserWarning, stacklevel=2, ) From 349c018402741084ab3bf1c033781bef77584eec Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 13:13:19 -0600 Subject: [PATCH 3/5] add segment timing --- neo/rawio/spikeglxrawio.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/neo/rawio/spikeglxrawio.py b/neo/rawio/spikeglxrawio.py index cbd5142a3..c0127d54d 100644 --- a/neo/rawio/spikeglxrawio.py +++ b/neo/rawio/spikeglxrawio.py @@ -126,8 +126,8 @@ def _source_name(self): def _parse_header(self): self.signals_info_list = scan_files(self.dirname) - _add_first_sample(self.signals_info_list) _add_segment_order(self.signals_info_list) + _add_segment_timing(self.signals_info_list) # sort stream_name by higher sampling rate first srates = {info["stream_name"]: info["sampling_rate"] for info in self.signals_info_list} @@ -270,9 +270,7 @@ def _parse_header(self): for seg_index in range(nb_segment): info = self.signals_info_dict[seg_index, stream_name] - frame_start = info["first_sample"] - sampling_frequency = info["sampling_rate"] - t_start = frame_start / sampling_frequency + t_start = info["t_start"] self._t_starts[stream_name][seg_index] = t_start @@ -408,16 +406,19 @@ def scan_files(dirname): return info_list -def _add_first_sample(info_list): +def _add_segment_timing(info_list): """ - Add ``info["first_sample"]`` for each signal in ``info_list``. + Add ``info["first_sample"]`` and ``info["t_start"]`` per signal. Reads ``meta["firstSample"]`` (documented in every SpikeGLX phase) and converts to float. When absent, defaults to 0 with a UserWarning naming the file. Zero - is the correct fallback for a binary that starts at the beginning of its run, - which covers the expected causes of a missing tag: pre-2016 SpikeGLX builds - (the tag was introduced in 2016), recordings where the end-of-run write was - interrupted, and `.meta` files modified after acquisition. + is the correct fallback for a file that starts at the beginning of its SpikeGLX + run, which covers the expected causes of a missing tag: pre-2016 builds (the + tag was introduced in 2016), recordings where the end-of-run write was + interrupted, and ``.meta`` files modified after acquisition. + + Then stores ``info["t_start"] = info["first_sample"] / info["sampling_rate"]`` + in seconds, so downstream code can read it directly without recomputation. """ for info in info_list: meta = info["meta"] @@ -434,6 +435,7 @@ def _add_first_sample(info_list): stacklevel=2, ) info["first_sample"] = 0.0 + info["t_start"] = info["first_sample"] / info["sampling_rate"] def _build_signals_info_dict(info_list): From d7ceda4f52cb34b5768c87957046750bcac0ee21 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 13:33:39 -0600 Subject: [PATCH 4/5] add stop time --- neo/rawio/spikeglxrawio.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/neo/rawio/spikeglxrawio.py b/neo/rawio/spikeglxrawio.py index c0127d54d..072d000ed 100644 --- a/neo/rawio/spikeglxrawio.py +++ b/neo/rawio/spikeglxrawio.py @@ -408,7 +408,7 @@ def scan_files(dirname): def _add_segment_timing(info_list): """ - Add ``info["first_sample"]`` and ``info["t_start"]`` per signal. + Add ``info["first_sample"]``, ``info["t_start"]``, and ``info["t_stop"]`` per signal. Reads ``meta["firstSample"]`` (documented in every SpikeGLX phase) and converts to float. When absent, defaults to 0 with a UserWarning naming the file. Zero @@ -418,7 +418,8 @@ def _add_segment_timing(info_list): interrupted, and ``.meta`` files modified after acquisition. Then stores ``info["t_start"] = info["first_sample"] / info["sampling_rate"]`` - in seconds, so downstream code can read it directly without recomputation. + and ``info["t_stop"] = info["sample_length"] / info["sampling_rate"]`` in + seconds, so downstream code can read both directly without recomputation. """ for info in info_list: meta = info["meta"] @@ -436,6 +437,7 @@ def _add_segment_timing(info_list): ) info["first_sample"] = 0.0 info["t_start"] = info["first_sample"] / info["sampling_rate"] + info["t_stop"] = info["sample_length"] / info["sampling_rate"] def _build_signals_info_dict(info_list): From a2e5156890a2887bb1aefd5bdbbd837def1100be Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 13:39:08 -0600 Subject: [PATCH 5/5] add t-stop --- neo/rawio/spikeglxrawio.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neo/rawio/spikeglxrawio.py b/neo/rawio/spikeglxrawio.py index 072d000ed..d59c2aa94 100644 --- a/neo/rawio/spikeglxrawio.py +++ b/neo/rawio/spikeglxrawio.py @@ -281,8 +281,7 @@ def _parse_header(self): self._t_starts[sync_stream_name] = {} self._t_starts[sync_stream_name][seg_index] = t_start - t_stop = info["sample_length"] / info["sampling_rate"] - self._t_stops[seg_index] = max(self._t_stops[seg_index], t_stop) + self._t_stops[seg_index] = max(self._t_stops[seg_index], info["t_stop"]) # fille into header dict self.header = {}