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

Abierto Apto para principiantes
#4,914 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@AxelNoun ya está trabajando en esto.

Desde el 5/11/2026.

  • #4922 de @AxelNoun — abierto
  • #4923 de @AxelNoun — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
84/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
numpy, python
Área
backend, data

Línea de trabajo

Empieza por los tres constructores mencionados en el issue: NumpyRecording y SharedMemoryRecording en src/spikeinterface/core/numpyextractors.py, y BinaryRecordingExtractor en src/spikeinterface/core/binaryrecordingextractor.py. Lee cómo gestiona cada uno get_segment_t_starts y, después, ejecuta la reproducción y las pruebas indicadas para recording y binary extractor. Se considera terminado cuando las grabaciones binarias y en memoria conservan los tiempos de inicio mixtos y los traces sin dejar shared memory sin liberar.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
Python
Estrellas
870
Forks
284
Merge medio
3 d 1 h
PR fusionados (30 d)
41

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de SpikeInterface/spikeinterface

Todos los issues de SpikeInterface/spikeinterface

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.