Skip to content

Fix broken Clean→Analyze round-trip: persist cleaned epochs to host disk#222

Open
jdpigeon wants to merge 1 commit into
pyodide-analysis-fixfrom
epoch-cleaned-epochs-writeback
Open

Fix broken Clean→Analyze round-trip: persist cleaned epochs to host disk#222
jdpigeon wants to merge 1 commit into
pyodide-analysis-fixfrom
epoch-cleaned-epochs-writeback

Conversation

@jdpigeon

@jdpigeon jdpigeon commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What & why

Forged from docs/epoch-review-ui-plan.md (Interactive Epoch Review UI plan). The plan is a preliminary vision doc (Phases 0–4, 8 open questions), so only the one slice it declares resolved and standalone was implemented: the MEMFS→host write-back bridge that repairs a currently-broken flow (plan §6d + Open Question 8).

The bug: The "Clean Data" flow told Pyodide to raw_epochs.save("<host OS path>"), but the worker's filesystem is MEMFS (in-memory) and can't reach host disk. So the cleaned -epo.fif never landed on disk, and the Analyze screen's readWorkspaceCleanedEEGData picker found nothing in a fresh workspace. The Clean→Analyze round-trip was silently broken.

The fix — a write-back bridge across every process boundary

Python raw_epochs.save("/tmp/<subject>-cleaned-epo.fif", overwrite=True)   (MEMFS)
  → worker pyodide.FS.readFile(...) + postMessage bytes zero-copy           (new dataKey 'savedEpochs')
  → pyodideMessageEpic routes savedEpochs → window.electronAPI.writeCleanedEpochs(title, subject, buffer)
  → preload bridge → fs:writeCleanedEpochs main handler
  → fs.writeFile → Data/<subject>/EEG/<subject>-cleaned-epo.fif             (host disk)

The output path is bit-identical to what the Analyze picker globs (epo.fif suffix) and matches OQ8's stated target. The new main handler mirrors the existing fs:storePyodideImagePng (binary renderer→disk write) — the reuse anchor the plan called out.

Files

  • src/main/index.ts — new fs:writeCleanedEpochs IPC handler
  • src/preload/index.ts + src/renderer/types/electron.d.tswriteCleanedEpochs bridge + type
  • src/renderer/utils/webworker/webworker.jsreadFileAfter read-back path posts MEMFS bytes as a transferable ArrayBuffer
  • src/renderer/utils/webworker/index.tssaveEpochs(worker, subject) saves to MEMFS (overwrite=True) and requests read-back
  • src/renderer/epics/pyodideEpics.ts — route savedEpochswriteCleanedEpochs, success/error toast
  • docs/epoch-review-ui-plan.md — tempered against source (grounding corrections)

Explicitly out of scope (blocked on open design questions)

Interactive epoch-array transport, the React EpochReviewer component, click-to-reject, bad-channel flagging, and the onboarding layer all depend on unresolved Open Questions (esp. Canvas vs WebGL, RPC-first). They were intentionally not forged. Note: cleanEpochsEpic still re-saves epochs unchanged (the plan's §4 caveat) — this PR ensures that saved object now actually persists, unblocking future interactive cleaning.

Verification

  • npm run typecheck0 errors
  • eslint on changed files — clean
  • npm run build (electron-vite) — green, worker bundle builds
  • Independent adversarial review confirmed the full chain, transferable safety (FS.readFile returns a fresh file-sized buffer, not the WASM heap), no undefined-buffer path (worker catch posts error, handled first), and MNE -epo.fif suffix satisfied.

🤖 Generated with Claude Code

The "Clean Data" flow told Pyodide to `raw_epochs.save("<host OS path>")`,
but the worker's filesystem is MEMFS (in-memory) and cannot reach host
disk — so the cleaned `-epo.fif` never landed on disk and the Analyze
screen's `readWorkspaceCleanedEEGData` picker found nothing in a fresh
workspace. This repairs that flow with a MEMFS→host write-back bridge,
the one slice the epoch-review plan (§6d / Open Question 8) declares
resolved and standalone.

Chain: Python saves to MEMFS `/tmp/<subject>-cleaned-epo.fif` → the
worker reads the bytes and posts them back zero-copy (transferable
ArrayBuffer) on a new `savedEpochs` dataKey → `pyodideMessageEpic`
writes them to `Data/<subject>/EEG/<subject>-cleaned-epo.fif` via a new
`fs:writeCleanedEpochs` IPC channel that mirrors `fs:storePyodideImagePng`.

- src/main/index.ts: new `fs:writeCleanedEpochs` handler (binary
  renderer→disk write)
- src/preload/index.ts + electron.d.ts: `writeCleanedEpochs` bridge + type
- webworker.js: `readFileAfter` read-back path posts MEMFS bytes as a
  transferable
- webworker/index.ts: `saveEpochs(worker, subject)` now saves to MEMFS
  (overwrite=True) and requests read-back
- pyodideEpics.ts: route `savedEpochs` → `writeCleanedEpochs`, with a
  success/error toast

Output path is bit-identical to what the Analyze picker globs (`epo.fif`
suffix). Also tempers docs/epoch-review-ui-plan.md against source.

typecheck: clean · eslint: clean · 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