PSTH Widget Roadmap

PSTHWidget Development Roadmap

This document tracks remaining development tasks for the PSTH Widget. For documentation of completed features and current architecture, see the main developer documentation.

Completed Work Summary

The following features are complete and documented in index.qmd:

  • Core rendering: OpenGL pipeline, GatherResult integration, HistogramMapper, SceneRenderer
  • Binning: Uniform time bins with configurable bin size
  • Plot styles: Bar chart and step-function line rendering via shared HistogramMapper
  • Interaction: Separated X/Y zoom, pan, double-click time navigation
  • Axis system: RelativeTimeAxisWidget (X) and VerticalAxisWidget (Y) with bidirectional sync
  • Properties panel: Alignment controls, event series management, style selection, per-event color picker
  • State serialization: Full round-trip JSON via rfl::json
  • Rate estimation integration: PSTHStateData now stores EstimationParams variant directly. PSTHPropertiesWidget uses the shared EstimationMethodControls widget from RateEstimationControls library for method selection and per-method parameter editing. Renderer extracts bin size from estimationParams() via std::get_if<BinningParams>.
  • Normalization/scaling : PSTHStateData stores ScalingMode from the shared RateEstimate.hpp. PSTHPropertiesWidget uses the shared ScalingModeControls widget. PSTHPlotOpenGLWidget::rebuildScene() applies scaling via applyScaling() from RateNormalization.hpp, supporting all scaling modes (Raw Count, Firing Rate Hz, Z-Score, Normalized [0,1], Count/Trial).

Phase 3: Variability and Confidence Intervals

Status: Design phase. Depends on Phase 1 (normalization) and the Rate Estimation Redesign.

Goal: Display trial-to-trial variability in the PSTH, providing a visual measure of the reliability of the rate estimate.

This phase uses the shared uncertainty infrastructure from the redesign rather than defining bespoke per-trial histogram or confidence band types.

3.1 Per-Trial Data via estimateRateWithTrials()

The shared library provides estimateRateWithTrials(), which returns a RateEstimateWithTrials containing both the aggregate RateEstimate (mean across trials) and a PerTrialData matrix (each trial’s individual rate curve). This is the opt-in computation path — estimateRate() (the default) does not allocate per-trial storage.

See Rate Estimation Redesign — Per-Trial Data.

3.2 Confidence Bands via Shared API

The shared ConfidenceBand struct and computation functions handle all variability modes:

struct ConfidenceBand {
    std::vector<double> lower;    ///< Lower bound at each time point
    std::vector<double> upper;    ///< Upper bound at each time point
};

auto ci = computeSEM(data, 1.96);               // SEM × 1.96 for 95% CI
auto ci = computePercentileCI(data, 2.5, 97.5); // Percentile-based 95% CI
auto ci = bootstrapCI(data, 1000, 0.95);        // Bootstrap CI

For the normalization + uncertainty pipeline, use the applyScaling(RateEstimateWithTrials &) overload to ensure aggregate and per-trial data are normalized consistently before computing CI:

auto data = estimateRateWithTrials(gathered, tf, window_size, params);
applyScaling(data, ScalingMode::ZScore, 1000.0);  // normalizes aggregate + all trials
auto ci = computeSEM(data);  // CI is in z-score space — consistent

See Rate Estimation Redesign — Uncertainty.

3.3 Rendering Confidence Bands

Confidence bands should be rendered as a filled semi-transparent region behind the mean line/bars. The ConfidenceBand provides lower[i] and upper[i] at each time point, which maps directly to a filled polygon or rectangle batch.

The HistogramMapper could be extended with a toConfidenceBand() method that produces a filled polygon batch, or a new ConfidenceBandMapper could be introduced. The band should use the same fill color as the histogram but with reduced opacity (e.g., alpha = 0.2).

3.4 Properties Panel

Add controls to PSTHPropertiesWidget:

  • Variability mode combo box (None, SEM, CI95, Percentile, Bootstrap)
  • Confidence band color/opacity slider
  • For Percentile mode: lower and upper percentile spinboxes

Phase 4: Cross-Widget Linking

Status: Design complete. pinned field exists in EventPlotWidget state. Implementation needed for PSTHWidget.

Goal: Enable PSTH, EventPlotWidget, and HeatmapWidget to synchronize on active unit selection.

4.1 SelectionContext Integration

Following the existing SelectionContext pattern used by DataInspector widgets and described in the EventPlot Roadmap:

void PSTHPropertiesWidget::_onSelectionChanged(SelectionSource const & source) {
    if (_state && _state->isPinned()) return;
    if (_state && source.editor_instance_id.toString() == _state->getInstanceId()) return;

    if (_selection_context) {
        auto const selected = _selection_context->primarySelectedData();
        if (selected.isValid()) {
            auto type = _data_manager->getType(selected.toString().toStdString());
            if (type == DM_DataType::DigitalEventSeries) {
                updateDisplayedEventSeries(selected.toString());
            }
        }
    }
}

4.2 Pin Button

Add a pinned field to PSTHStateData and a pin/unpin toggle button to the properties panel toolbar:

struct PSTHStateData {
    // ... existing fields ...
    bool pinned = false;
};

When pinned, the widget ignores SelectionContext changes, preserving its current view. This enables workflows where one PSTH is locked to a specific unit while another follows the user’s selection.

The pinned state should be serialized for workspace restoration.

4.3 Heatmap → PSTH + Raster Flow

The primary cross-widget workflow:

HeatmapWidget row click
        ↓
SelectionContext::setSelectedData("unit_42", source)
        ↓
PSTHWidget (if not pinned) receives selectionChanged
        ↓
Checks type == DigitalEventSeries → updates histogram
        ↓
EventPlotWidget (if not pinned) also receives selectionChanged
        ↓
Updates raster plot for same unit

This enables rapid exploration of neural data: click through units in the heatmap while the PSTH and raster plot update in sync. See the EventPlot Roadmap Phase 6 for the complementary EventPlot implementation.

4.4 Shared Alignment State (Future Consideration)

Currently, each trial-aligned widget (EventPlot, PSTH, Heatmap) maintains its own independent PlotAlignmentState. A future enhancement could allow these widgets to share a single alignment state so that changing the alignment event or window size in one widget updates all linked widgets simultaneously. See EventPlot Roadmap Phase 6 for discussion.


Phase 5: SVG Export

Status: Infrastructure exists in CorePlotting::Export. Implementation needed for PSTHWidget.

Goal: Export the current PSTH plot to SVG for publication-quality figures.

5.1 Existing Infrastructure

The project already has SVG export infrastructure in CorePlotting/Export/SVGPrimitives.hpp:

  • buildSVGDocument(RenderableScene, SVGExportParams) — generates a complete SVG from any RenderableScene
  • renderRectangleBatchToSVG() — converts bar charts to <rect> elements
  • renderPolyLineBatchToSVG() — converts line plots to <polyline> elements
  • renderSceneToSVG() — renders a full scene including all batch types

The DataViewerWidget already uses this infrastructure via a bespoke SVGExporter class that builds scenes from the DataViewer’s specific state format.

5.2 Shared SVG Export for Plot Widgets

Rather than building another bespoke exporter, a shared SVG export mechanism should be created for all plot widgets that use SceneRenderer. Since the PSTH, EventPlot, and other OpenGL widgets all produce RenderableScene objects, the export path is:

namespace Plots::Export {
    QString exportPlotToSVG(
        RenderableScene const & scene,
        glm::mat4 const & view_matrix,
        glm::mat4 const & projection_matrix,
        SVGExportParams const & params);
}

This can be shared across EventPlotWidget, PSTHWidget, ACFWidget, and any future OpenGL plot widget.

5.3 UI Integration

Add an “Export SVG…” action to the PSTH properties panel or a right-click context menu on the plot. The action should:

  1. Open a file save dialog
  2. Build the SVG from the cached RenderableScene and current view/projection matrices
  3. Optionally include axis labels, title, and scalebar
  4. Save to the chosen path

5.4 Export Options

struct PlotSVGExportOptions {
    int width = 1920;
    int height = 1080;
    bool include_axes = true;
    bool include_title = true;
    bool include_scalebar = false;
    int scalebar_length = 100;      // in time units
    std::string background_color = "#FFFFFF";  // White for publication
};

Phase 6: Embeddable PSTH for Tooltips

Status: Design exploration. Depends on histogram infrastructure being factored for lightweight use.

Goal: Provide a lightweight, embeddable PSTH rendering that can appear in tooltips — for example, when hovering over a row of the HeatmapWidget.

6.1 Concept

When a user hovers over a unit row in the HeatmapWidget, a tooltip could display:

  • A miniature PSTH for that unit
  • Optionally, a combined raster + PSTH view

This requires decoupling the PSTH computation and rendering from the full widget machinery.

6.2 Lightweight Histogram Renderer

Use the shared estimateRate() from the EventRateEstimation library to compute the histogram data. The RateEstimate output (paired times[] + values[]) can be rendered directly by a lightweight QPainter-based renderer without needing the full OpenGL widget machinery.

For tooltip rendering, there are two approaches:

  1. QPixmap approach: Render the histogram to a QPixmap via a headless OpenGL context or a software rasterizer, then display in QToolTip
  2. QPainter approach: Implement a simple QPainter-based histogram renderer that doesn’t require OpenGL. This is simpler and avoids GL context issues in tooltips.

6.3 QPainter-based Mini Renderer

namespace CorePlotting::PSTH {
    QPixmap renderMiniPSTH(
        HistogramData const & data,
        HistogramStyle const & style,
        QSize size = {200, 80});
}

This would use QPainter to draw bars/lines onto a QPixmap, suitable for embedding in tooltips, table cells, or any widget that can display a pixmap.

6.4 Combined Raster + PSTH Tooltip

For the HeatmapWidget tooltip showing both raster and PSTH:

┌─────────────────────┐
│ ▎ ▎  ▎▎   ▎  ▎     │  ← raster (mini)
│  ▎▎   ▎  ▎ ▎  ▎▎   │
│ ▎  ▎▎ ▎    ▎   ▎    │
├─────────────────────┤
│ ▓▓▓▓████▓▓▓▓▓▓▓▓▓  │  ← PSTH (mini)
└─────────────────────┘
  Unit: spikes_unit_42

This requires factoring out the EventPlot’s raster rendering in a similar lightweight fashion.

6.5 Reuse with ACFWidget

The same QPainter-based mini histogram renderer could serve ACFWidget for ISI distribution tooltips or summary displays in other contexts where a full OpenGL widget is overkill.


Phase 7: Per-Series Histograms and Multi-Color Overlay

Status: Partially implemented. Multiple event series can be added but currently all events are binned into a single histogram.

Goal: When multiple DigitalEventSeries are overlaid, render separate histograms per series with distinct colors, with optional stacking or transparency.

7.1 Per-Series Binning

Modify rebuildScene() to produce separate HistogramData per event series rather than accumulating all events into a single histogram.

7.2 Overlay Modes

enum class PSTHOverlayMode {
    Stacked,        ///< Bars stacked vertically (colors distinguish series)
    Overlaid,       ///< Semi-transparent bars overlaid at same baseline
    SideBySide      ///< Bars placed side-by-side within each bin
};

7.3 Color Application

The existing per-event hex_color in PSTHEventOptions should be applied to the HistogramStyle::fill_color when building each series’ scene. Currently, all series share a single default color.


Implementation Priority

Next Up

  1. Phase 2 (remaining): Migrate PSTHPlotOpenGLWidget::rebuildScene() to use EventRateEstimation library’s estimateRate(); then add Gaussian kernel and other smoothing methods
  2. Phase 4: Cross-widget linking with pin support
  3. Phase 5: SVG export (infrastructure already exists)

Following

  1. Phase 7: Per-series histograms and multi-color overlay
  2. Phase 3: Variability / confidence intervals

Later

  1. Phase 6: Embeddable PSTH for tooltips

Open Questions

  1. SVG export scope: Should SVG export be a per-widget feature (button in each widget), or a centralized “Export All Visible Plots” action? The DataViewerWidget already has its own bespoke SVG exporter — should that be migrated to the shared infrastructure?

  2. Tooltip rendering: Should tooltip histograms use QPainter (simpler, no GL context needed) or render to QPixmap via GL (consistent with main rendering)? QPainter is strongly preferred for reliability.

  3. Multiple event series: When overlaying multiple event series, should the default be stacked or overlaid? Neuroscience convention varies — PSTH typically shows one unit at a time, but comparing two conditions on the same unit is common.

See Also