From 0f2e9095e202864611147d68624007179c7cfffc Mon Sep 17 00:00:00 2001 From: Christian Tabedzki <35670232+tabedzki@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:56:15 -0400 Subject: [PATCH] fix: fall back to frame-alignment check when meta lacks fileSizeBytes SpikeGLX writes fileSizeBytes, fileTimeSecs, fileSHA1, and the hardware error-flag summary only in the final step of its shutdown sequence, after acquisition data is fully flushed to disk. If the app is closed or crashes during that finalization step (e.g. while still computing the SHA1 checksum of a large .bin file), the resulting .meta file is missing these trailing fields even though the recorded data itself is complete. This previously caused a hard KeyError in validate_file, crashing EphysRecording ingestion for otherwise-usable recordings (confirmed on two live cases: the .ap.bin files were exact multiples of the per-sample-frame size, with no evidence of truncation). When fileSizeBytes is absent, fall back to checking that the file size divides evenly into whole sample frames (nSavedChans * 2 bytes) instead of failing outright. A genuinely truncated/corrupted file will not be frame-aligned and will still raise. Logs a warning so these recordings remain visible for follow-up. Assisted-by: ClaudeCode:claude-sonnet-5 --- element_array_ephys/readers/spikeglx.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/element_array_ephys/readers/spikeglx.py b/element_array_ephys/readers/spikeglx.py index b8e6d2a0..4dac6956 100644 --- a/element_array_ephys/readers/spikeglx.py +++ b/element_array_ephys/readers/spikeglx.py @@ -174,10 +174,31 @@ def validate_file(self, file_type="ap"): else: raise KeyError(f"Unknown file_type {file_type} - must be 'ap' or 'lf'") - if file_size != meta.meta["fileSizeBytes"]: + expected_file_size = meta.meta.get("fileSizeBytes") + if expected_file_size is not None: + if file_size != expected_file_size: + raise IOError( + f"File size error! {file_path} may be corrupted or in transfer?" + ) + return + + # "fileSizeBytes" is written by SpikeGLX only as one of the last + # steps of its shutdown sequence (after computing "fileSHA1"), so a + # recording whose acquisition finished normally can still be missing + # it if the app was closed/killed before that step completed. Fall + # back to a frame-alignment check: a genuinely truncated/corrupted + # file will not divide evenly into whole sample frames, whereas a + # fully-written recording that only failed to finalize its metadata + # will. + bytes_per_frame = meta.meta["nSavedChans"] * np.dtype("int16").itemsize + if file_size % bytes_per_frame: raise IOError( f"File size error! {file_path} may be corrupted or in transfer?" ) + logger.warning( + f"{meta.fname} is missing 'fileSizeBytes' (recording may not have" + " shut down cleanly) - validated file size by frame alignment instead." + ) def compress(self): from mtscomp import compress as mts_compress