Skip to content

Legacy WaveformExtractor load path calls set_params before _handle_backward_compatibility_on_load, rejecting deprecated metric names that the compat handler is designed to migrate #4508

@galenlynch

Description

@galenlynch

_read_old_waveforms_extractor_binary in spikeinterface/core/waveforms_extractor_backwards_compatibility.py invokes ext.set_params() (which triggers _set_params() validation) before running ext._handle_backward_compatibility_on_load(). For extensions whose on-disk params.json contains metric names that were renamed in 0.104 (e.g. peak_to_valley, velocity_above, l_ratio, …), validation raises a ValueError before the compat handler ever runs — even though the compat handler exists specifically to migrate those names.

The result is that legacy WaveformExtractor folders that SI is otherwise still able to read cannot be loaded on SI ≥ 0.104 without rewriting params.json on disk, which is not always possible (e.g. Code Ocean data assets are immutable).

Reproduction

Create or obtain a WaveformExtractor folder that has a template_metrics or quality_metrics extension whose params.json contains any deprecated metric name. For example, a template_metrics/params.json with:

{
  "metric_names": ["peak_to_valley", "velocity_above", "velocity_below"],
  "metric_params": {
    "velocity_above": {"min_channels_for_velocity": 3, "min_r2_velocity": 0.5}
  }
}

Then:

import spikeinterface as si
from spikeinterface.core import load_sorting_analyzer_or_waveforms
analyzer = load_sorting_analyzer_or_waveforms("/path/to/legacy_waveforms")

yields:

ValueError: The metric 'peak_to_valley' has been re-named or re-organized.
You can now compute it using the metric name 'peak_to_trough_duration'.
The metric 'velocity_above' has been re-named or re-organized.
You can now compute it using the metric name 'velocity_fits'.
...

Root cause

waveforms_extractor_backwards_compatibility.py:631-637 runs:

new_params = ext._set_params()
updated_params = make_ext_params_up_to_date(ext, params, new_params)
ext.set_params(**updated_params, save=False)        # ← validation fires here
if ext.need_backward_compatibility_on_load:
    ext._handle_backward_compatibility_on_load()    # ← never reached

The deprecated-name validation in BaseMetricExtension._set_params (analyzer_extension_core.py:1127-1134) exists to catch code paths that are constructing extensions with old names at runtime. But it's also triggered on the legacy load path, where the deprecated names come from on-disk params.json — which is exactly the case the per-extension _handle_backward_compatibility_on_load handlers are designed to handle.

ComputeTemplateMetrics._handle_backward_compatibility_on_load (metrics/template/template_metrics.py:100-171) has full, authoritative migration logic for num_positive_peaks/num_negative_peaksnumber_of_peaks, velocity_above/velocity_belowvelocity_fits (including renaming min_channels_for_velocitymin_channels etc. inside metric_params), peak_to_valleypeak_to_trough_duration, and peak_trough_ratiowaveform_ratios. ComputeQualityMetrics._handle_backward_compatibility_on_load does the same for isolation_distance/l_ratiomahalanobis. None of it runs, because validation aborts the load first.

Proposed fix

Catch the deprecation ValueError in _read_old_waveforms_extractor_binary, install the raw params on the extension so the compat handler can see them, run the handler, and replay set_params with the migrated values:

# waveforms_extractor_backwards_compatibility.py, replacing lines 634-637
try:
    ext.set_params(**updated_params, save=False)
except ValueError as e:
    if "has been re-named or re-organized" not in str(e):
        raise
    # Legacy params.json contains deprecated metric names. Install the
    # raw params so _handle_backward_compatibility_on_load can see them,
    # run the compat handler to rename metric_names AND migrate
    # metric_params sub-keys, then replay set_params with migrated values.
    ext.params = dict(updated_params)
    if ext.need_backward_compatibility_on_load:
        ext._handle_backward_compatibility_on_load()
    ext.set_params(**ext.params, save=False)
else:
    if ext.need_backward_compatibility_on_load:
        ext._handle_backward_compatibility_on_load()

The contained change: one function, no signature changes, non-legacy folders take the existing fast path. Happy to send a PR if this approach looks reasonable.

Environment

  • spikeinterface: 0.104.1
  • python: 3.13

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions