diff --git a/doc/releases/0.104.1.rst b/doc/releases/0.104.1.rst index 284446e5cc..838b0fa56b 100644 --- a/doc/releases/0.104.1.rst +++ b/doc/releases/0.104.1.rst @@ -3,7 +3,7 @@ SpikeInterface 0.104.1 release notes ------------------------------------ -April 7th 2025 +April 7th 2026 Minor release with bug fixes diff --git a/doc/releases/0.104.2.rst b/doc/releases/0.104.2.rst index c3a660e5f3..c1e6dabd65 100644 --- a/doc/releases/0.104.2.rst +++ b/doc/releases/0.104.2.rst @@ -3,7 +3,7 @@ SpikeInterface 0.104.2 release notes ------------------------------------ -April 20th 2025 +April 20th 2026 Minor release with bug fixes diff --git a/doc/releases/0.104.3.rst b/doc/releases/0.104.3.rst index 68c27b005a..a3a5451a1d 100644 --- a/doc/releases/0.104.3.rst +++ b/doc/releases/0.104.3.rst @@ -3,7 +3,7 @@ SpikeInterface 0.104.3 release notes ------------------------------------ -April 22th 2025 +April 22th 2026 Minor release with bug fixes diff --git a/doc/releases/0.104.4.rst b/doc/releases/0.104.4.rst new file mode 100644 index 0000000000..5ff53932e1 --- /dev/null +++ b/doc/releases/0.104.4.rst @@ -0,0 +1,17 @@ +.. _release0.104.4: + +SpikeInterface 0.104.4 release notes +------------------------------------ + +June 10th 2026 + +Minor release with bug fixes + +preprocessing: + +* Hotfix: overflow error in correct LSB (#4610) + + +Contributors: + +* @alejoe91 diff --git a/doc/whatisnew.rst b/doc/whatisnew.rst index f7fca01d94..10bfc63b3a 100644 --- a/doc/whatisnew.rst +++ b/doc/whatisnew.rst @@ -55,6 +55,12 @@ Release notes releases/0.9.9.rst releases/0.9.1.rst + +Version 0.104.4 +=============== + +* Minor release with bug fixes + Version 0.104.3 =============== diff --git a/src/spikeinterface/preprocessing/correct_lsb.py b/src/spikeinterface/preprocessing/correct_lsb.py index b0df95213b..cb0d3b40d4 100644 --- a/src/spikeinterface/preprocessing/correct_lsb.py +++ b/src/spikeinterface/preprocessing/correct_lsb.py @@ -10,6 +10,11 @@ def correct_lsb(recording, num_chunks_per_segment=20, chunk_size=10000, seed=Non Estimates the LSB of the recording and divide traces by LSB to ensure LSB = 1. Medians are also subtracted to avoid rounding errors. + Since the LSB is set at the acquisition level, it is shared across channels. The global + LSB is therefore estimated as the mode (most frequent value) of the per-channel LSBs, + which is robust both to undersampled channels (that overestimate the LSB) and to + spuriously small per-channel values arising from rounding errors. + Parameters ---------- recording : RecordingExtractor @@ -64,7 +69,12 @@ def correct_lsb(recording, num_chunks_per_segment=20, chunk_size=10000, seed=Non def _estimate_lsb_from_data(data): """ - Estimate LSB by taking the minimum LSB found across channels. + Estimate the global LSB as the mode of the per-channel LSBs. + + For each channel the LSB is the smallest difference between consecutive unique values. + Since the LSB is shared across channels, the global estimate is the most frequent + per-channel value, which is robust to undersampled channels (that overestimate the LSB) + and to spuriously small values from rounding errors. Parameters ---------- @@ -76,20 +86,23 @@ def _estimate_lsb_from_data(data): lsb_value : int The estimated LSB value. -1 indicates that lsb could not be estimated """ - lsb_value = None num_channels = data.shape[1] + per_channel_lsb = [] for ch in np.arange(num_channels): - unique_values = np.unique(data[:, ch]) + # cast to int64 to avoid integer overflow in np.diff when consecutive + # unique values are farther apart than the dtype range (e.g. int16) + unique_values = np.unique(data[:, ch]).astype(np.int64) # It might happen that there is a single unique value (e.g. a channel is broken, or all zeros) if len(unique_values) > 1: - chan_lsb_val = np.min(np.diff(unique_values)) + per_channel_lsb.append(np.min(np.diff(unique_values))) else: # in this case we can't estimate the LSB for the channel continue - if lsb_value is None: - lsb_value = chan_lsb_val - lsb_value = min(lsb_value, chan_lsb_val) - if lsb_value is None: - lsb_value = -1 + if len(per_channel_lsb) == 0: + return -1 + + # mode: most frequent per-channel LSB (ties broken towards the smaller value) + values, counts = np.unique(per_channel_lsb, return_counts=True) + lsb_value = int(values[np.argmax(counts)]) return lsb_value