Skip to content

Auto-Fit

Automatic neuron model fitting to experimental recordings.

Fitter

sc_neurocore.autofit.fitter

Fit SC-NeuroCore neuron models to experimental voltage recordings.

Takes a voltage trace and current injection protocol, sweeps candidate models, optimizes parameters for each, and ranks by fit quality.

Usage

from sc_neurocore.autofit import fit results = fit(voltage_trace, current_trace, dt=0.1, top_k=5) best = results[0] print(f"Best model: {best.model_name}, RMSE: {best.rmse:.4f}")

FittedModel dataclass

Result of fitting one model to experimental data.

Source code in src/sc_neurocore/autofit/fitter.py
29
30
31
32
33
34
35
36
37
38
39
40
41
@dataclass
class FittedModel:
    """Result of fitting one model to experimental data."""

    model_name: str
    model_class: type
    params: dict
    rmse: float
    feature_error: float
    combined_score: float
    simulated_voltage: np.ndarray
    target_features: dict = field(repr=False, default_factory=dict)
    model_features: dict = field(repr=False, default_factory=dict)

fit(voltage, current, dt=0.1, threshold=0.0, candidates=None, top_k=5)

Fit neuron models to an experimental voltage recording.

Parameters

voltage : ndarray Target voltage trace. current : ndarray Injected current trace (same length as voltage). dt : float Timestep in ms. threshold : float Spike detection threshold. candidates : list of str, optional Model names to try. Default: all fittable models. top_k : int Return top K best-fitting models.

Returns

list of FittedModel Sorted by combined_score (lower is better).

Source code in src/sc_neurocore/autofit/fitter.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def fit(
    voltage: np.ndarray,
    current: np.ndarray,
    dt: float = 0.1,
    threshold: float = 0.0,
    candidates: list[str] | None = None,
    top_k: int = 5,
) -> list[FittedModel]:
    """Fit neuron models to an experimental voltage recording.

    Parameters
    ----------
    voltage : ndarray
        Target voltage trace.
    current : ndarray
        Injected current trace (same length as voltage).
    dt : float
        Timestep in ms.
    threshold : float
        Spike detection threshold.
    candidates : list of str, optional
        Model names to try. Default: all fittable models.
    top_k : int
        Return top K best-fitting models.

    Returns
    -------
    list of FittedModel
        Sorted by combined_score (lower is better).
    """
    if candidates is None:
        candidates = _FITTABLE_MODELS

    results = []
    for name in candidates:
        cls = _get_model_class(name)
        if cls is None:
            continue
        try:
            result = _fit_single_model(cls, name, voltage, current, dt, threshold)
            if result is not None:
                results.append(result)
        except (ValueError, TypeError, RuntimeError, ArithmeticError):
            continue

    results.sort(key=lambda r: r.combined_score)
    return results[:top_k]

Feature Extraction

sc_neurocore.autofit.features

Extract electrophysiology features from voltage traces for model fitting.

extract_features(voltage, dt=1.0, threshold=0.0)

Extract standard electrophysiology features from a voltage trace.

Returns dict with

spike_times, spike_count, mean_isi, cv_isi, firing_rate, v_rest, v_max, v_min, ap_height, ap_width

Source code in src/sc_neurocore/autofit/features.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def extract_features(voltage: np.ndarray, dt: float = 1.0, threshold: float = 0.0) -> dict:
    """Extract standard electrophysiology features from a voltage trace.

    Returns dict with:
        spike_times, spike_count, mean_isi, cv_isi, firing_rate,
        v_rest, v_max, v_min, ap_height, ap_width
    """
    spike_times = extract_spike_times(voltage, threshold, dt)
    n_spikes = len(spike_times)
    duration = len(voltage) * dt

    if n_spikes > 1:
        isis = np.diff(spike_times)
        mean_isi = float(np.mean(isis))
        cv_isi = float(np.std(isis) / mean_isi) if mean_isi > 0 else 0.0
    else:
        mean_isi = 0.0
        cv_isi = 0.0

    firing_rate = n_spikes / max(duration, 1e-9)

    # Resting potential: median of subthreshold voltage
    sub = voltage[voltage <= threshold]
    v_rest = float(np.median(sub)) if len(sub) > 0 else float(voltage[0])

    # AP features
    v_max = float(voltage.max())
    v_min = float(voltage.min())
    ap_height = v_max - v_rest

    # AP width: time above threshold at first spike
    ap_width = 0.0
    if n_spikes > 0:
        idx = int(spike_times[0] / dt)
        width_samples = 0
        for j in range(idx, min(idx + 100, len(voltage))):
            if voltage[j] > threshold:
                width_samples += 1  # pragma: no cover
            else:
                break
        ap_width = width_samples * dt

    return {
        "spike_times": spike_times,
        "spike_count": n_spikes,
        "mean_isi": mean_isi,
        "cv_isi": cv_isi,
        "firing_rate": firing_rate,
        "v_rest": v_rest,
        "v_max": v_max,
        "v_min": v_min,
        "ap_height": ap_height,
        "ap_width": ap_width,
    }

extract_spike_times(voltage, threshold=0.0, dt=1.0)

Find spike times from a voltage trace via threshold crossing.

Source code in src/sc_neurocore/autofit/features.py
15
16
17
18
19
def extract_spike_times(voltage: np.ndarray, threshold: float = 0.0, dt: float = 1.0) -> np.ndarray:
    """Find spike times from a voltage trace via threshold crossing."""
    above = voltage > threshold
    crossings = np.where(np.diff(above.astype(int)) > 0)[0]
    return crossings.astype(np.float64) * dt