Skip to content

Probes API

Probe operators and multimodal readout channels for neural signals.

Overview

Probe operators extract various readout modalities from neural simulation: - Spike-based: Spike detection (SPK) from membrane voltage - Voltage-based: Direct voltage readouts (Vm) - Field-based: lfp_proxy and csd_proxy from spatial current distributions - Sensor-based: eeg_proxy and meg_proxy from head/field proxy projection operators (computational scaffold; no clinical forward-model claim) - Activity cost: emm_proxy signaling-energy estimation

All operators return proxy readouts suitable for tutorial simulations, designed for exploratory workflows.


Probe Operators (Eight Kinds)

1. SPK: Spike Detection

cfg = cfg.probes(["SPK"])

Math form: \(\mathrm{SPK}_n = \mathbb{1}[V_n(t) \geq \theta_n]\)

Output: Binary spike raster [time, neurons]

Parameters: - threshold (float): Spike threshold (default: 30 mV for Izhikevich)

Status: Simulated proxy; threshold-based detection

Units: Binary (0/1)

Example:

signals = jtfne.simulate(model, duration_ms=1000.0, dt_ms=0.1, seed=7)
spikes = signals.spikes  # [time, neurons]
spike_rate_hz = spikes.mean(axis=0) * 1000.0 / dt_ms


2. Vm: Membrane Voltage

cfg = cfg.probes(["Vm"])

Math form: \(\mathrm{Vm}_n(t) = V_n(t)\)

Output: Voltage trace [time, neurons]

Status: Direct state readout from Izhikevich emitter

Units: mV (millivolts)

Range: Typically -90 to 30 mV

Example:

V_m = signals.V_m  # [time, neurons]
mean_voltage = V_m.mean()
voltage_std = V_m.std()


3. source: Transmembrane Current

cfg = cfg.probes(["source"])

Math form: \(S_n(t) = f_{\mathrm{source}}(I_{\mathrm{mem}}, \text{anatomy})\)

Output: Source density [time, spatial locations]

Description: Projects Izhikevich transmembrane currents into space using anatomical mapping. This feeds field-proxy operators (LFP-proxy, CSD-proxy); no PDE field solver is invoked in v0.3.x.

Status: Spatial projection proxy

Units: Current density (relative Izhikevich units, not µA/mm³)

Example:

source = signals.source  # [time, locations]


4. lfp_proxy: Local Field Potential

cfg = cfg.probes(["lfp_proxy"])

Math form: \(\phi_{\mathrm{proxy}}(t,c) = \sum_{n=1}^{N} W_{cn} S_n(t)\)

where \(W_{cn}\) is row-normalized source-to-contact mapping.

Output: Potential at recording contacts [time, contacts]

Status: Proxy convolution; no PDE solve

Units: Proxy voltage units (unscaled). Calibrated mV requires separate calibration evidence; not claimed by default.

Description: Extracellular potential sampled at electrode contacts. Computed via weighted summation of sources with spatial weighting. Approximates field without solving Poisson equation.

Example:

LFP = signals.LFP  # [time, n_contacts]
LFP_mean = LFP.mean(axis=0)  # Mean per contact


5. csd_proxy: Current Source Density

cfg = cfg.probes(["csd_proxy"])

Math form: \(\mathrm{CSD}_{\mathrm{proxy}}(t,c) = \frac{\phi(t,c+1) - 2\phi(t,c) + \phi(t,c-1)}{(\Delta z)^2}\)

Output: Current source density [time, locations]

Status: Proxy second spatial derivative

Units: Proxy current density units (relative)

Description: Estimate of inward/outward transmembrane current at each depth. Computed as second spatial derivative of LFP. Sign convention: positive CSD = extracellular source = inward current.

Example:

CSD = signals.CSD  # [time, n_locations]
CSD_positive = (CSD > 0).sum()  # Count source voxels


6. eeg_proxy: Electroencephalogram

cfg = cfg.probes(["eeg_proxy"])

Math form: \(Y_{\mathrm{EEG}}(t, e) = \sum_{c=1}^{C} L_{ec} \phi_{\mathrm{proxy}}(t,c)\)

Output: Scalp electrode readings [time, n_eeg_channels]

Status: Toy head model projection; no volumetric conductivity

Units: Proxy voltage units (relative mV)

Description: Projects laminar LFP to scalp electrodes using a simplified lead-field matrix. Not a realistic head model; suitable for relative visualization.

Example:

EEG = signals.EEG  # [time, n_eeg_channels]


7. meg_proxy: Magnetoencephalogram

cfg = cfg.probes(["meg_proxy"])

Math form: \(Y_{\mathrm{MEG}}(t, m) = \sum_{n,s} L_{m,ns} o_n S_n(t)\)

Output: Magnetometer readings [time, n_meg_channels]

Status: Toy dipole model; no volume conductor

Units: Proxy magnetic field units (relative to source)

Description: Estimates magnetic field from dipoles with orientation weighting. Each source contributes based on its orientation relative to magnetometer. Simplified model for visualization.

Example:

MEG = signals.MEG  # [time, n_meg_channels]


8. emm_proxy: Energetic/Metabolic Activity Metric

cfg = cfg.probes(["emm_proxy"])

Math form: \(\mathrm{EMM}(t) = \alpha \|S(t)\|_1 + \beta \|\phi(t)\|_1\)

Output: Single activity metric [time]

Status: Proxy cost function for exploratory analysis; signaling-energy proxy

Units: Relative activity intensity (no physical units)

Description: Estimates relative energy cost of network activity. Combines source magnitude (ion pump cost) and field magnitude (ATP for signal propagation). Weighted by parameters α and β.

Example:

EMM = signals.EMM  # [time]
activity = EMM.mean()  # Mean metabolic proxy over time


Probe Specifications

Declaring Multiple Probes

cfg = cfg.probes([
    "SPK",
    "Vm",
    "source",
    "LFP-proxy",
    "CSD-proxy",
    "EEG-proxy",
    "MEG-proxy",
    "EMM-proxy"
])

Or selectively:

cfg = cfg.probes(["MUA-proxy", "LFP-proxy", "CSD-proxy"])

Probe Report Structure

Each probe operator returns a JSON-safe report:

@dataclass
class ProbeReport:
    kind: str  # "spk" | "vm" | "source" | "lfp_proxy" | ...
    method: str  # Computation method
    units_or_status: str  # Units or proxy declaration
    operator_status: str  # "simulated_proxy" for all v0.2.x
    amplitude_status: bool  # Always false for proxy

Example:

{
  "kind": "lfp_proxy",
  "method": "point_or_finite_contact_phi_proxy",
  "units_or_status": "proxy_voltage_units",
  "operator_status": "simulated_proxy",
  "amplitude_status": false
}


Statement Boundaries

⚠️ All probe operators are computational proxies:

  • No empirical validation: Results are simulated, not measured
  • No physical amplitude: Cannot statement mV or µV units without calibration
  • Relative metrics only: Use for comparative analysis, not absolute scaling
  • Sign conventions declared: CSD+ = inward current (extracellular source)
  • Spatial approximations: Field solvers use convolution, not full PDE

Safe statements: - "Spike rate increased by 20%" - "LFP magnitude varies with depth" - "EMM proxy indicates higher activity"

Unsafe statements: - "LFP amplitude is 50 µV" - "CSD source is located at 400 µm depth" (localization not solved) - "EEG matches real recordings" (without validation)


Readout Metrics

Available Metrics (from ReadoutSpec)

Metric Operator Description
spike_rate_hz SPK Mean firing rate (Hz)
burst_frequency_hz SPK Burst rate estimate
max_spike_rate_hz SPK Peak firing rate
mean_V_m Vm Mean membrane voltage (mV)
min_V_m Vm Min voltage (mV)
max_V_m Vm Max voltage (mV)
mean_source source Mean source magnitude
mean_LFP LFP-proxy Mean LFP-proxy magnitude (unscaled)
mean_CSD CSD-proxy Mean CSD-proxy magnitude (unscaled)
mean_EEG EEG-proxy Mean EEG-proxy magnitude (unscaled)
mean_MEG MEG-proxy Mean MEG-proxy magnitude (unscaled)
mean_EMM EMM-proxy Mean metabolic proxy

Example:

readouts = model.compute_readout(signals, [
    jtfne.readout_spec("firing_rate", "spike_rate_hz"),
    jtfne.readout_spec("voltage", "mean_V_m"),
    jtfne.readout_spec("field_strength", "mean_LFP")
])
print(readouts.results)


JSON Serialization

All probe outputs must be JSON-safe:

import json
from jaxfne.io import json_safe

signals_dict = json_safe(signals.to_dict())
json.dumps(signals_dict, allow_nan=False)  # Must not raise

NaN or Inf values in signals will fail serialization.


See also