A browser-based web application for visualizing and analyzing longitudinal MS lesion data. Built with React Native for Web (Expo).
- NIfTI File Loading: Load FLAIR images and lesion masks directly in the browser (no server required).
- Dual Timepoint Comparison: Compare baseline and follow-up scans side-by-side.
- Lesion Matching & Classification: Automatically match lesions across timepoints using graph-based connectivity.
- Interactive 3D Navigation: Navigate through axial, sagittal, and coronal slices.
- Session Statistics: View lesion counts, volumes, new lesions, and volume change metrics.
- Demo Data: Includes sample data for testing the application.
The core analysis logic is implemented in utils/lesionAnalysis.js and consists of two main functions:
Segments a binary mask into individual lesion objects using iterative Union-Find.
| Parameter | Value | Description |
|---|---|---|
| Threshold | > 0.8 |
Voxels above this probability are considered lesion |
| Connectivity | 26-neighbor | Full 3D connectivity (including diagonals) |
| Minimum Size | ≥ 3 voxels | Components smaller than this are discarded |
Output: A labeled mask where each lesion has a unique integer ID, plus a list of lesion objects with centroid, volume, and ID.
Matches lesions between two timepoints using graph-based transitive closure.
Algorithm Steps:
- Label Components: Run
findConnectedComponentson both masks independently. - Build Overlap Graph:
- Nodes: Each component from TP1 (
"1:ID") and TP2 ("2:ID"). - Edges: If any voxel has both a TP1 label and a TP2 label, connect those nodes.
- Nodes: Each component from TP1 (
- Union-Find Grouping: Use Union-Find to merge overlapping components into "Unified Lesions".
- Classification: For each Unified Lesion:
- New: No TP1 components, only TP2.
- Gone: Only TP1 components, no TP2.
- Growing: TP2 volume > TP1 volume.
- Shrinking: TP2 volume < TP1 volume.
- Static: TP1 volume = TP2 volume.
Unified Lesion Count: The number of graph nodes with at least one component at each timepoint. This handles merges (2 TP1 blobs → 1 TP2 blob) and splits correctly.
Displayed in the UI via components/SessionStats.js:
| Metric | Calculation |
|---|---|
| Lesion Count | Unified Lesion Count (not raw blob count) |
| Lesion Volume | Sum of all lesion voxels × voxel volume (mm³) |
| New Lesions | Count of Unified Lesions with status = 'new' |
| Vol Change | (TP2 Volume - TP1 Volume) / TP1 Volume × 100% |
lesionViewOnline/
├── App.js # Main application entry point
├── components/
│ ├── DataLoadModal.js # File upload and demo data loading
│ └── SessionStats.js # Statistics panel UI
├── utils/
│ ├── lesionAnalysis.js # Core lesion matching algorithm
│ ├── niftiUtils.js # NIfTI file parsing utilities
│ └── colorUtils.js # Color mapping for visualization
├── public/
│ └── demo/ # Demo NIfTI data files
└── package.json
| Package | Purpose |
|---|---|
expo |
React Native for Web framework |
nifti-reader-js |
Parse NIfTI files in browser |
pako |
Decompress gzipped NIfTI files |
npm install
npx expo start --web- Click Load Demo Data for sample brain data, or
- Click Load Custom Data to select your own NIfTI files:
- FLAIR TP1 (baseline)
- FLAIR TP2 (follow-up)
- Lesion Mask TP1
- Lesion Mask TP2
After loading data, the Session Stats panel shows:
- Lesion Count (TP1 → TP2)
- Lesion Volume (mm³)
- Number of New Lesions
- Volume Change (%)
Use the filter buttons (ALL, NEW, ENLARGING, STABLE) to highlight specific lesion types.
The lesion matching algorithm has been validated against the Python implementation in laminateReport. Both implementations produce identical:
- Unified Lesion Counts
- New Lesion Detection
- Volume Change Statistics
The Python implementation (laminateReport/src/lesion_analysis.py) is a direct port of this JavaScript code.
MIT