Skip to content

Commit 87007f0

Browse files
eurunuelaclaude
andcommitted
Add interactive brain visualization with Niivue integration
- Add BrainViewer component using Niivue for 3D brain stat maps - Add TimeSeries component for component time series visualization - Add FFTSpectrum component for power spectrum analysis - Create tsvParser and fftUtils utilities for data processing - Load mixing matrix TSV and 4D NIfTI files from tedana output - Implement custom RdBu colormap (blue-white-red) matching matplotlib - Display axial, sagittal, coronal views in row layout - Add mask overlay support for brain boundaries - Update Plots layout with interactive views on right side - Fallback to PNG display when NIfTI/mixing data unavailable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2250bc3 commit 87007f0

12 files changed

Lines changed: 1647 additions & 27 deletions

claude-progress.txt

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,218 @@ src/
325325

326326
---
327327

328+
## Session 3 - Niivue Integration (2025-12-14)
329+
330+
### Goals
331+
- Replace static PNG brain images with interactive Niivue 3D viewer
332+
- Add interactive time series visualization
333+
- Add FFT power spectrum visualization
334+
- Synchronize all visualizations with component selection
335+
336+
### Changes Made
337+
338+
#### New Dependencies (package.json)
339+
- `@niivue/niivue` - WebGL2-based neuroimaging viewer
340+
- `fft.js` - Fast Fourier Transform for power spectrum computation
341+
342+
#### New Utility Files
343+
1. **src/utils/tsvParser.js** - Parse mixing matrix TSV file
344+
- `parseMixingMatrix()` - Converts TSV to component time series array
345+
- `getComponentIndex()` - Maps component labels to array indices
346+
347+
2. **src/utils/fftUtils.js** - FFT computation utilities
348+
- `computePowerSpectrum()` - One-sided power spectrum from time series
349+
- `powerToDecibels()` - Convert power to dB scale
350+
351+
#### New Visualization Components
352+
1. **src/Plots/TimeSeries.js** - visx line chart for component time series
353+
- Zoom/pan support with @visx/zoom
354+
- Hover tooltips showing TR and value
355+
- Blue line (#3b82f6) color scheme
356+
357+
2. **src/Plots/FFTSpectrum.js** - visx line chart for power spectrum
358+
- Computes FFT internally using fft.js (memoized)
359+
- X-axis: Frequency (cycles/TR)
360+
- Y-axis: Power with exponential notation
361+
- Green line (#10b981) color scheme
362+
363+
3. **src/Plots/BrainViewer.js** - Niivue wrapper component
364+
- Loads 4D NIfTI from ArrayBuffer
365+
- Uses `setFrame4D()` to display selected component
366+
- Warm colormap for z-scores
367+
- Multi-planar slice view
368+
- Loading and error states
369+
370+
#### Data Loading Updates (IntroPopUp.js)
371+
- Added `readFileAsArrayBuffer()` helper
372+
- New file patterns:
373+
- `*_mixing.tsv` (excluding PCA) - ICA time series
374+
- `*stat-z_components.nii.gz` (ICA) - 4D brain maps
375+
- Passes `mixingMatrix` and `niftiBuffer` to parent
376+
377+
#### State Management (index.js)
378+
- New state: `mixingMatrix`, `niftiBuffer`
379+
- Passes new props to Plots component
380+
381+
#### Plots.js Integration
382+
- New props: `mixingMatrix`, `niftiBuffer`
383+
- `hasInteractiveViews` - Conditional rendering flag
384+
- `currentTimeSeries` - Memoized time series for selected component
385+
- Graceful fallback to PNG if NIfTI/mixing files not available
386+
387+
### New Layout (Right Side)
388+
```
389+
+---------------------------+
390+
| Time Series (600x180) |
391+
+---------------------------+
392+
| Brain Stat Map |
393+
| (Niivue - 600x400) |
394+
+---------------------------+
395+
| FFT Spectrum (600x180) |
396+
+---------------------------+
397+
```
398+
399+
### Files Created
400+
- `src/utils/tsvParser.js`
401+
- `src/utils/fftUtils.js`
402+
- `src/Plots/TimeSeries.js`
403+
- `src/Plots/FFTSpectrum.js`
404+
- `src/Plots/BrainViewer.js`
405+
406+
### Files Modified
407+
- `package.json` - Added @niivue/niivue, fft.js
408+
- `src/PopUps/IntroPopUp.js` - Load mixing TSV + NIfTI
409+
- `src/index.js` - Add state, pass props
410+
- `src/Plots/Plots.js` - Import new components, conditional layout
411+
412+
### Technical Notes
413+
- Niivue requires WebGL2 canvas
414+
- 4D NIfTI loaded as ArrayBuffer from FileReader
415+
- `setFrame4D(volumeId, index)` switches displayed volume
416+
- FFT memoized to prevent expensive recomputation
417+
- Graceful degradation if new files not present
418+
419+
### Test Data
420+
Location: `/Users/eurunuela/tedana_for_rica_tests/`
421+
- 32 ICA components
422+
- ~200 timepoints
423+
- 3MB NIfTI file
424+
425+
---
426+
427+
## Session 3.1 - Niivue Fixes (2025-12-14)
428+
429+
### Issues Addressed
430+
1. **NIfTI loading error** - "Failed to parse NIfTI file components.nii.gz"
431+
2. **Wrong layout order** - Brain map should be in middle, not on top
432+
3. **Width mismatch** - Visualizations should match previous PNG width
433+
434+
### Changes Made
435+
436+
#### BrainViewer.js Fixes
437+
- Changed from `loadVolumes()` with `buffer` property to `loadFromArrayBuffer()`
438+
- Added Uint8Array conversion: `new Uint8Array(niftiBuffer)`
439+
- Made `attachToCanvas()` async (added await)
440+
```javascript
441+
// Before (broken)
442+
const volumeData = { url: "components.nii.gz", buffer: niftiBuffer };
443+
await nv.loadVolumes([volumeData]);
444+
445+
// After (working)
446+
const uint8Array = new Uint8Array(niftiBuffer);
447+
await nv.loadFromArrayBuffer(uint8Array, "components.nii.gz");
448+
```
449+
450+
#### Plots.js Layout Update
451+
- Changed order: Time Series (top) → Brain Map (middle) → FFT (bottom)
452+
- Unified width to 600px for all components
453+
- Adjusted heights: TimeSeries 180px, BrainViewer 400px, FFTSpectrum 180px
454+
- Changed gap from 4 to 2 for tighter stacking
455+
456+
### Files Modified
457+
- `src/Plots/BrainViewer.js` - Fixed Niivue ArrayBuffer loading
458+
- `src/Plots/Plots.js` - Updated layout order and dimensions
459+
460+
---
461+
462+
## Session 3.2 - Niivue Colormap and Display Polish (2025-12-14)
463+
464+
### Goals
465+
- Implement proper RdBu colormap matching matplotlib
466+
- Configure multiplanar view (axial, sagittal, coronal in row)
467+
- Match visualization widths and colors with component classification
468+
- Polish background and colormap display
469+
470+
### Changes Made
471+
472+
#### BrainViewer.js - Colormap Implementation
473+
1. **Custom RdBu colormap** - Created two half-colormaps to match matplotlib's RdBu:
474+
- `white2red`: For positive values (white at 0 → red at max)
475+
- `blue2white`: For negative values (blue at -max → white at 0)
476+
477+
2. **Colormap range**: Set to 90% of absolute maximum for symmetric display
478+
479+
3. **Multiplanar configuration**:
480+
- `setSliceType(nv.sliceTypeMultiplanar)` - Enable multiplanar view
481+
- `setMultiplanarLayout(3)` - ROW layout (axial, sagittal, coronal side by side)
482+
- `multiplanarShowRender = 0` - Disable 3D render
483+
- `multiplanarEqualSize = true` - Equal size panels
484+
- `multiplanarPadPixels = 0` - No gaps between views
485+
486+
4. **Background**: White background (`backColor: [1, 1, 1, 1]`)
487+
488+
5. **Loading method**: Changed to Blob URL approach for reliable NIfTI loading
489+
490+
#### Plots.js - Visualization Updates
491+
1. **Width**: All visualizations (TimeSeries, BrainViewer, FFTSpectrum) set to 800px
492+
2. **Line colors**: Dynamic based on classification
493+
- Accepted components: Green (#22C55E)
494+
- Rejected components: Red (#EF4444)
495+
496+
#### TimeSeries.js & FFTSpectrum.js
497+
- Added `lineColor` prop for dynamic coloring
498+
- Default colors updated to match app theme
499+
500+
#### IntroPopUp.js - Mask Loading
501+
- Added detection and loading of mask files (`*_mask.nii*`)
502+
- Passes `maskBuffer` to parent component for optional underlay
503+
504+
### Technical Details
505+
```javascript
506+
// Custom colormaps for diverging display
507+
const white2red = {
508+
R: [255, 254, 252, 250, 246, 239, 229, 215, 199, 180, 165, 146, 127, 103, 84, 67],
509+
G: [255, 240, 220, 199, 175, 150, 125, 99, 75, 55, 40, 28, 18, 8, 2, 0],
510+
B: [255, 237, 214, 190, 165, 140, 114, 89, 68, 50, 38, 28, 20, 13, 7, 3],
511+
...
512+
};
513+
514+
const blue2white = {
515+
R: [5, 24, 47, 75, 103, 134, 162, 186, 206, 222, 235, 244, 250, 253, 254, 255],
516+
G: [48, 78, 111, 143, 173, 197, 217, 232, 243, 250, 253, 254, 254, 254, 255, 255],
517+
B: [97, 127, 157, 183, 206, 224, 238, 247, 252, 254, 254, 254, 254, 254, 255, 255],
518+
...
519+
};
520+
```
521+
522+
### Files Modified
523+
- `src/Plots/BrainViewer.js` - Custom RdBu colormap, multiplanar config, Blob URL loading
524+
- `src/Plots/Plots.js` - Width 800px, dynamic line colors
525+
- `src/Plots/TimeSeries.js` - Added lineColor prop
526+
- `src/Plots/FFTSpectrum.js` - Added lineColor prop
527+
- `src/PopUps/IntroPopUp.js` - Mask file loading
528+
- `src/index.js` - maskBuffer state
529+
530+
### Colormap Evolution
531+
1. Started with `blue2red` built-in colormap
532+
2. Tried `jet` colormap (user disliked green background)
533+
3. Added 0.001 threshold to hide near-zero values
534+
4. Implemented custom RdBu matching matplotlib
535+
5. Split into two half-colormaps for proper diverging display
536+
6. Removed threshold as no longer needed with proper colormap
537+
538+
---
539+
328540
## Template for Future Sessions
329541

330542
```

features.json

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,73 @@
280280
"Verify the class is compiled into output.css"
281281
],
282282
"passes": false
283+
},
284+
{
285+
"id": "display_brain_stat_map",
286+
"category": "visualization",
287+
"description": "Display interactive 3D brain stat map using Niivue for selected ICA component",
288+
"steps": [
289+
"Load a tedana folder containing *stat-z_components.nii.gz",
290+
"Navigate to the 'ICA' tab",
291+
"Verify brain viewer displays axial, sagittal, and coronal views in a row",
292+
"Verify RdBu colormap (blue-white-red) is applied",
293+
"Select different components and verify brain map updates"
294+
],
295+
"passes": true
296+
},
297+
{
298+
"id": "display_time_series",
299+
"category": "visualization",
300+
"description": "Display interactive time series plot for selected ICA component from mixing matrix",
301+
"steps": [
302+
"Load a tedana folder containing *_mixing.tsv",
303+
"Navigate to the 'ICA' tab",
304+
"Verify time series chart displays above brain viewer",
305+
"Verify line color matches component classification (green=accepted, red=rejected)",
306+
"Hover to see TR and value tooltips",
307+
"Use scroll to zoom and drag to pan"
308+
],
309+
"passes": true
310+
},
311+
{
312+
"id": "display_fft_spectrum",
313+
"category": "visualization",
314+
"description": "Display FFT power spectrum for selected ICA component time series",
315+
"steps": [
316+
"Load a tedana folder containing *_mixing.tsv",
317+
"Navigate to the 'ICA' tab",
318+
"Verify FFT spectrum chart displays below brain viewer",
319+
"Verify line color matches component classification",
320+
"Verify X-axis shows frequency (cycles/TR) and Y-axis shows power",
321+
"Hover to see frequency and power tooltips"
322+
],
323+
"passes": true
324+
},
325+
{
326+
"id": "synchronized_component_views",
327+
"category": "interactivity",
328+
"description": "All visualizations update synchronously when selecting a different component",
329+
"steps": [
330+
"Load a tedana folder with mixing matrix and NIfTI files",
331+
"Select a component from scatter plot or pie chart",
332+
"Verify brain stat map updates to show selected component volume",
333+
"Verify time series updates to show selected component",
334+
"Verify FFT spectrum updates based on new time series",
335+
"Verify all views reflect component classification color"
336+
],
337+
"passes": true
338+
},
339+
{
340+
"id": "fallback_png_display",
341+
"category": "visualization",
342+
"description": "Fall back to static PNG display when NIfTI/mixing files are not available",
343+
"steps": [
344+
"Load a tedana folder without mixing TSV or NIfTI files",
345+
"Navigate to the 'ICA' tab",
346+
"Verify static PNG component images are displayed instead of interactive views",
347+
"Verify component selection still updates displayed PNG"
348+
],
349+
"passes": true
283350
}
284351
]
285352
}

0 commit comments

Comments
 (0)