Skip to content

Epoch reviewer Phase 0: ship epoch arrays to a static Canvas renderer#223

Open
jdpigeon wants to merge 2 commits into
epoch-cleaned-epochs-writebackfrom
epoch-reviewer-phase0
Open

Epoch reviewer Phase 0: ship epoch arrays to a static Canvas renderer#223
jdpigeon wants to merge 2 commits into
epoch-cleaned-epochs-writebackfrom
epoch-reviewer-phase0

Conversation

@jdpigeon

@jdpigeon jdpigeon commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What & why

Phase 0 of the Interactive Epoch Review UI (docs/epoch-review-ui-plan.md), forged from the plan locked via /plan-eng-review (§0). This half is read-only: it ships epoch data across the worker boundary and draws it, proving the data path and the Canvas 2D rendering choice. Click-to-reject, apply, and live ERP are Phase 1 (next PR).

Locked architecture decisions this implements: OQ1 = Canvas 2D + DOM/SVG overlay, OQ2 = extend the dataKey pattern (no runPython RPC).

Data path (all new)

CleanComponent "Load Dataset"
  → loadEpochsEpic → requestEpochArrays('raw_epochs')
  → worker: get_epochs_arrays() writes a float32 buffer to MEMFS + returns metadata
  → worker posts { buffer, results, dataKey:'epochArrays' }  ← zero-copy transferable
  → pyodideMessageEpic → SetEpochArrays → reducer
  → CleanContainer (...state.pyodide) → CleanComponent → EpochReviewer draws

Changes

  • utils.pyget_epochs_arrays(epochs, out_path) → float32 C-order [epoch][channel][time] buffer + metadata (ch_names EEG-only with the Marker/stim channel excluded via pick_types(eeg=True), sfreq, times, event_codes, drop_log). plot_topo gains a palette param; the condition-color fallback is DRYed to one _DEFAULT_CONDITION_PALETTE.
  • webworker.js — the readFileAfter read-back is unified to post { buffer, results }; the PR Fix broken Clean→Analyze round-trip: persist cleaned epochs to host disk #222 savedEpochs write-back migrated to the same field (verified still working).
  • conditionPalette.ts (new) — canonical condition palette, single source for both the Python matplotlib plots (JSON-injected into plot_topo/plot_conditions) and the React reviewer.
  • reduxSetEpochArrays action + EpochArraysMeta type + reducer state (reset on experiment cleanup).
  • EpochReviewer.tsx (new) — Canvas 2D traces (epochs side-by-side, channels stacked) + DOM overlay (channel/epoch labels, dividers), viewport windowing (first 8 epochs), spike-preserving min/max downsampling, condition coloring, empty state, devicePixelRatio scaling. Pure decode/downsample helpers in epochArrays.ts.

Out of scope (Phase 1)

Click-to-reject, apply_rejection, live ERP preview, scroll/scale navigation.

Verification

  • npm run typecheck0 errors
  • eslint (changed files) — 0 errors (1 pre-existing warning)
  • npx vitest run37/37 pass (incl. 9 new decode/downsample/color-index cases)
  • native pytest tests/analysis/10/10 pass (4 new: buffer decodes to get_data(), Marker excluded, byte length, event codes; 6 existing, no regression)
  • npm run build (electron-vite) — green
  • Independent adversarial review traced the full data path, buffer-layout match, Marker exclusion (no off-by-one), transferable safety, palette single-source, and confirmed no Phase-1 scope drift. Verdict: PASS.

Stacked on epoch-cleaned-epochs-writeback (PR #222). 🤖 Generated with Claude Code

Dano Morrison added 2 commits July 7, 2026 08:47
Resolves the forge-blocking open questions and writes a forge-ready
Phase 0/1 execution plan into docs/epoch-review-ui-plan.md §0:

- OQ1 rendering → Canvas 2D + DOM/SVG overlay, thin drawEpochs boundary,
  viewport windowing; WebGL a later swap.
- OQ2 transport → extend the dataKey pattern (epochArrays, applyRejection);
  defer the runPython RPC (matches TODOS).
- OQ6 live ERP preview → in scope for v1 (Phase 1), computed CLIENT-SIDE
  over the Phase-0 buffer (pure computeErp mean, zero worker round-trips) —
  Flavor 1 (while-cleaning) only; real-time-during-experiment is a separate
  future non-Python effort.
- Shared condition-color palette built in Phase 0 (used by Phase-0 render,
  so not dead code).
- Narrow Pyodide buffer-path fidelity test added in Phase 0.
- Delivery split into two sequential PRs.

Also updates the standing Pyodide-fidelity TODO to note the narrow test.
First half of the interactive epoch-review UI (docs/epoch-review-ui-plan.md
§0/§9). Read-only this phase — proves the data path and the Canvas 2D
rendering choice; click-to-reject, apply, and live ERP are Phase 1.

Data path (all new, extends the proven dataKey pattern — no runPython RPC):
  Load Dataset → loadEpochsEpic → requestEpochArrays('raw_epochs')
    → worker get_epochs_arrays() writes a float32 buffer to MEMFS + returns meta
    → worker posts { buffer, results, dataKey:'epochArrays' } zero-copy transferable
    → pyodideMessageEpic → SetEpochArrays → reducer → CleanComponent → EpochReviewer

- utils.py: get_epochs_arrays(epochs, out_path) → float32 C-order
  [epoch][channel][time] buffer + metadata (Marker/stim channel excluded via
  pick_types(eeg=True)); plot_topo gains a palette param; condition-color
  fallback DRYed to one _DEFAULT_CONDITION_PALETTE constant.
- webworker.js: readFileAfter read-back unified to post { buffer, results }
  (savedEpochs write-back from PR #222 migrated to the same field).
- conditionPalette.ts: canonical condition palette, single source for the
  Python matplotlib plots (JSON-injected) and the React reviewer.
- EpochReviewer.tsx: Canvas 2D traces (epochs side-by-side, channels stacked)
  + DOM overlay (labels, dividers), viewport windowing (first 8 epochs),
  spike-preserving min/max downsampling, condition coloring, empty state, DPR
  scaling. Pure decode/downsample helpers in epochArrays.ts.

Tests: native pytest (get_epochs_arrays buffer decodes to get_data(), Marker
excluded, byte length, event codes) + vitest (decode/downsample/color-index).

typecheck 0 · eslint 0 errors · vitest 37/37 · native pytest 10/10 · build green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant