@@ -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```
0 commit comments