Skip to content

Commit 71df66e

Browse files
authored
Merge pull request #1832 from h-mayorquin/spikeglx_fix_lack_of_first_sample
Default missing `firstSample` to 0 in SpikeGLX reader
2 parents d4c9d61 + a2e5156 commit 71df66e

1 file changed

Lines changed: 37 additions & 5 deletions

File tree

neo/rawio/spikeglxrawio.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def _source_name(self):
127127
def _parse_header(self):
128128
self.signals_info_list = scan_files(self.dirname)
129129
_add_segment_order(self.signals_info_list)
130+
_add_segment_timing(self.signals_info_list)
130131

131132
# sort stream_name by higher sampling rate first
132133
srates = {info["stream_name"]: info["sampling_rate"] for info in self.signals_info_list}
@@ -269,9 +270,7 @@ def _parse_header(self):
269270
for seg_index in range(nb_segment):
270271
info = self.signals_info_dict[seg_index, stream_name]
271272

272-
frame_start = float(info["meta"]["firstSample"])
273-
sampling_frequency = info["sampling_rate"]
274-
t_start = frame_start / sampling_frequency
273+
t_start = info["t_start"]
275274

276275
self._t_starts[stream_name][seg_index] = t_start
277276

@@ -282,8 +281,7 @@ def _parse_header(self):
282281
self._t_starts[sync_stream_name] = {}
283282
self._t_starts[sync_stream_name][seg_index] = t_start
284283

285-
t_stop = info["sample_length"] / info["sampling_rate"]
286-
self._t_stops[seg_index] = max(self._t_stops[seg_index], t_stop)
284+
self._t_stops[seg_index] = max(self._t_stops[seg_index], info["t_stop"])
287285

288286
# fille into header dict
289287
self.header = {}
@@ -407,6 +405,40 @@ def scan_files(dirname):
407405
return info_list
408406

409407

408+
def _add_segment_timing(info_list):
409+
"""
410+
Add ``info["first_sample"]``, ``info["t_start"]``, and ``info["t_stop"]`` per signal.
411+
412+
Reads ``meta["firstSample"]`` (documented in every SpikeGLX phase) and converts
413+
to float. When absent, defaults to 0 with a UserWarning naming the file. Zero
414+
is the correct fallback for a file that starts at the beginning of its SpikeGLX
415+
run, which covers the expected causes of a missing tag: pre-2016 builds (the
416+
tag was introduced in 2016), recordings where the end-of-run write was
417+
interrupted, and ``.meta`` files modified after acquisition.
418+
419+
Then stores ``info["t_start"] = info["first_sample"] / info["sampling_rate"]``
420+
and ``info["t_stop"] = info["sample_length"] / info["sampling_rate"]`` in
421+
seconds, so downstream code can read both directly without recomputation.
422+
"""
423+
for info in info_list:
424+
meta = info["meta"]
425+
if "firstSample" in meta:
426+
info["first_sample"] = float(meta["firstSample"])
427+
else:
428+
warn(
429+
f"'firstSample' missing from {info['meta_file']}; defaulting to 0. "
430+
f"Zero is correct for files that start at the beginning of their SpikeGLX run, "
431+
f"but wrong for any file that represents a non-initial trigger or that was "
432+
f"derived from a longer recording. Typical causes: pre-2016 SpikeGLX build, "
433+
f"interrupted end-of-run write, or post-acquisition modification of the .meta file.",
434+
UserWarning,
435+
stacklevel=2,
436+
)
437+
info["first_sample"] = 0.0
438+
info["t_start"] = info["first_sample"] / info["sampling_rate"]
439+
info["t_stop"] = info["sample_length"] / info["sampling_rate"]
440+
441+
410442
def _build_signals_info_dict(info_list):
411443
"""
412444
Re-index a flat list of info dicts into a dict keyed by (seg_index, stream_name).

0 commit comments

Comments
 (0)