stroke-viewer-frontend / docs /specs /19-perf-base64-to-file-urls.md
Claude
fix(ui): replace base64 data URLs with Gradio file serving (#19)
b0a934c unverified
|
raw
history blame
6.79 kB

Issue #19: Replace Base64 Data URLs with File URLs for NiiVue Viewer

Status: RESOLVED βœ…

Date: 2025-12-09 Resolved: 2025-12-09 Priority: P3 (Performance optimization) GitHub Issue: https://github.com/The-Obstacle-Is-The-Way/stroke-deepisles-demo/issues/19 Related: Bug #10, Bug #11 (both FIXED)


TL;DR

Replace base64-encoded data URLs (~65MB payloads) with Gradio's file serving for NiiVue volumes. The viewer works correctly now, but large payloads may cause slow loading or memory issues.


Problem

The NiiVue 3D viewer currently uses base64-encoded data URLs to pass NIfTI volumes to the browser:

# Current implementation in viewer.py
def nifti_to_data_url(nifti_path: Path) -> str:
    """Convert NIfTI file to base64 data URL."""
    data = nifti_path.read_bytes()
    b64 = base64.b64encode(data).decode("ascii")
    return f"data:application/octet-stream;base64,{b64}"

Payload Size Analysis

File Raw Size Base64 Size
DWI 30.1 MB ~40 MB
ADC 17.7 MB ~24 MB
Total ~48 MB ~65 MB

Potential Issues

  1. Browser memory pressure - Large base64 strings in DOM
  2. Slow loading times - 65MB transferred per segmentation
  3. Gradio payload limits - May hit internal limits on large responses
  4. Mobile/low-bandwidth issues - Poor UX on slower connections

Proposed Solution

Use Gradio's built-in file serving instead of base64 data URLs.

Option A: Use gr.File component (Recommended)

Gradio automatically serves files and provides URLs:

from gradio import FileData

def nifti_to_file_url(nifti_path: Path) -> str:
    """Get Gradio file URL for NIfTI file."""
    file_data = FileData(path=str(nifti_path))
    return file_data.url  # Returns /file=... URL served by Gradio

Option B: Use Gradio's file caching

import gradio as gr

# Gradio caches files and provides URLs
cached_path = gr.utils.get_upload_folder() / nifti_path.name
shutil.copy(nifti_path, cached_path)
file_url = f"/file={cached_path}"

Files to Modify

File Changes
src/stroke_deepisles_demo/ui/viewer.py Replace nifti_to_data_url() with file URL function
src/stroke_deepisles_demo/ui/app.py Update run_segmentation() to use file URLs

Implementation Steps

Step 1: Research Gradio File Serving

Verify how Gradio serves files and what URL format NiiVue expects:

# Test script
import gradio as gr
from gradio import FileData

file_data = FileData(path="/path/to/test.nii.gz")
print(f"URL: {file_data.url}")
print(f"Type: {type(file_data.url)}")

Step 2: Update nifti_to_data_url() β†’ nifti_to_file_url()

# viewer.py
def nifti_to_file_url(nifti_path: Path) -> str:
    """Get Gradio-served file URL for NIfTI file.

    Args:
        nifti_path: Path to NIfTI file

    Returns:
        URL string that Gradio will serve (e.g., /file=...)
    """
    from gradio import FileData
    file_data = FileData(path=str(nifti_path))
    return file_data.url

Step 3: Update app.py to Use File URLs

# app.py - run_segmentation()
# Replace:
dwi_url = nifti_to_data_url(dwi_path)
mask_url = nifti_to_data_url(result.prediction_mask)

# With:
dwi_url = nifti_to_file_url(dwi_path)
mask_url = nifti_to_file_url(result.prediction_mask)

Step 4: Test NiiVue with File URLs

Verify NiiVue can load from Gradio's file URLs:

  • Check CORS headers
  • Verify Content-Type header
  • Test with different browsers

Step 5: Cleanup

Remove or deprecate nifti_to_data_url() if no longer needed.


Testing Checklist

  • NiiVue loads DWI volume from file URL
  • NiiVue loads prediction mask overlay from file URL
  • No CORS errors in browser console (same-origin requests)
  • Loading time improved (no base64 encoding overhead)
  • Memory usage reduced (streaming vs. DOM strings)
  • Works on HF Spaces deployment (uses tempfile.gettempdir())
  • All existing tests pass (134 tests)

Implementation Details

Final Implementation (2025-12-09)

The solution uses Gradio's built-in file serving at /gradio_api/file=<path>:

viewer.py - New function:

def nifti_to_gradio_url(nifti_path: Path) -> str:
    """Get Gradio file URL for a NIfTI file."""
    abs_path = nifti_path.resolve()
    return f"/gradio_api/file={abs_path}"

app.py - Updated usage:

dwi_url = nifti_to_gradio_url(dwi_path)
mask_url = nifti_to_gradio_url(result.prediction_mask)

Why This Works

  1. Gradio allows temp files by default: Files in tempfile.gettempdir() are automatically accessible via the /gradio_api/file= endpoint.

  2. Pipeline results are in temp dir: run_pipeline_on_case() creates results in tempfile.mkdtemp(), which is under tempfile.gettempdir().

  3. NiiVue supports HTTP URLs: The loadVolumes() method can fetch from any HTTP/HTTPS URL, including relative URLs served by Gradio.

  4. Same-origin requests: Since NiiVue's JavaScript runs in the browser and requests files from the same Gradio server, there are no CORS issues.

Tests Added

class TestNiftiToGradioUrl:
    def test_returns_gradio_api_format(self, synthetic_nifti_3d: Path) -> None:
        url = nifti_to_gradio_url(synthetic_nifti_3d)
        assert url.startswith("/gradio_api/file=")

    def test_uses_absolute_path(self, synthetic_nifti_3d: Path) -> None:
        url = nifti_to_gradio_url(synthetic_nifti_3d)
        path_part = url.replace("/gradio_api/file=", "")
        assert path_part.startswith("/")

    def test_no_base64_encoding(self, synthetic_nifti_3d: Path) -> None:
        url = nifti_to_gradio_url(synthetic_nifti_3d)
        assert not url.startswith("data:")
        assert ";base64," not in url

Risks and Mitigations

Risk Mitigation
CORS issues Gradio should handle CORS for its own file serving
NiiVue URL format Test that NiiVue accepts relative URLs
File cleanup Gradio handles temp file cleanup automatically
Security Gradio's file serving is sandboxed to allowed paths

Acceptance Criteria

  1. NiiVue viewer loads volumes from file URLs (not base64)
  2. No regression in viewer functionality
  3. Measurable improvement in loading time or memory usage
  4. All 130+ tests pass
  5. Works on HF Spaces

References