Saving a multi-segment recording fails when only some segments have a t_start

未关闭 适合新手
#4,914 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@AxelNoun 已经在做这个了。

开始于 2026年11月5日。

  • #4922 来自 @AxelNoun —— 未关闭
  • #4923 来自 @AxelNoun —— 未关闭

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
84/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
numpy, python
领域
backend, data

调研方向

从 issue 中提到的三个构造函数开始:src/spikeinterface/core/numpyextractors.py 中的 NumpyRecording 和 SharedMemoryRecording,以及 src/spikeinterface/core/binaryrecordingextractor.py 中的 BinaryRecordingExtractor。阅读它们各自如何处理 get_segment_t_starts,然后运行复现步骤以及指定的 recording 和 binary extractor 测试。完成标准是二进制保存和内存保存都能保留混合的开始时间和 traces,且不会遗留未释放的共享内存。

由索引模型根据 Issue 内容生成。

描述

core

get_segment_t_starts returns a mixed list such as [None, 100.0] when only some segments have a start time. Three constructors then call float() on every entry. This happens with append_recordings([rec_without_t_start, rec_with_t_start]), and more generally when shift_times has been applied to only some segments.

Reproduction

main at 9ef464e66, Linux (WSL Ubuntu 24.04), Python 3.12.3, numpy 2.5.4, zarr 3.4.2:

import spikeinterface.core as sc

a = sc.generate_recording(num_channels=2, sampling_frequency=1000.0, durations=[3.0], seed=0)
b = sc.generate_recording(num_channels=2, sampling_frequency=1000.0, durations=[2.0], seed=1)
b.shift_times(100.0)
rec = sc.append_recordings([a, b])
print(rec.get_segment_t_starts(), [rec.get_start_time(i) for i in range(2)])

for kwargs in [
    dict(format="binary", folder="rec_binary"),
    dict(format="memory", shared_memory=False),
    dict(format="memory", shared_memory=True),
    dict(format="zarr", folder="rec.zarr"),
]:
    try:
        saved = rec.save(**kwargs)
        print(kwargs["format"], "OK", [saved.get_start_time(i) for i in range(2)])
    except TypeError as ex:
        print(kwargs["format"], f"TypeError: {ex}")
[None, 100.0] [0.0, 100.0]
binary TypeError: float() argument must be a string or a real number, not 'NoneType'
memory TypeError: float() argument must be a string or a real number, not 'NoneType'
memory TypeError: float() argument must be a string or a real number, not 'NoneType'
zarr OK [0.0, array(100.)]

Three details make this worse:

  • Binary: the exception comes after all traces are written. In the example, rec_binary contains traces_cached_seg0.raw and traces_cached_seg1.raw, but neither binary.json nor si_folder.json. For a long recording, this means that hours of writing end in a folder that cannot be loaded.
  • Shared memory: the exception comes before self.main_shm_owner is set, so __del__ raises AttributeError: 'SharedMemoryRecording' object has no attribute 'main_shm_owner', and the segments are not unlinked. The script ends with resource_tracker: There appear to be 4 leaked shared_memory objects to clean up at shutdown.
  • Zarr: saving works, but get_start_time(1) returns a 0-d array(100.) instead of a float.
Cause

get_segment_t_starts only returns None when all segments have no start time. The conversion is in NumpyRecording, SharedMemoryRecording and BinaryRecordingExtractor. In SharedMemoryRecording, main_shm_owner is set after this line, and __del__ reads it.

Possible fix

In the three constructors, keep None:

t_starts = [None if t_start is None else float(t_start) for t_start in t_starts]

With this, the binary and both memory saves work, with start times [0.0, 100.0] and identical traces, and the script no longer reports leaked segments. test_basesorting.py, test_baserecording.py, test_binaryfolder.py, test_binaryrecordingextractor.py and test_numpy_extractors.py still pass (33 tests).

Two separate points would remain: setting main_shm_owner before anything that can fail, so that __del__ is safe after another error, and the 0-d array from zarr. I have not changed them.

I found no report of this case. Nearby: #2515 and #3120 (t_starts lost by save_to_memory) and #3940 (shift_times when t_start is None). I can open a PR with this change and a round-trip test.

主要语言
Python
星标
870
派生
284
平均合并
2 天 3 小时
30 天内合并 PR
40

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

SpikeInterface/spikeinterface 的其他 Issue

查看 SpikeInterface/spikeinterface 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。