Epoch reviewer Phase 2: bad-channel flagging, auto-flag suggestions, condition labels#225
Epoch reviewer Phase 2: bad-channel flagging, auto-flag suggestions, condition labels#225jdpigeon wants to merge 4 commits into
Conversation
Auto-flag = dev-gated one-click button + expandable threshold settings; suggestions computed in Python (peak-to-peak), pre-marked but overridable. Bad-channel flagging enabled on all devices with a warning Dialog on the 2nd bad channel of a 4-ch dataset. Condition legends use codeToLabel.
…bels
Full-parity phase (docs/epoch-review-ui-plan.md §0/§9).
Bad-channel flagging (OQ5):
- Click a channel label to toggle it bad (struck-through red label + red lane
wash across all epochs). Flows into the Phase-1 apply path:
CleanEpochs({dropIndices, badChannels}) → apply_rejection sets info['bads'].
- shadcn Dialog warns when marking a 2nd bad channel on a 4-channel (Muse)
dataset — informational, user can proceed.
Auto-flag (OQ4 + New-A, Python/MNE):
- Dev-gated "Auto-flag artifacts" button (AUTO_FLAG_EXPERIMENTS set, keyed by
the EXPERIMENTS enum) + expandable peak-to-peak threshold settings.
- suggest_rejections(epochs, threshold_uv) in utils.py (peak-to-peak per epoch,
Marker excluded; advisory — drops nothing), native-tested. New
'suggestedRejections' dataKey (fire-and-forget, no runPython RPC) →
SetSuggestedRejections → CleanComponent PRE-MARKS suggested epochs (additive
union, fully overridable) and shows reasons. Actual drop still via
apply_rejection, so the saved .fif stays MNE-exact.
Condition legend (New-B):
- EpochReviewer + LiveErpPane legends use human-readable labels via
buildMarkerRegistry(stimuli).codeToLabel (fallback to "Condition {code}").
typecheck 0 · eslint 0 errors · vitest 44/44 · native pytest 17/17 · build green
Phase 3 (onboarding layer, OQ3 open — route via office-hours/ceo-review) and Phase 4 (N-channel/WebGL, a11y, keyboard, perf; OQ7 open) added under Deferred. Three non-blocking Phase 2 review notes (bad-channel display exclude='bads', additive auto-flag re-merge, threshold min guard) added under tech debt.
jdpigeon
left a comment
There was a problem hiding this comment.
Too many unnecessary comments. Add this repo's preference for avoiding comments outside of function, prop, or data structure definitions to learnings.md
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| ReceiveError: createAction<any, 'RECEIVE_ERROR'>('RECEIVE_ERROR'), // Worker error event — shape is dynamic | ||
| SetWorkerReady: createAction('SET_WORKER_READY'), | ||
| // Auto-flag: request suggestions (payload = peak-to-peak threshold in µV). |
There was a problem hiding this comment.
remove this comment
| // all epochs; amp +/- scale trace amplitude. Rendering stays Canvas 2D + a DOM | ||
| // overlay for labels/hit-targets (no canvas hit-testing). | ||
| // | ||
| // Interactive (Phase 2): channel labels in the left gutter are click-to-flag |
There was a problem hiding this comment.
Remove these process-specific comments. Leaveo only a high level comment that concisely describes the intent and bejavoir of the component.
|
|
||
| const REJECTED_TRACE_COLOR = 'rgba(120, 120, 120, 0.5)'; | ||
| const REJECTED_FILL_COLOR = 'rgba(120, 120, 120, 0.15)'; | ||
| // Translucent wash over a flagged bad channel's lane (drawn across all epochs). |
| const firstShown = clampedStart + 1; | ||
| const lastShown = clampedStart + visibleCount; | ||
|
|
||
| // Unique condition codes (sorted) for the legend — mirrors the canvas draw's |
| return this.state.selectedSubject === subjectFromFilepath; | ||
| }); | ||
|
|
||
| const codeToLabel = buildMarkerRegistry( |
There was a problem hiding this comment.
This smells wrong. buildMarkerRegistry sounds like something that should be executed once rather then to get a label on every render
|
|
||
| // Experiments where the "Auto-flag artifacts" button is offered on the Clean | ||
| // screen. Dev-configurable — add/remove experiments here to opt them in/out. | ||
| export const AUTO_FLAG_EXPERIMENTS = new Set<EXPERIMENTS>([ |
There was a problem hiding this comment.
I don't love this way of registering auto flag. WHy don't we do it the inverse way: add an optional hideAutoFlagEpochs field to an experiment definition data structure -- maybe experiment params? does that not already exist?
| action$.pipe( | ||
| filter(isActionOf(PyodideActions.GetSuggestedRejections)), | ||
| pluck('payload'), | ||
| // Fire-and-forget: result returns via pyodideMessageEpic → |
There was a problem hiding this comment.
Remove this comment. This knowledge should exist in the pyodide skill if it does not already
| }); | ||
| }; | ||
|
|
||
| // Auto-flag: ask Python which epochs look like artifacts (peak-to-peak > |
- Strip process-narrating / Phase-history comments (EpochReviewer top block, pyodideActions, pyodideEpics, webworker index, EpochReviewer color/legend); keep only definition-level intent comments. - Invert auto-flag registration: replace the central AUTO_FLAG_EXPERIMENTS set with an opt-out hideAutoFlagEpochs field on ExperimentParameters (default on), colocated with the experiment definition. - Memoize buildMarkerRegistry by stimuli reference so the code->label map isn't rebuilt on every CleanComponent render. - Record the repo's comment-style preference in .llms/learnings.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uiGzUcGN7j6qDWrfH5MCr
What & why
Phase 2 of the Interactive Epoch Review UI (
docs/epoch-review-ui-plan.md§0/§9) — full parity with (and beyond) MNE's cleaning GUI. Builds on Phase 1 (PR #224). Locked decisions: OQ4 (dev-gated auto-flag button + settings), OQ5 (bad channels enabled everywhere + 4-ch warning), New-A (auto-flag in Python), New-B (codeToLabellegends).Three features
1. Bad-channel flagging (OQ5)
Click a channel label → toggles it bad (struck-through red label + red lane wash across all epochs). Bad channels ride the Phase-1 apply path:
CleanEpochs({dropIndices, badChannels})→apply_rejectionsetsinfo['bads']. A shadcn Dialog warns when you mark a 2nd bad channel on a 4-channel (Muse) dataset, nudging re-collection — informational, you can proceed. (8-ch+ devices don't warn.)2. Auto-flag artifacts (OQ4 + New-A, Python/MNE)
A dev-gated "Auto-flag artifacts" button (visibility via
AUTO_FLAG_EXPERIMENTS, a constant set keyed by theEXPERIMENTSenum — devs opt experiments in/out) with an expandable settings panel exposing the peak-to-peak threshold (µV).Suggestions are advisory — they pre-mark epochs (with reasons shown) but you can unclick any of them; the actual drop still goes through
apply_rejection, so the saved.fifstays MNE-exact.3. Condition legend labels (New-B)
EpochReviewer+LiveErpPanelegends now show human-readable condition labels viabuildMarkerRegistry(stimuli).codeToLabel(fallback "Condition {code}").Changes
utils.py—suggest_rejections(epochs, threshold_uv)(peak-to-peak, Marker excluded, drops nothing), native-tested.SuggestedRejectiontype,Get/SetSuggestedRejections, reducersuggestedRejections(reset on new arrays + cleanup),getSuggestedRejectionsEpic+ message branch,requestSuggestRejections,AUTO_FLAG_EXPERIMENTS+DEFAULT_PTP_THRESHOLD_UV.EpochReviewer(clickable bad-channel labels + lane wash + codeToLabel legend),LiveErpPane(codeToLabel legend),CleanComponent(badChannels state, warning Dialog, auto-flag button/settings, suggestion merge),CleanContainer(passesparams).Out of scope
Phase 3 (onboarding/guided mode).
apply_rejectionreused from Phase 1, untouched.Verification
npm run typecheck— 0 errors ·eslint— 0 errors (2 benign warnings)npx vitest run— 44/44 · nativepytest tests/analysis/— 17/17 (3 newsuggest_rejections)npm run build— greenKnown behaviors flagged in review (non-blocking, candidate follow-ups)
get_epochs_arrays/suggest_rejectionsusepick_types(eeg=True)(MNE defaultexclude='bads') — after a Clean that flags a bad channel, the re-fetched reviewer omits that channel from the display (saved.fifunaffected). Defensible; confirm the UX you want.Stacked on
epoch-reviewer-phase1(PR #224). 🤖 Generated with Claude Code