# Synaptipy User Tutorial and Documentation

Synaptipy is a comprehensive electrophysiology visualisation and analysis suite.
The primary focus is whole-cell patch-clamp and intracellular recordings; however,
any electrophysiology signal whose file format is supported by the
[Neo](https://neo.readthedocs.io/en/latest/) I/O library can be loaded, visualised, and processed.
Built on Python and Qt6, it provides OpenGL-accelerated signal visualisation,
17 built-in analysis modules, a composable batch processing engine, and an extensible
plugin interface. This tutorial covers every feature of the application in detail,
including the mathematical methods used in each analysis module.

```{note}
Screenshots in this tutorial reflect the current release.  Where a screenshot
shows a UI element that differs from what you see, the behaviour described in
the text is authoritative.  The **Manual Trial Averaging** panel (Section 1.3)
and the **Analysis Selection** panel (Section 1.6) were updated in v0.1.5b6:
the "Selected:" label now shows only trials queued for cross-file analysis
(not the display-only "Plot Selected Trials" value), and the "Mark Current
Trial" workflow requires **Cycle Single Trial** plot mode.
```

---

## Table of Contents

1. [The Explorer Tab](#1-the-explorer-tab)
2. [Signal Preprocessing](#2-signal-preprocessing)
3. [The Analyser Tab](#3-the-analyser-tab)
   - [3.6 Adding Your Own Analysis Tab](#36-adding-your-own-analysis-tab)
4. [Analysis Modules - Detailed Reference](#4-analysis-modules--detailed-reference)
   - [4.1 Baseline / RMP](#41-baseline--rmp-analysis)
   - [4.2 Spike Detection](#42-spike-detection)
   - [4.3 Event Detection - Threshold Based](#43-event-detection--threshold-based)
   - [4.4 Event Detection - Template Match](#44-event-detection--template-match--deconvolution)
   - [4.5 Event Detection - Baseline Peak](#45-event-detection--baseline-peak--kinetics)
   - [4.6 Input Resistance](#46-input-resistance-rin)
   - [4.7 Tau (Time Constant)](#47-tau-membrane-time-constant)
   - [4.8 I-V Curve](#48-i-v-curve)
   - [4.9 Burst Analysis](#49-burst-analysis)
   - [4.10 Capacitance](#410-capacitance)
   - [4.11 Excitability (F-I Curve)](#411-excitability-f-i-curve)
   - [4.12 Optogenetic Synchronization](#412-optogenetic-synchronization)
   - [4.13 Phase Plane Analysis](#413-phase-plane-analysis)
   - [4.14 Spike Train Dynamics](#414-spike-train-dynamics)
   - [4.15 Sag Ratio (I_h)](#415-sag-ratio-i_h)
5. [Batch Processing](#5-batch-processing)
6. [The Exporter Tab](#6-the-exporter-tab)
7. [Preferences and Customization](#7-preferences-and-customization)

---

## 1. The Explorer Tab

![Explorer Tab](screenshots/explorer_tab.png)

*Multi-channel current-clamp view:*

![Explorer Tab - Multichannel](screenshots/explorer_tab_multichannel.png)

*Voltage-clamp view:*

![Explorer Tab - Voltage Clamp](screenshots/explorer_tab_voltageclamp.png)

The Explorer tab is the starting point for loading, browsing, and inspecting
electrophysiology data files. It supports all formats handled by the Neo library
(`.abf`, `.smr`, `.smrx`, `.nex`, `.h5`, and many more).

> **Resizable panels:** The Explorer tab has three side-by-side panels separated
> by draggable dividers. Drag the handle between the Configuration panel and the
> plot, or between the plot and the File/Analysis panel, to redistribute the
> available width. Default widths are 320 px (left), 800 px (centre), and
> 360 px (right).

### 1.1 File Sidebar

- **File System Tree** - A live tree filtered to supported electrophysiology extensions.
  Click any file to load it instantly.
- **Project Tree** - Groups recordings hierarchically by **Animal → Slice → Cell**.
  Supports multi-selection for batch operations.
- **Drag & Drop** - Drag files from the OS file manager directly into the sidebar.
- **Quality Indicator** - Traffic-light badge per file:
  - Green **Good** - Clean signal, low drift, acceptable SNR.
  - Yellow **Warning** - Moderate baseline drift or line noise detected.
  - Red **Bad** - Excessive noise, drift, or signal quality issues.
  Hover to see detailed metrics: RMS noise, drift rate, and specific warnings.

### 1.2 Plot Canvas

- **Multi-Channel Display** - Each channel is plotted on a separate axis with linked
  X-axes for synchronized panning/zooming.
- **Hardware-Accelerated Rendering** - PyQtGraph + OpenGL, up to 60 fps on large files.
- **Interactive Navigation** - Scroll to zoom, drag to pan, toolbar for fine control.
- **Interactive Cursors** - Enable crosshair cursors via the **Interactive Cursor**
  group in the display panel. Clicking on the trace places a persistent marker at the
  selected (time, amplitude) coordinate. Enable **Delta Mode** to measure differences
  between successive cursor placements (Δt, ΔV/ΔI). Undo and Clear buttons manage
  cursor history.

*The interactive cursor group is located in the display configuration panel on the right side of the Explorer tab.*

### 1.3 Display Configuration Panel

- **Plot Mode**
  - *Overlay All + Average* - All trials with average highlighted in bold.
  - *Cycle Single Trial* - One trial at a time with Prev/Next buttons.
- **Auto Downsample** - Reduces data density automatically for very large files.
- **Manual Trial Averaging** (Cycle mode) - **Mark Current Trial** (queue the
  displayed sweep for cross-file averaging), **Add Marked Trials to Set** (commit
  to Analysis Set), **Clear Average** / **Plot Selected Average** (overlay the
  in-progress average on the current trace).
- **Trial Selection** - Trial Gap (skip N) and Start Trial index for sub-sampling.
- **Channel Visibility** - Per-channel show/hide checkboxes. When a channel is
  hidden its plot row collapses completely so no blank space is left in the canvas.
  Re-checking the box restores the row to its normal height.
- **Live Spike Detection** - Real-time detection with configurable threshold
  (default -20 mV) and refractory period (default 2 ms). Spikes shown as red dots.
- **Trial Quality Metrics** - Displays series resistance (Rs), membrane capacitance
  (Cm), and signal-to-noise ratio (SNR) extracted from channel metadata for the
  currently visible trial. These metrics update automatically when navigating between
  trials.

### 1.4 Toolbar

- **Prev / Next File** - Navigate through all files in the directory.
  When a single file is opened (via drag-and-drop or **Help > Download Demo Data**)
  Synaptipy automatically scans the parent folder for other files with the same
  extension, so **Prev / Next File** works immediately without a second drop.
- **Save Plot** - Export the current view to PNG or PDF.
- **Reset View** - Restore default zoom and position.
- **Lock Zoom** - Preserve the viewport when switching files (useful for cross-file
  comparison of the same time window). The viewport is restored precisely to the
  saved ranges; cumulative zoom drift across file cycles is eliminated.
- **X Zoom Slider** - Fine horizontal zoom control.
- **Prev / Next Trial** (Cycle mode) - Navigate individual sweeps.

### 1.5 Y-Axis Controls

- **Lock Y Views** - Link or unlink Y-axes across channels.
- **Global Y Controls** (locked) - Single slider + scroll bar for all channels.
- **Per-Channel Y Controls** (unlocked) - Independent slider + scroll bar per channel.
  All controls are debounced (50 ms) for smooth interaction.

### 1.6 Adding Data to the Analyser

**Whole recording** - Click **"Add to Analysis Set"** to send the full file as a
single Recording item. The Analyser will average across all trials in that file.

**Specific trials (cross-file workflow)**

![Explorer with marked trials](screenshots/explorer_tab_mark_trials.png)

Use this workflow to cherry-pick individual sweeps from any combination of files
and average them together in the Analyser:

1. Set **Plot Mode** to *Cycle Single Trial*.
2. Navigate to any trial you want to include and click **"Mark Current Trial"**.
   A badge on the **"Add Marked Trials to Set"** button updates to show how many
   trials are pending (e.g. *"Add Marked Trials to Set (2)"*).
3. Navigate freely between files and continue marking. Marks from different files
   accumulate in the pending set.
4. When ready, click **"Add Marked Trials to Set"**. The pending marks are moved
   to the Analysis Set and the mark queue is cleared automatically - clicking the
   button again without marking new trials will show a *"no trials marked"* notice
   rather than a confusing *"already in set"* message.
5. Switch to the **Analyser** tab, choose **Cross-File Average** as the data
   source, and click **Run Analysis**.

The Analysis Set header in the Analyser shows the exact item count and unique
file count (e.g. **"3 items (2 files)"**) so you always know how many sweeps
are included, not just how many files.

---

## 2. Signal Preprocessing

A unified preprocessing panel is available in both the Explorer and Analyser tabs.
Preprocessing applied in any analysis sub-tab propagates globally to all sub-tabs.

### 2.1 Digital Filters

All filters use zero-phase Butterworth IIR via `scipy.signal.sosfiltfilt`
(no phase distortion).

| Filter | Parameters | Description |
|--------|------------|-------------|
| **Lowpass** | Cutoff (Hz), Order | Remove high-frequency noise |
| **Highpass** | Cutoff (Hz), Order | Remove slow baseline drift |
| **Bandpass** | Low & High cutoff (Hz), Order | Pass a specific frequency band |
| **Notch** | Center (Hz), Q-factor | Remove a narrow band (e.g., 50/60 Hz mains) - `scipy.signal.iirnotch` |
| **Comb** | Base freq (Hz), Q, N harmonics | Cascaded notch filters removing a fundamental and all harmonics |

### 2.2 Baseline Subtraction Methods

| Method | Description |
|--------|-------------|
| **Mode** | Subtract statistical mode (configurable decimal precision) |
| **Mean** | Subtract full-trace arithmetic mean |
| **Median** | Subtract full-trace median |
| **Linear Detrend** | Fit and remove a linear trend via `scipy.signal.detrend` |
| **Time Window** | Subtract mean of a user-specified region [t_start, t_end] |

### 2.3 Artifact Blanking

The preprocessing pipeline includes a general-purpose artifact blanking step
that can be applied before any analysis:

| Method | Description |
|--------|-------------|
| **Hold** | Replace the artifact window with the last pre-artifact sample value (sample-and-hold) |
| **Zero** | Set all samples in the artifact window to zero |
| **Linear** | Linearly interpolate between the last pre-artifact and first post-artifact sample |

Configure via the pipeline API:

```python
from Synaptipy.core.processing_pipeline import ProcessingPipeline

pipeline = ProcessingPipeline()
pipeline.add_step({
    "type": "artifact",
    "onset_time": 0.1,       # seconds
    "duration_ms": 5.0,      # milliseconds
    "method": "linear",      # "hold", "zero", or "linear"
})
```

### 2.4 Trace Quality Assessment

Computed on file load; shown as a traffic-light badge with tooltip:
- **SNR** - signal power / noise power.
- **Baseline Drift** - linear regression slope over the full trace (mV/s).
- **Line Noise** - Welch PSD at 50/60 Hz.
- **Low-Frequency Variance** - energy below a configurable cutoff.

### 2.5 Preprocessing Comparison

A side-by-side comparison dialog allows visual inspection of the signal before
and after preprocessing. Two linked PyQtGraph plots display the raw and processed
traces with synchronised X-axes, accompanied by a statistics table comparing
mean, standard deviation, minimum, and maximum values for each condition. This
facilitates verification that the preprocessing parameters produce the intended
effect without introducing artefacts.

---

## 3. The Analyser Tab

![Analyser Tab](screenshots/analyser_tab.png)

The central analysis engine. All 17 analysis sub-tabs are auto-built from registry
metadata - parameter widgets, interactive plots, and result tables are generated
automatically.

### 3.1 File and Data Source Selection

- **File Selector** - All files in the analysis set. The header label shows the
  item count and unique file count, e.g. **"3 items (2 files)"**, so you can
  always confirm that all expected sweeps are present.
- **Channel Selector** - Choose the channel to analyze. Clamp mode (CC / VC) is
  auto-detected from channel units and controls which parameters appear.
- **Data Source** - Average (mean of all trials), Trial 1, Trial 2, etc., or
  **Cross-File Average** (grand average across all items in the Analysis Set,
  with per-item faint traces shown alongside the bold average).

  ```{note}
  When **Cross-File Average** is selected the plot shows all per-item traces in
  a faint colour and the grand average as a bold line.  The title bar reports
  the number of items and files (e.g. "3 items / 2 files").
  ```
  <!-- Screenshot: screenshots/analyser_cross_file_average.png
       Generated by: conda run -n synaptipy python scripts/capture_screenshots.py -->

  When **Cross-File Average** is selected, the plot shows every per-item trace
  in a faint colour and the grand average as a bold line so trial-to-trial
  variability is immediately visible. The title bar reports the number of files
  and warns if trace lengths differ across files.
- **Drag-and-Drop File List** - Drop files directly from the Explorer sidebar.

### 3.2 Analysis Sub-Tab Features

Each sub-tab provides:

- **Parameter Panel** - Auto-generated spin boxes, combo boxes, and checkboxes.
  All include tooltips (populated from registry metadata), unit suffixes, valid
  min/max ranges.
  - *Free-form numeric entry* - Number fields accept typed values directly;
    intermediate states (a lone `−`, an empty field, etc.) are tolerated while
    typing and validated only on commit. Stepping uses adaptive decimal increments.
  - *Validation feedback* - Parameters that fall outside their valid range are
    highlighted with a red border, providing immediate visual indication of
    invalid entries without blocking interaction.
  - *Interactive / Manual mode* - Sub-tabs with draggable plot regions expose a
    mode selector. In **Interactive** mode the time-window spinboxes are read-only
    (their values are driven by the plot regions); switching to **Manual** mode
    unlocks all spinboxes for direct entry.
  - *Conditional visibility* - Parameters that are irrelevant for the current
    clamp mode, analysis type, or detection method are hidden automatically.
- **Preprocessing Indicator** - When preprocessing (baseline subtraction, filtering)
  is active, a coloured banner appears at the top of the Analyser listing the active
  steps (e.g. "Active Preprocessing: Baseline: mode | Filter: lowpass (500 Hz)").
  This ensures the operator is aware that the displayed signal differs from the raw
  acquisition.
- **Interactive Plot**
  - *Draggable Regions* - Color-coded overlays (blue = baseline, red = response,
    orange = peak, green = steady-state) bidirectionally linked to parameter boxes.
  - *Interactive Event Markers* - Click to remove; Ctrl+click on trace to add.
  - *Draggable Threshold Line* - Syncs with the threshold parameter.
  - *Artifact Overlay* - Semi-transparent green shading on detected artifact windows.
  - *Fit Overlays* - Exponential or regression curves overlaid on the trace.
  - *Statistical Annotations* - Popup plots display quantitative annotations
    (R², regression slope, τ values, event counts) directly on the figure.
- **Results Table** - Parameter / Value pairs for all computed metrics.
- **Clamp-Mode Awareness** - Parameters show/hide automatically (CC vs. VC).

### 3.3 Shared Controls

- **Run Analysis** - Executes with current parameters; also auto-triggers (debounced)
  on parameter change.
- **Add to Session** - Accumulate the current result to the session for later
  export or cross-cell comparison. A numeric badge on the button displays the
  running count of accumulated results (e.g. "Add to Session (5)").
- **View Session** - Inspect accumulated results; also badged with the current count.
- **Copy Methods Text** - Publication-ready methods paragraph copied to clipboard.
- **Run Batch** - Opens the Batch Analysis Dialog (see Section 5).
- **Cross-Tab Zoom Sync** - Zoom states shared across all analysis sub-tabs.
- **Auto-Save** - The session state is persisted automatically at five-minute
  intervals, reducing the risk of data loss during prolonged analysis sessions.

### 3.4 Popup Windows

- **I-V Curve popup** - Scatter + linear regression with R² annotation.
- **F-I Curve popup** - Firing frequency vs. current with regression.
- **Phase Plane popup** - dV/dt vs. V with threshold circles and max-dV/dt crosses.
- **ISI Plot popup** - ISI number vs. ISI duration scatter.

### 3.5 Session Summary

Shows all accumulated results with **Mean ± SD** for numeric columns across cells.

### 3.6 Adding Your Own Analysis Tab

You can add a completely new analysis tab to the Analyser **without touching any
Synaptipy source code**.  All you need is a single Python file - Synaptipy
generates the parameter panel, Run button, results table, and plot overlays
automatically from the metadata you provide.

#### Bundled Example Plugins

Three ready-to-run example plugins ship with every Synaptipy installation.
When **Enable Custom Plugins** is checked in **Edit > Preferences** (it is on
by default), they appear automatically as new sub-tabs in the Analyser and are
the fastest way to see what the plugin system can do before writing your own.

| File | Tab label | What it measures |
|------|-----------|-----------------|
| `examples/plugins/synaptic_charge.py` | **Synaptic Charge (AUC)** | Total charge (pC) of a postsynaptic current via trapezoidal integration over a user-defined window; shaded fill overlay marks the integrated area and a star marks the detected peak amplitude. |
| `examples/plugins/opto_jitter.py` | **Opto Latency Jitter** | Trial-to-trial latency variability for optogenetically evoked spikes; requires a secondary digital/TTL channel. |
| `examples/plugins/ap_repolarization.py` | **Max Repolarization Rate** | Steepest falling dV/dt of the first action potential in a window (V/s), a proxy for voltage-gated potassium-channel dynamics. |

![Analyser tab overview](screenshots/analyser_tab.png)

To customise an example, copy it to `~/.synaptipy/plugins/` and edit your copy.
Synaptipy always prefers the user copy over the bundled file.

---

#### Step 1 - Create the plugin file

Copy the starter template that ships with Synaptipy:

```bash
# macOS / Linux
cp src/Synaptipy/templates/plugin_template.py ~/.synaptipy/plugins/my_analysis.py

# Windows (PowerShell)
Copy-Item src\Synaptipy\templates\plugin_template.py ~\.synaptipy\plugins\my_analysis.py
```

> **Plugin folder location by platform:**
>
> | Platform | Path |
> |----------|------|
> | macOS / Linux | `~/.synaptipy/plugins/` |
> | Windows | `C:\Users\<YourUsername>\.synaptipy\plugins\` |
>
> If the folder does not exist yet, either create it manually or simply start
> Synaptipy once - it creates the folder automatically.
>
> **Windows tip:** You can open the folder in Explorer by typing
> `%USERPROFILE%\.synaptipy\plugins` in the address bar, or in PowerShell:
> `explorer "$HOME\.synaptipy\plugins"`

#### Step 2 - Write your analysis function

Open `~/.synaptipy/plugins/my_analysis.py` in any text editor.  The file has two
parts.  The example below implements **area-under-the-curve (AUC)** measurement
- the same approach used by the bundled `synaptic_charge.py` plugin.

**Part A - Your analysis logic** (plain Python + NumPy, no GUI code):

```python
import numpy as np

def calculate_area_under_curve(data, time, sampling_rate,
                               baseline_start, baseline_end,
                               window_start, window_end):
    """Integrate a baseline-subtracted trace to obtain synaptic charge (pC)."""
    # Baseline: mean over the pre-event window
    bl0 = int(np.searchsorted(time, baseline_start, side="left"))
    bl1 = int(np.searchsorted(time, baseline_end,   side="right"))
    baseline = float(np.mean(data[bl0:bl1])) if bl1 > bl0 else 0.0

    # Integration window
    wi0 = int(np.searchsorted(time, window_start, side="left"))
    wi1 = int(np.searchsorted(time, window_end,   side="right"))
    win_t = time[wi0:wi1]
    win_d = data[wi0:wi1] - baseline

    if win_t.size < 2:
        return {"error": "Integration window too narrow"}

    # np.trapezoid: pA * s = pC
    charge_pc = float(np.trapezoid(win_d, win_t))
    return {
        "Charge_pC":   round(charge_pc, 6),
        "Baseline_pA": round(baseline,  4),
        "_int_x": win_t,          # hidden - fed to fill_between overlay
        "_int_y": data[wi0:wi1],  # hidden - raw (unshifted) curve for shading
        "_base":  baseline,       # hidden - baseline level for hline
    }
```

**Part B - Register it with the decorator:**

```python
from Synaptipy.core.analysis.registry import AnalysisRegistry

@AnalysisRegistry.register(
    name="synaptic_charge",              # unique internal ID
    label="Synaptic Charge (AUC)",       # name shown on the tab
    ui_params=[...],                     # parameter widgets  (see below)
    plots=[...],                         # plot overlays      (see below)
)
def run_auc(data, time, sampling_rate, **kwargs):
    """Wrapper - pulls GUI values from kwargs and calls the logic."""
    return calculate_area_under_curve(
        data, time, sampling_rate,
        baseline_start = kwargs.get("baseline_start", 0.0),
        baseline_end   = kwargs.get("baseline_end",   0.05),
        window_start   = kwargs.get("window_start",   0.05),
        window_end     = kwargs.get("window_end",     0.3),
    )
```

> **The wrapper signature is fixed:** `def f(data, time, sampling_rate, **kwargs)`
> - Synaptipy passes in the trace array, time array, and sampling rate, plus
> every parameter widget value as a keyword argument.

#### Step 3 - Define parameter widgets (`ui_params`)

Each dict in the `ui_params` list creates one widget in the parameter panel.
Four widget types are available:

| Type | Widget | Example |
|------|--------|---------|
| `"float"` | Decimal spin box | `{"name": "threshold", "label": "Threshold (mV):", "type": "float", "default": -20.0, "min": -200, "max": 200, "decimals": 2}` |
| `"int"` | Integer spin box | `{"name": "min_events", "label": "Min Events:", "type": "int", "default": 3, "min": 1, "max": 10000}` |
| `"bool"` | Check box | `{"name": "auto_detect", "label": "Auto-Detect Baseline", "type": "bool", "default": False}` |
| `"choice"` | Drop-down | `{"name": "direction", "label": "Direction:", "type": "choice", "choices": ["negative", "positive"], "default": "negative"}` |

Full example for the AUC analysis:

```python
ui_params=[
    {"name": "baseline_start", "label": "Baseline Start (s):", "type": "float",
     "default": 0.0,  "min": 0.0, "max": 1e9, "decimals": 4},
    {"name": "baseline_end",   "label": "Baseline End (s):",   "type": "float",
     "default": 0.05, "min": 0.0, "max": 1e9, "decimals": 4},
    {"name": "window_start",   "label": "Window Start (s):",   "type": "float",
     "default": 0.05, "min": 0.0, "max": 1e9, "decimals": 4},
    {"name": "window_end",     "label": "Window End (s):",     "type": "float",
     "default": 0.3,  "min": 0.0, "max": 1e9, "decimals": 4},
],
```

**Optional extras on any parameter:**

- `"tooltip": "Hover text"` - shows on mouse-over.
- `"visible_when": {"param": "mode", "value": "advanced"}` - shows this widget
  only when another widget named `"mode"` has the value `"advanced"`.

#### Step 4 - Define plot overlays (`plots`)

Each dict in the `plots` list draws something on the trace after the analysis
runs.  Common overlay types:

| Type | What it draws | Example |
|------|---------------|---------|
| `"interactive_region"` | Draggable shaded region synced to two float parameters | `{"type": "interactive_region", "data": ["window_start", "window_end"], "color": "g"}` |
| `"hlines"` | Horizontal lines at result values | `{"type": "hlines", "data": ["_base"], "color": "b", "styles": ["dash"]}` |
| `"vlines"` | Vertical lines at result values | `{"type": "vlines", "data": "stim_times", "color": "c"}` |
| `"markers"` | Scatter dots at (x, y) arrays from result | `{"type": "markers", "x": "peak_times", "y": "peak_voltages", "color": "r"}` |
| `"threshold_line"` | Draggable h-line synced to a parameter | `{"type": "threshold_line", "param": "threshold"}` |
| `"overlay_fit"` | Curve overlay (e.g. exponential fit) | `{"type": "overlay_fit", "x": "_fit_t", "y": "_fit_v", "color": "r"}` |
| `"fill_between"` | Shaded filled area between two curves | `{"type": "fill_between", "x": "_int_x", "y1": "_int_y", "y2": "_base", "brush": (0, 100, 255, 100)}` |
| `"popup_xy"` | Opens a separate plot window | `{"type": "popup_xy", "title": "F-I Curve", "x": "currents", "y": "rates", "x_label": "I (pA)", "y_label": "f (Hz)"}` |

Full example - baseline region, integration region, baseline h-line, and shaded
integrated area:

```python
plots=[
    {"type": "interactive_region", "data": ["baseline_start", "baseline_end"], "color": "b"},
    {"type": "interactive_region", "data": ["window_start",   "window_end"],   "color": "g"},
    {"type": "hlines", "data": ["_base"], "color": "b", "styles": ["dash"]},
    {"type": "fill_between", "x": "_int_x", "y1": "_int_y", "y2": "_base",
     "brush": (0, 100, 255, 100)},
],
```

#### Step 5 - Control what shows in the results table

Every key in the dict your function returns becomes a row in the results table.
To keep helper data out of the table (e.g. arrays used only by plot overlays),
prefix the key with an underscore:

```python
return {
    "Charge_pC":    1.23,       # ← shown in results table
    "Baseline_pA":  -42.0,      # ← shown in results table
    "_int_x":  time_array,      # ← hidden (used by fill_between overlay)
    "_int_y":  data_array,      # ← hidden (used by fill_between overlay)
    "_base":   -42.0,           # ← hidden (used by hlines overlay)
}
```

If your function encounters an error, return `{"error": "message"}` and the GUI
will display the message instead of a results table.

#### Step 6 - Restart and use

Save your file, restart Synaptipy, and your new tab appears in the Analyser:

```
Analyser → ... | Baseline | Spikes | Synaptic Charge (AUC)
```

Load a voltage-clamp recording, select the current channel, and click
**Run Analysis** (or just change a parameter - analysis re-runs automatically).
The results table, shaded fill overlay, and batch processing all work
immediately.

#### Tips

- **No rebuild needed** - just save the `.py` file and restart Synaptipy.
- **Errors are safe** - if your plugin has a syntax error or crashes, Synaptipy
  logs the error and keeps running (your tab simply won't appear).  Check the log
  at `~/.synaptipy/logs/` (Windows: `C:\Users\<YourUsername>\.synaptipy\logs\`).
- **Multiple plugins** - put as many `.py` files in the plugins folder as you
  like; each one with an `@AnalysisRegistry.register` decorator creates a
  separate tab.
- **Use any installed package** - your plugin can import SciPy, scikit-learn,
  or anything installed in the same Python environment.
- **Template location** - the ready-to-copy template is at
  `src/Synaptipy/templates/plugin_template.py`.
- **Full reference** - for the complete list of all parameter types, all 12
  plot overlay types, and advanced features like conditional visibility, see
  [Writing Custom Analysis Plugins](../extending_synaptipy.md).

---

## 4. Analysis Modules - Detailed Reference

> **Formal mathematics:** For publication-ready LaTeX definitions of every metric
> computed below, see the [Algorithmic Definitions](../algorithmic_definitions.md) page.

All 17 analyses are grouped into five tabs that match the neuroscience workflow from
passive membrane characterisation through synaptic and evoked-response studies.

| UI Tab | Analyses covered |
|--------|------------------|
| **Intrinsic Properties** | Baseline/RMP (4.1), Input Resistance (4.6), Tau (4.7), I-V Curve (4.8), Capacitance (4.10), Sag Ratio (4.15) |
| **Spike Analysis** | Spike Detection (4.2), Phase Plane (4.13) |
| **Excitability** | Excitability - F-I Curve (4.11), Burst (4.9), Spike Train Dynamics (4.14) |
| **Synaptic Events** | Threshold (4.3), Deconvolution (4.4), Baseline+Peak+Kinetics (4.5) |
| **Evoked Responses** | Evoked Sync (4.12), Paired-Pulse Ratio, Stimulus Train / STP |

![Intrinsic Properties tab](screenshots/analyser_intrinsic_properties.png)

![Spike Analysis tab](screenshots/analyser_spike_analysis.png)

![Excitability tab](screenshots/analyser_excitability.png)

![Synaptic Events tab](screenshots/analyser_synaptic_events.png)

![Evoked Responses tab](screenshots/analyser_evoked_responses.png)

---

### 4.1 Baseline / RMP Analysis

![Baseline RMP interface](screenshots/analyser_intrinsic_properties_baseline_rmp.png)

**Registry name**: `rmp_analysis` | **Tab label**: *Baseline (RMP)*

Computes resting membrane potential and baseline stability.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Baseline Start | float (s) | Start of baseline window |
| Baseline End | float (s) | End of baseline window |
| Auto Detect | bool | Find most stable window automatically |
| Window Duration | float (s) | Sliding window size (auto mode) |
| Step Duration | float (s) | Sliding window step (auto mode) |

#### Methods

- **RMP** = mean voltage in baseline window.
- **SD** bounds = RMP ± std(baseline).
- **Drift** = linear regression slope (mV/s).
- **Auto-detect**: `find_stable_baseline()` uses sliding-window variance
  minimization to locate the quietest trace segment.

#### Results

`rmp_mv`, `rmp_std`, `rmp_mv_plus_sd`, `rmp_mv_minus_sd`, `rmp_drift`,
`rmp_duration`

#### Visualization

Green draggable region; solid red h-line at RMP; dashed red h-lines at ±SD.

![Baseline RMP popup](screenshots/analyser_intrinsic_properties_baseline_rmp_popup.png)

---

### 4.2 Spike Detection

![Spike Detection interface](screenshots/analyser_spike_analysis_spike_detection.png)

**Registry name**: `spike_detection` | **Tab label**: *Spike Detection*

Automated detection and comprehensive parameterization of action potentials.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Threshold | float (mV) | Detection threshold (default −20 mV) |
| Refractory Period | float (s) | Minimum inter-spike interval (default 2 ms) |
| Peak Search Window | float (s) | Post-threshold window to find peak |
| dV/dt Threshold | float (V/s) | Derivative threshold for onset |
| AHP Window | float (s) | Post-peak window for AHP minimum |
| Onset Lookback | float (s) | Lookback for onset refinement |

#### Methods

- **Onset** - `numpy.gradient` computes dV/dt; onset where dV/dt > `dvdt_threshold`.
  More robust against baseline drift than amplitude thresholding.
- **Per-spike features** (vectorized NumPy):
  - Amplitude = V_peak − V_threshold
  - Half-width at 50% amplitude (linear interpolation)
  - Rise time 10-90%, Decay time 90-10% (sub-sample precision)
  - AHP depth = V_threshold − V_min_post (Savitzky-Golay smoothed)
  - AHP duration = repolarization to AHP recovery
  - ADP amplitude = local max after AHP trough
  - Max / Min dV/dt (V/s)
- **ISI statistics** via `numpy.diff(spike_times)`.
- **Multi-sweep**: `analyze_multi_sweep_spikes()` aggregates across all trials.

#### Results

`spike_count`, `mean_freq_hz`, `spike_times`, mean ± SD for: `amplitude`,
`half_width`, `rise_time`, `decay_time`, `ahp_depth`, `ahp_duration`,
`adp_amplitude`, `max_dvdt`, `min_dvdt`

#### Visualization

Red dashed threshold h-line; red dot markers at spike peaks.

---

### 4.3 Event Detection - Threshold Based

![Event Detection - Threshold interface](screenshots/analyser_synaptic_events_threshold_based.png)

**Registry name**: `event_detection_threshold` | **Tab label**: *Event Detection*

Detects mEPSCs / mIPSCs using adaptive prominence-based thresholding.
A **method selector** dropdown switches between all three event detection
algorithms (sections 4.3 - 4.5).

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Threshold | float (pA/mV) | Minimum event amplitude |
| Direction | combo | `negative` or `positive` |
| Refractory Period | float (s) | Minimum inter-event interval |
| Rolling Baseline Window | float (ms) | Rolling median baseline window |
| Reject Artifacts | bool | Gradient-based artifact rejection |
| Artifact Slope Threshold | float | Gradient threshold for artifacts |
| Artifact Padding | float (ms) | Dilation padding around artifacts |

#### Methods

1. Rolling median subtraction removes slow drift.
2. MAD noise estimate: σ = 1.4826 × MAD.
3. Adaptive prominence = max(threshold, 2σ).
4. `scipy.signal.find_peaks` with prominence, height, refractory distance,
   and min-width constraints. Events < 0.2 ms rejected as non-physiological.
5. Artifact rejection: gradient thresholding + asymmetric binary dilation.

#### Results

`event_count`, `frequency_hz`, `mean_amplitude`, `amplitude_sd`,
`event_times`, `event_indices`, `event_amplitudes`

#### Visualization

Interactive scatter markers (click to remove; Ctrl+click to add);
draggable threshold line; artifact overlay.

---

### 4.4 Event Detection - Template Match / Deconvolution

![Event Detection - Deconvolution interface](screenshots/analyser_synaptic_events_deconvolution_custom.png)

**Registry name**: `event_detection_deconvolution` | **Tab label**: *Event (Template Match)*

Detects events via a matched-filter approach using a bi-exponential template.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| τ Rise | float (ms) | Rise time constant |
| τ Decay | float (ms) | Decay time constant |
| Threshold (SD) | float | Z-score detection threshold |
| Direction | combo | Event polarity |
| Rolling Baseline Window | float (ms) | Baseline subtraction window |
| Reject Artifacts | bool | Enable artifact rejection |
| Min Event Distance | float (ms) | Minimum event separation |
| Kernel Multipliers | string | Comma-separated scaling factors for tau_decay (e.g. `1.0, 2.0, 3.0`). Allows the kernel bank to cover slower, dendritically filtered events. Cable theory predicts ~2-3x decay slowing for distal inputs. |

#### Methods

1. Bi-exponential kernel: K(t) = exp(−t/τ_decay) − exp(−t/τ_rise), normalized.
2. FFT cross-correlation (matched filter) with the time-reversed template.
3. Z-score normalization by MAD; peaks above `threshold_sd` are events.
4. Local argmax refinement aligns detected times to true event peaks.

#### Results

Same as threshold method plus `tau_rise_ms`, `tau_decay_ms`.

#### Visualization

Same interactive markers / threshold line / artifact overlay.

---

### 4.5 Event Detection - Baseline Peak / Kinetics

![Event Detection - Baseline Peak interface](screenshots/analyser_synaptic_events_baseline_peak_kinetics.png)

**Registry name**: `event_detection_baseline_peak` | **Tab label**: *Event (Baseline Peak)*

Detects events by finding the most stable baseline and locating peaks above the
noise floor.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Direction | combo | Event polarity |
| Auto Baseline | bool | Variance-minimization baseline search |
| Threshold (SD Factor) | float | Multiples of noise SD |
| Min Event Separation | float (ms) | Minimum inter-event gap |
| Rolling Baseline Window | float (ms) | Rolling baseline size |
| Baseline Window | float (s) | Manual window (if auto disabled) |
| Baseline Step | float (s) | Manual step size |

#### Methods

1. Stable baseline via sliding-window variance minimization.
2. MAD noise estimate from baseline region.
3. Prominence-based peaks, min prominence = 0.5 × threshold.
4. Optional Butterworth lowpass pre-filter.
5. Biological minimum event width ≥ 0.2 ms.

#### Results

`event_count`, `event_indices`, `event_times`, `event_amplitudes`

---

### 4.6 Input Resistance (Rin)

![Input Resistance interface](screenshots/analyser_intrinsic_properties_input_resistance.png)

**Registry name**: `rin_analysis` | **Tab label**: *Input Resistance*

Computes passive membrane input resistance from step protocols.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Current Amplitude | float (pA) | CC mode step (hidden in VC) |
| Voltage Step | float (mV) | VC mode step (hidden in CC) |
| Auto Detect Pulse | bool | Derivative-based pulse edge detection |
| Baseline Start / End | float (s) | Baseline window (read-only in Interactive mode) |
| Response Start / End | float (s) | Steady-state window (read-only in Interactive mode) |

#### Methods

- Ohm's Law: Rin (MΩ) = |ΔV| / |ΔI|, where ΔV = V_ss − V_baseline.
- Conductance: G (μS) = ΔI / ΔV.
- **Auto-detection** uses the first and second derivatives of the stimulus channel
  to locate step edges. The detected windows are validated for adequacy (≥ 2
  samples each); if validation fails (e.g. the trace contains action potentials
  that mislead the derivative search), the analysis transparently falls back to
  the user-specified spinbox values. After a successful run, the spinboxes are
  updated to reflect the windows actually used.

#### Results

`rin_mohm`, `conductance_us`, `voltage_deflection_mv`, `current_injection_pa`,
`baseline_voltage_mv`, `steady_state_voltage_mv`, `sag_ratio`, `sag_percentage`

The sag ratio is computed with configurable `peak_smoothing_ms` (default 5 ms)
and `rebound_window_ms` (default 100 ms) parameters.  For standalone sag ratio
analysis with dedicated parameter controls, see
[§4.15 Sag Ratio (I_h)](#415-sag-ratio-i_h).  See also
[Algorithmic Definitions §4](../algorithmic_definitions.md) for the full formula.

#### Visualization

Blue region (baseline) + red region (response); blue h-line at baseline
voltage; red h-line at steady-state voltage.

---

### 4.7 Tau (Membrane Time Constant)

![Tau interface](screenshots/analyser_intrinsic_properties_tau_time_constant.png)

**Registry name**: `tau_analysis` | **Tab label**: *Tau (Time Constant)*

Fits the membrane charging curve to extract the passive time constant.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Stim Start Time | float (s) | Current step onset |
| Fit Duration | float (s) | Duration of decay region to fit |
| Tau Model | combo | `mono` or `bi` exponential |
| Artifact Blanking | float (ms) | Exclude N ms after stimulus onset |
| Tau Bound Min / Max | float (ms) | Bounds for τ optimizer |

#### Methods

- **Mono**: V(t) = V_ss + (V_0 − V_ss) · exp(−t/τ), optimized via
  `scipy.optimize.curve_fit` (bounded non-linear least squares).
- **Bi**: V(t) = V_ss + A_fast · exp(−t/τ_fast) + A_slow · exp(−t/τ_slow),
  separating fast capacitive from slower dendritic components.
- Artifact blanking excludes the first N ms after stimulus onset.

#### Results

Mono: `tau_ms`. Bi: `tau_fast_ms`, `tau_slow_ms`, `amp_fast`, `amp_slow`.
Plus `fit_time` and `fit_values` for the overlay.

#### Visualization

Red exponential fit curve overlaid on the raw voltage trace.

---

### 4.8 I-V Curve

![I-V Curve interface](screenshots/analyser_intrinsic_properties_i-v_curve.png)

**Registry name**: `iv_curve_analysis` | **Tab label**: *I-V Curve*
**Requires multiple trials** (multi-sweep step protocol).

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Start Current | float (pA) | Current of sweep 0 |
| Step Current | float (pA) | Current increment per sweep |
| Baseline Start / End | float (s) | Baseline measurement window |
| Response Start / End | float (s) | Steady-state measurement window |

#### Methods

- Per sweep n: I_n = I_start + n × I_step;
  ΔV_n = mean(V_response) − mean(V_baseline).
- Linear regression ΔV vs. ΔI: slope = Rin (MΩ), R² = goodness of fit.

#### Results

`rin_aggregate_mohm`, `iv_intercept`, `iv_r_squared`, `delta_vs`,
`current_steps`

#### Visualization

Popup I-V scatter + linear regression line with slope annotation and R².

![I-V Curve popup](screenshots/analyser_intrinsic_properties_i-v_curve_popup.png)

---

### 4.9 Burst Analysis

![Burst Analysis interface](screenshots/analyser_excitability_burst_analysis.png)

**Registry name**: `burst_analysis` | **Tab label**: *Burst*

Identifies firing bursts based on inter-spike interval (ISI) criteria.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Threshold | float (mV) | Spike detection threshold |
| Max ISI (Start) | float (s) | Maximum ISI to start a burst |
| Max ISI (End) | float (s) | Maximum ISI to continue a burst |
| Min Spikes | int | Minimum spikes per burst |

#### Methods

1. Threshold-crossing spike detection.
2. ISI-based detector: burst starts when ISI ≤ max_isi_start; continues while
   ISI ≤ max_isi_end; groups with < min_spikes are discarded.

#### Results

`burst_count`, `spikes_per_burst_avg`, `burst_duration_avg_s`,
`burst_freq_hz`, `bursts`

#### Visualization

Red bracket lines spanning each burst, with spike markers inside each burst.

---

### 4.10 Capacitance

![Capacitance interface](screenshots/analyser_intrinsic_properties_capacitance.png)

**Registry name**: `capacitance_analysis` | **Tab label**: *Capacitance*

Estimates whole-cell membrane capacitance from step protocols.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Mode | combo | Current-Clamp or Voltage-Clamp |
| Current Amplitude | float (pA) | Step size (CC mode) |
| Voltage Step | float (mV) | Step size (VC mode) |
| Baseline Start / End | float (s) | Baseline window |
| Response Start / End | float (s) | Response window |

#### Methods

- **CC mode**: C_m = τ / R_in. Tau and Rin computed first; τ(ms) / R(MΩ) → C(pF).
- **VC mode**: C_m = Q / ΔV, where Q = ∫(I_transient − I_ss) dt via
  `scipy.integrate.trapezoid`.

#### Results

`capacitance_pf`, `tau_ms`, `rin_mohm`, `mode`

---

### 4.11 Excitability (F-I Curve)

![Excitability interface](screenshots/analyser_excitability_excitability.png)

**Registry name**: `excitability_analysis` | **Tab label**: *Excitability*
**Requires multiple trials** (increasing current-step protocol).

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Threshold | float (mV) | Spike detection threshold |
| Start Current | float (pA) | Current of sweep 0 |
| Step Current | float (pA) | Current increment per sweep |
| Refractory Period | float (ms) | Refractory period for spike detection |

#### Methods

1. Per-sweep spike detection with threshold + refractory filtering.
2. Rheobase = minimum current eliciting ≥ 1 spike.
3. F-I slope via linear regression: f = slope × I + b (Hz/pA).
4. Spike Frequency Adaptation per sweep = last ISI / first ISI.

#### Results

`rheobase_pa`, `fi_slope`, `fi_r_squared`, `max_freq_hz`, `frequencies`,
`adaptation_ratios`, `current_steps`

#### Visualization

Popup F-I scatter with regression line and slope annotation.

![F-I Curve popup](screenshots/analyser_excitability_excitability_popup.png)

---

### 4.12 Optogenetic Synchronization

![Evoked Synchronization interface](screenshots/analyser_evoked_responses_evoked_sync.png)

**Registry name**: `optogenetic_sync` | **Tab label**: *Optogenetic Synchronization*
**Requires secondary TTL channel** (dropdown to select the optical stimulus channel).

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| TTL Threshold | float (V) | Binarize threshold for TTL channel |
| Response Window | float (ms) | Post-stimulus event search window |
| Event Detection Type | combo | `Spikes` / `Events (Threshold)` / `Events (Template)` |

**Spike mode** (`Event Detection Type = Spikes`):

| Parameter | Type | Description |
|-----------|------|-------------|
| Spike Threshold | float (mV) | AP detection threshold |

**Threshold-event mode** (`Event Detection Type = Events (Threshold)`):

| Parameter | Type | Description |
|-----------|------|-------------|
| Event Threshold | float (same units as signal) | Amplitude threshold |
| Event Direction | combo | `positive` / `negative` |
| Refractory Period | float (s) | Minimum inter-event interval |

**Template mode** (`Event Detection Type = Events (Template)`):

| Parameter | Type | Description |
|-----------|------|-------------|
| Rise Tau | float (ms) | Double-exponential template rise time constant |
| Decay Tau | float (ms) | Double-exponential template decay time constant |
| Template Threshold | float (SD) | Detection threshold in units of template SD |
| Template Direction | combo | `positive` / `negative` |

Parameters for the inactive detection modes are hidden automatically.

#### Methods

1. TTL edge detection: binarize → `numpy.diff` for rising edges.
2. For each stimulus onset, the response window is searched for an event using
   the selected detection method:
   - **Spikes** - threshold-crossing AP detection with refractory filtering.
   - **Events (Threshold)** - prominence-based threshold crossing with direction
     and refractory filtering (`detect_events_threshold`).
   - **Events (Template)** - double-exponential template deconvolution
     (`detect_events_template`).
3. Optical latency = mean(t_event − t_onset).
4. Response probability = stimuli_with_events / total_stimuli.
5. Jitter = std(latencies).

#### Results

`optical_latency_ms`, `response_probability`, `spike_jitter_ms`,
`stimulus_count`, `event_count`, `stimulus_onsets`

#### Visualization

Event markers on signal trace; cyan dashed vertical lines at TTL onsets.

---

### 4.13 Paired-Pulse Ratio (PPR)

![Paired-Pulse Ratio interface](screenshots/analyser_evoked_responses_paired-pulse_ratio.png)

**Registry name**: `paired_pulse_ratio` | **Tab label**: *Paired-Pulse Ratio*
**Optional secondary TTL channel** (when "Detect Stim from TTL" is enabled).

Quantifies short-term synaptic plasticity from two closely spaced stimuli by
computing R2 / R1 after subtracting the residual exponential decay of R1 from
beneath the R2 measurement window.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Detect Stim from TTL | bool | Auto-detect stimulus times from TTL channel |
| TTL Threshold | float (V) | Binarise threshold (visible when TTL mode enabled) |
| Stim 1 / Stim 2 Onset | float (s) | Manual stimulus times (visible when TTL disabled) |
| Event Polarity | combo | `negative` / `positive` |
| Response Window | float (ms) | Post-stimulus peak-search window |
| Baseline Window | float (ms) | Pre-stimulus baseline window |
| Decay Fit Start | float (ms) | Offset from stim 1 to begin exponential decay fit |
| Decay Fit Window | float (ms) | Duration of decay-fit window |
| Artifact Blanking | float (ms) | Suppress stimulus artefact at peak detection |

#### Methods

1. Measure R1 relative to its local pre-stimulus baseline.
2. Fit a mono- or bi-exponential decay to the R1 tail.
3. Extrapolate the decay to estimate the residual level at stim 2.
4. Measure R2_raw; subtract the residual to obtain R2_corrected.
5. PPR = R2_corrected / R1.

#### Results

`r1_amplitude`, `r2_amplitude_raw`, `r2_amplitude_corrected`,
`residual_at_stim2`, `paired_pulse_ratio`, `decay_tau_ms`, `ppr_error`

#### Visualization

Vertical lines at stimulus onsets; cyan baseline shading; orange exponential
decay fit overlay; diamond scatter markers at R1 and R2 peak positions.

---

### 4.14 Stimulus Train (STP)

![Stimulus Train STP interface](screenshots/analyser_evoked_responses_stimulus_train_stp.png)

**Registry name**: `stimulus_train_stp` | **Tab label**: *Stimulus Train (STP)*
**Optional secondary TTL channel** for automatic stimulus detection.

Measures short-term plasticity across a train of stimuli by computing
baseline-subtracted peak amplitudes for each pulse, normalised to R1.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Detect Stim from TTL | bool | Auto-detect pulse times from TTL channel |
| TTL Threshold | float (V) | Binarise threshold (visible when TTL mode enabled) |
| First Stim Onset | float (s) | Manual first-pulse time (visible when TTL disabled) |
| Stim Frequency | float (Hz) | Pulse frequency for manual mode |
| Number of Pulses | int | How many pulses to analyse |
| Event Polarity | combo | `negative` / `positive` |
| Response Window | float (ms) | Post-stimulus peak-search window |
| Baseline Window | float (ms) | Pre-stimulus baseline window |
| Artifact Blanking | float (ms) | Suppress stimulus artefact at peak detection |

#### Methods

1. Detect or generate N stimulus onset times.
2. For each onset: measure pre-stimulus baseline, find peak in response window.
3. Amplitude_n = peak - baseline (sign-corrected for polarity).
4. Normalise: R_n/R_1 = Amplitude_n / Amplitude_1.
5. Classify as facilitation or depression from R2 vs R1.

#### Results

`pulse_count`, `r1_amplitude`, `stp_type`, `R2/R1 … Rn/R1`,
`amplitudes`, `amplitudes_norm`, `pulse_numbers`

#### Visualization

Vertical lines at each stimulus onset; diamond scatter markers at each peak;
popup scatter plot of normalised amplitude vs. pulse number.

![STP popup](screenshots/analyser_evoked_responses_stimulus_train_stp_popup.png)

---

### 4.15 Phase Plane Analysis

![Phase Plane interface](screenshots/analyser_spike_analysis_phase_plane.png)

**Registry name**: `phase_plane_analysis` | **Tab label**: *Phase Plane*

Visualizes AP dynamics in phase space (dV/dt vs. V).

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Sigma | float (ms) | Gaussian smoothing for dV/dt |
| dV/dt Threshold | float (V/s) | Phase-plane crossing threshold |
| Spike Threshold | float (mV) | Voltage threshold for spike detection |
| Kink Slope | float | dV/dt value defining initiation "kink" |
| Search Window | float (ms) | Lookback window for kink detection |

#### Methods

1. dV/dt via `numpy.gradient` + `scipy.ndimage.gaussian_filter1d`.
2. Phase-plane trajectory = V(t) vs. dV/dt(t).
3. Kink detection: first point where dV/dt crosses kink_slope in the lookback
   window before the spike peak - reflects axonal AP initiation.
4. Max dV/dt = peak upstroke velocity.

#### Results

`voltage`, `dvdt` arrays; `threshold_indices`, `threshold_v`,
`threshold_dvdt`, `threshold_mean`, `max_dvdt`

#### Visualization

Popup phase-plane plot: red circles at threshold, green crosses at max dV/dt.
H-line on main trace at threshold voltage.

![Phase Plane popup](screenshots/analyser_spike_analysis_phase_plane_popup.png)

---

### 4.16 Spike Train Dynamics

![Spike Train Dynamics interface](screenshots/analyser_excitability_spike_train_dynamics.png)

**Registry name**: `train_dynamics` | **Tab label**: *Spike Train Dynamics*

Quantifies spike train regularity and temporal structure with ISI statistics.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Spike Threshold | float (mV) | AP detection threshold |

#### Methods

1. ISI = numpy.diff(spike_times).
2. **CV** = σ_ISI / μ_ISI - global variability (0 = regular, 1 = Poisson).
3. **CV₂** (Holt et al. 1996) = mean(2|ISI_(i+1) − ISI_i| / (ISI_(i+1) + ISI_i))
   - local variability, insensitive to slow rate changes.
4. **LV** (Shinomoto et al. 2003) = mean(3(ISI_i − ISI_(i+1))² / (ISI_i + ISI_(i+1))²)
   - LV < 1 regular, LV ≈ 1 Poisson, LV > 1 bursty.

#### Results

`spike_count`, `mean_isi_s`, `cv`, `cv2`, `lv`, `isi_numbers`, `isi_ms`

#### Visualization

Spike markers on the voltage trace; popup ISI-number vs. ISI-duration scatter.

![Spike Train Dynamics popup](screenshots/analyser_excitability_spike_train_dynamics_popup.png)

---

### 4.17 Sag Ratio (I_h)

![Sag Ratio interface](screenshots/analyser_intrinsic_properties_sag_ratio_ih.png)

**Registry name**: `sag_ratio_analysis` | **Tab label**: *Sag Ratio (Ih)*

Quantifies hyperpolarisation-activated sag (I_h current) from a hyperpolarising
current-step protocol. This is a standalone analysis module - while the Rin
analysis (§4.6) includes sag ratio in its output, this dedicated tab provides
finer control over the measurement windows and smoothing.

#### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| Baseline Start | float (s) | Start of baseline window |
| Baseline End | float (s) | End of baseline window |
| Peak Window Start | float (s) | Start of the peak search window (initial sag) |
| Peak Window End | float (s) | End of the peak search window |
| Steady-State Start | float (s) | Start of the steady-state measurement window |
| Steady-State End | float (s) | End of the steady-state measurement window |
| Peak Smoothing | float (ms) | Savitzky-Golay smoothing window for peak detection (default 5 ms) |
| Rebound Window | float (ms) | Window after stimulus offset for rebound measurement (default 100 ms) |

#### Methods

1. **V_baseline** = mean voltage in baseline window.
2. **V_peak** = minimum of Savitzky-Golay-smoothed voltage in the peak window
   (polynomial order 3, window = `peak_smoothing_ms`).
3. **V_ss** = mean voltage in steady-state window.
4. **Sag Ratio** = (V_peak − V_baseline) / (V_ss − V_baseline). A value > 1
   indicates I_h sag; a value of 1 indicates no sag.
5. **Sag Percentage** = 100 × (V_peak − V_ss) / (V_peak − V_baseline).
6. **Rebound Depolarisation** = max(V in rebound window) − V_baseline.

See [Algorithmic Definitions §4](../algorithmic_definitions.md) for the full
LaTeX-formatted formulae.

#### Results

`sag_ratio`, `sag_percentage`, `v_peak`, `v_ss`, `v_baseline`,
`rebound_depolarization`

#### Visualization

Blue h-line at V_baseline; magenta h-line at V_peak; red h-line at V_ss.

---

## 5. Batch Processing

### 5.1 Overview

The batch engine applies an analysis pipeline across many files automatically,
producing a consolidated Pandas DataFrame result.  Every row is fully traceable
to its source file, channel, trial, and analysis step.  The output is designed
for direct import into Excel, Origin, R, MATLAB, or Python scripts without
post-processing.

### 5.2 Opening the Batch Dialog

Click **"Run Batch"** in the Analyser tab toolbar. The dialog shows:
- The file list from the analysis set (paths *or* in-memory Recording objects).
- A visual pipeline builder with an expanded method selector for browsing
  available analyses by module tab.
- A channel filter (comma-separated names/IDs; leave empty for all channels).
- A progress bar, status label, and cancel button.
- **View Error Log** - opens the batch error log file
  (`~/.synaptipy/logs/batch_errors.log`) for post-hoc diagnosis of failures
  encountered during prior batch runs.

### 5.3 Building a Pipeline

1. **Add Step** - select from all 15 registered analyses (or registered
   preprocessing functions).
2. **Scope** - determines how data is fed to the analysis function:
   - *Average* - single averaged trace across all trials.
   - *All Trials* - analysis runs once per trial, producing one row per trial.
   - *First Trial* - analysis runs on trial 0 only.
   - *Specific Trial* - pick a 0-based trial index.
   - *Channel Set* - pass the full list of trials to the function (e.g. for
     F-I curves or I-V curves that need all sweeps at once).
3. Configure **Parameters** with auto-generated widgets driven by
   `ui_params` in the registry metadata.
4. Optionally prepend **Preprocessing Steps** (filter, baseline-subtract).
   Preprocessing modifies the data context for all subsequent analysis steps
   in the same channel.

### 5.4 Running and Cancellation

A background `QThread` worker processes each file sequentially with per-file
progress updates.  Click **Stop** at any time - cancellation is thread-safe
and produces a partial-result DataFrame.

### 5.5 Output Columns

The result DataFrame uses a standardised column layout designed for
traceability and software compatibility:

#### Metadata columns (always present, appear first)

| Column | Description |
|--------|-------------|
| `file_name` | Source filename (e.g. `cell_01.abf`) |
| `file_path` | Full path to the source file |
| `protocol` | Acquisition protocol name (from file metadata, if available) |
| `recording_duration_s` | Total recording duration in seconds |
| `channel` | Channel name or ID processed |
| `channel_units` | Physical units of the channel (`mV`, `pA`, etc.) |
| `analysis` | Registry key of the analysis function |
| `scope` | Data scope used (`average`, `all_trials`, etc.) |
| `trial_index` | 0-based trial index (present for per-trial scopes) |
| `trial_count` | Total number of trials available in the channel |
| `sampling_rate` | Sampling frequency in Hz |

#### Analysis result columns (middle, alphabetically sorted)

These vary by analysis function.  Examples:

| Analysis | Key columns |
|----------|-------------|
| `rmp_analysis` | `rmp_mv`, `rmp_std`, `rmp_drift` |
| `spike_detection` | `spike_count`, `mean_freq_hz`, `ap_threshold_mean`, `half_width_mean`, `ahp_depth_mean` |
| `rin_analysis` | `rin_mohm`, `conductance_us`, `voltage_deflection_mv`, `current_injection_pa` |
| `event_detection_*` | `event_count`, `frequency_hz`, `mean_amplitude` |
| `excitability_analysis` | `rheobase_pa`, `fi_slope`, `max_freq_hz` |
| `capacitance_analysis` | `capacitance_pf`, `tau_ms`, `rin_mohm` |
| `optogenetic_sync` | `optical_latency_ms`, `response_probability`, `spike_jitter_ms` |

Most keys include a **unit suffix** (`_mv`, `_ms`, `_hz`, `_pa`, `_pf`, `_mohm`)
to eliminate ambiguity.  Dimensionless metrics (e.g. `cv`, `cv2`, `lv`) have
human-readable alias columns added automatically (e.g. `coeff_of_variation`).

#### Array-valued results

Large arrays (spike times, I-V curves, etc.) are summarised as compact strings
in the tabular output (e.g. `n=42, mean=15.3, min=2.1, max=87.6`) and can be
recovered from the full DataFrame in Python via the `_<key>_raw` private column.
Small arrays (≤ 5 elements) are kept inline as lists.

#### Trailing / debug columns

| Column | Description |
|--------|-------------|
| `batch_timestamp` | ISO 8601 timestamp of batch start |
| `error` | Error message if the analysis failed for this row |
| `debug_trace` | Python traceback (only present on error rows) |

Error rows preserve all metadata columns (file, channel, analysis, scope) so
they can be filtered and diagnosed in Excel without losing context.

### 5.6 Export Formats

Click **"Export Results..."** to save:

| Format | Extension | Notes |
|--------|-----------|-------|
| **CSV** | `.csv` | Comment header with batch metadata (`#`-prefixed lines). Private/raw-array columns are stripped. Opens directly in Excel, Origin, R. |
| **Excel** | `.xlsx` | Requires `openpyxl` (falls back to CSV if not installed). Private columns stripped. |
| **JSON** | `.json` | Array of records with `indent=2`. **Arrays are preserved as lists** for programmatic consumption. |

CSV files include a reproducibility header:
```
# Synaptipy Batch Analysis Export
# Exported: 2026-03-03T14:22:01
# Files processed: 12
# Pipeline: rmp_analysis -> spike_detection
# Rows: 48
#
file_name,file_path,channel,...
```

### 5.7 Programmatic / Headless Batch Usage

The engine can be used from Python scripts without the GUI:

```python
from pathlib import Path
from Synaptipy.core.analysis.batch_engine import BatchAnalysisEngine

# IMPORTANT: import the full package to register all analysis functions
import Synaptipy.core.analysis  # noqa: F401

engine = BatchAnalysisEngine()

files = list(Path("data/").glob("*.abf"))

pipeline = [
    {"analysis": "rmp_analysis", "scope": "average",
     "params": {"baseline_start": 0.0, "baseline_end": 0.1}},
    {"analysis": "spike_detection", "scope": "all_trials",
     "params": {"threshold": -20.0}},
]

df = engine.run_batch(files, pipeline, channel_filter=["Vm_prime"])

# Export - private columns are internal; drop for clean CSV
clean = df.drop(columns=[c for c in df.columns if c.startswith("_")])
clean.to_csv("results.csv", index=False)
```

### 5.8 Pipeline Context and Preprocessing

When a **preprocessing** step (e.g. bandpass filter, baseline subtraction) is
in the pipeline, it modifies the data context passed to all subsequent analysis
steps for the *same channel*.  This means you can build pipelines like:

1. Bandpass Filter (preprocessing) - modifies data in-place in the context
2. Spike Detection - runs on filtered data
3. Burst Analysis - also runs on filtered data

The context is not shared across channels or files.

### 5.9 Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| 0 analyses in Add Step dialog | Registry not populated | Ensure `import Synaptipy.core.analysis` runs before opening dialog (automatic in GUI) |
| Channel filter matches nothing | Name mismatch | Check exact channel names in Explorer tab; filter is case-sensitive |
| Empty results DataFrame | No data in requested scope | Verify the file has trials and the scope matches (e.g. `all_trials` on a file with 1 trial still works) |
| `error` column filled | Analysis function raised exception | Check the `debug_trace` column or export to JSON for the full traceback |
| Array columns show "n=..." | Large arrays are summarised | Use JSON export or access `_<key>_raw` columns in Python for raw data |

---

## 6. The Exporter Tab

![Exporter Tab](screenshots/exporter_tab.png)

### 6.1 NWB Export

Click **"Export to NWB"** to open the NWB Metadata Dialog (four tabs):
- **Session** - description, UUID (auto-generated), start time, experimenter,
  lab, institution.
- **Subject** - ID, species, age (ISO 8601), sex, genotype, weight.
- **Device** - name, description, manufacturer.
- **Electrodes** - description, location, filtering.

The exporter automatically selects the appropriate NWB series class based on
channel units:

| Channel Units | NWB Series Class | SI Conversion |
|---------------|-----------------|---------------|
| mV, millivolt | `CurrentClampSeries` | ×10⁻³ → volts |
| pA, picoampere | `VoltageClampSeries` | ×10⁻¹² → amperes |
| nA, nanoampere | `VoltageClampSeries` | ×10⁻⁹ → amperes |
| Other / unknown | `PatchClampSeries` | stored as-is |

All data is stored in SI base units (volts, amperes) with the appropriate
`conversion` factor recorded in the NWB metadata.  For full details on the
mapping between Synaptipy data structures and NWB containers, see the
[NWB Export Mapping](../nwb_mapping.md) reference page.

Produces a `.nwb` file with raw traces, metadata, and extracted features -
fully NWB 2.x compliant.

### 6.2 Analysis Results Export

- Table: Analysis Type, Source File, Data Source, Value, Timestamp, Details.
- Select/deselect individual results with checkboxes.
- **Refresh Results** syncs with the latest session accumulation.
- Export to **CSV** (R, MATLAB, Python) or **JSON** (nested structure).

### 6.3 Plot Export

Every Explorer and Analyser sub-tab has a **"Save Plot"** button that opens an
export dialog with the following options:

- **Preset selector** - Choose from predefined configurations:
  - *Journal Quality (300 DPI, PDF)* - Vector format at 300 DPI, suitable for
    submission to peer-reviewed journals.
  - *Presentation (150 DPI, PNG)* - Raster format at 150 DPI for slide decks.
  - *Web (72 DPI, PNG)* - Lightweight raster for online documentation or reports.
  - *Custom* - Manual control over format and resolution.
- **Format** - PNG, JPG, SVG, or PDF. Vector formats (SVG, PDF) retain editable
  text and scale without loss.
- **DPI** - Configurable resolution from 72 to 1200 dpi.

---

## 7. Preferences and Customization

### 7.1 Preferences Dialog (Edit → Preferences)

- **Scroll Behavior** - Natural, Inverted, or System.
- **Appearance** - Switch between **Light**, **Dark**, or **System** theme.
  - *Light* and *Dark* switch to the cross-platform Fusion style and apply an
    explicit color palette so the result is consistent across macOS, Windows,
    and Linux.
  - *System* restores the OS-native style and palette exactly as they were at
    application startup. All three modes cycle fully at runtime with no restart.
  - Opening the Preferences dialog no longer flashes a theme preview -
    signals are blocked while the radio buttons are initialised.

### 7.2 Plot Customization Dialog (View → Plot Customization)

- **Average Trace** - color, line width, opacity.
- **Single Trial** - color, line width, opacity + "Force Opaque" toggle for faster
  rendering on large datasets.
- **Grid** - grid line customization.
- Each tab includes **Reset to Defaults**.

### 7.3 Analysis Defaults (Edit → Configure Analysis Defaults)

Registry parameter editor - modify default values for any analysis module.
Changes persist across sessions.

---

## Appendix A: Supported File Formats

| Format | Extensions |
|--------|------------|
| Axon ABF | `.abf` |
| Spike2 | `.smr`, `.smrx` |
| NeuroExplorer | `.nex` |
| HDF5 / NWB | `.h5`, `.nwb` |
| Axon ATF | `.atf` |
| Igor Pro | `.ibw`, `.pxp` |
| And many more | See [Neo documentation](https://neo.readthedocs.io/en/latest/iolist.html) |

---

## Appendix B: Architecture

| Layer | Responsibility | Technologies |
|-------|---------------|--------------|
| **Core** | Signal processing, analysis algorithms | NumPy, SciPy, pure Python |
| **Application** | GUI, user interaction, visualization | PySide6, PyQtGraph |
| **Infrastructure** | File I/O, format adapters | Neo, PyNWB |

The **Registry Pattern** (`AnalysisRegistry`) provides plugin-style extensibility:
any script decorated with `@AnalysisRegistry.register` automatically becomes a new
analysis sub-tab in the GUI.

### Adding Your Own Analysis (Plugin Interface)

You can add custom analysis functions **without modifying any Synaptipy source
code**.  Place a Python script in `~/.synaptipy/plugins/` with a function
decorated by `@AnalysisRegistry.register(...)` and restart the application.
Your analysis will appear as a new tab in the Analyser, complete with
auto-generated parameter widgets, a results table, and plot overlays - all
driven by metadata in the decorator.

A ready-to-copy template is included at `src/Synaptipy/templates/plugin_template.py`.
For the full step-by-step guide, parameter reference, and annotated examples,
see **[Writing Custom Analysis Plugins](../extending_synaptipy.md)**.
