feat(docs): add comprehensive specifications for NiiVue integration and JavaScript loading issues
5ac4ed0
| # spec: hugging face spaces + gradio deployment | |
| > **Version**: December 2025 | |
| > **Status**: APPROVED - Ready for Implementation | |
| > **Last Updated**: 2025-12-05 | |
| > **Verified**: Cold start claims, pause/restart behavior, ZeroGPU limitations | |
| ## important: gradio 6 is now available | |
| As of December 2025, **Gradio 6.0.2** is the latest stable release. Our `pyproject.toml` currently specifies `gradio>=5.0.0`, which will install Gradio 6.x. | |
| **Key breaking changes affecting our codebase:** | |
| | Change | Impact | Our Code | | |
| |--------|--------|----------| | |
| | `theme`, `css`, `js` moved from `Blocks()` to `launch()` | HIGH | `app.py:111` uses `gr.Blocks()`, `app.py:170` passes theme to `launch()` - **OK** | | |
| | `gr.HTML` padding default `True` → `False` | LOW | No visual impact expected | | |
| | Chatbot tuple format removed | NONE | We don't use Chatbot | | |
| | `show_api` → `footer_links` | LOW | We don't customize this | | |
| **Recommendation**: Pin to `gradio>=6.0.0,<7.0.0` for stability, or test with latest and update as needed. | |
| **Migration guide**: [Gradio 6 Migration Guide](https://www.gradio.app/main/guides/gradio-6-migration-guide) | |
| --- | |
| ## purpose | |
| This spec documents the requirements, constraints, and best practices for deploying the `stroke-deepisles-demo` Gradio application to Hugging Face Spaces. It identifies potential friction points between our current implementation and HF Spaces constraints, providing concrete guidance before deployment. | |
| ## executive summary | |
| ### critical friction points identified | |
| | Issue | Severity | Current State | Fix Required | | |
| |-------|----------|---------------|--------------| | |
| | **NVIDIA GPU required** | HIGH | DeepISLES needs CUDA | Use Docker SDK + GPU on HF Spaces | | |
| | **JavaScript in `gr.HTML`** | HIGH | `<script type="module">` in viewer.py | May not execute; needs `js=` param pattern | | |
| | **Git dependency in pyproject.toml** | MEDIUM | `datasets @ git+https://...` | Needs `requirements.txt` with git URL | | |
| | **Large NIfTI files as base64** | MEDIUM | Full file loaded to memory | Should be fine with GPU tier RAM | | |
| | **NiiVue version** | LOW | Currently 0.57.0 in viewer.py | Update to **0.65.0** (latest) | | |
| ### deployment strategy | |
| > **Important**: DeepISLES requires NVIDIA GPU with CUDA. There is no CPU-only or Apple Silicon option. "Demo mode" with pre-computed results was rejected as it defeats the purpose of a real inference demo. | |
| ### Primary: Local NVIDIA GPU | |
| - Develop and test locally with your NVIDIA GPU | |
| - Free, unlimited, real inference | |
| - Works on Windows/Linux with NVIDIA GPU (GTX 1080+, RTX series) | |
| ### Showcase: HF Spaces Docker SDK + GPU (On-Demand) | |
| - Use `sdk: docker` with GPU hardware | |
| - **Spin up** when demoing, **pause** when done | |
| - Cost: ~$0.20-$0.40 per 30-60 min demo session | |
| - Billing stops when paused ($0 while inactive) | |
| --- | |
| ## critical: cold start reality | |
| > ⚠️ **OPERATIONAL MANDATE**: Always run `api.restart_space()` **20-30 minutes** before a scheduled demo. Verify the Space is "Running" before sharing your screen. | |
| ### verified cold start times (december 2025) | |
| | Phase | Time | Source | | |
| |-------|------|--------| | |
| | HF Infrastructure boot | ~2 minutes | [HF Forums](https://discuss.huggingface.co/t/slow-space-cold-boot/72154) | | |
| | Docker image provision | 5-20 minutes | Large images (CUDA + nnU-Net ~15-20GB) | | |
| | Application startup | 1-5 minutes | Gradio + model loading | | |
| | **Total (best case)** | **8-12 minutes** | Normal conditions | | |
| | **Total (worst case)** | **30-60+ minutes** | Resource contention, Feb 2025 T4 issues | | |
| **Sources**: [T4 startup 45+ min issue (Feb 2025)](https://discuss.huggingface.co/t/staring-up-t4-instances-is-taking-45-minutes/139567), [Cold boot discussion](https://discuss.huggingface.co/t/slow-space-cold-boot/72154) | |
| ### why cold start is unavoidable | |
| From HF Staff (forum moderator): | |
| > "avoiding a cold start here is not possible" | |
| The ~2-minute infrastructure delay is inherent to HF Spaces architecture. Docker GPU Spaces add additional time for image provisioning and GPU allocation. | |
| ### deployment risks (edge cases) | |
| | Risk | Frequency | Mitigation | | |
| |------|-----------|------------| | |
| | Space stuck in "Starting" | Rare | Factory rebuild, contact HF support | | |
| | Space stuck in "Paused" | Rare | Wait + retry, contact HF support | | |
| | Build timeout (30-45 min limit) | Possible | Optimize Dockerfile, cache layers | | |
| | GPU unavailable (resource contention) | Rare | Try again later, different hardware tier | | |
| **Sources**: [Space stuck at Starting (Nov 2025)](https://discuss.huggingface.co/t/hf-space-stuck-at-starting/170911), [Space stuck in Paused (Oct 2025)](https://discuss.huggingface.co/t/space-stuck-in-paused/169467) | |
| ### pre-demo warm-up procedure | |
| ```bash | |
| # 20-30 minutes before your demo: | |
| # 1. Restart the Space | |
| python -c " | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| api.restart_space('YOUR_USERNAME/stroke-deepisles-demo') | |
| print('Space restart initiated...') | |
| " | |
| # 2. Monitor status (check every 2 min) | |
| python -c " | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| info = api.space_info('YOUR_USERNAME/stroke-deepisles-demo') | |
| print(f'Status: {info.runtime.stage}') # Should be 'RUNNING' | |
| " | |
| # 3. Only proceed when status = RUNNING | |
| ``` | |
| ### contingency plan if cold start fails | |
| 1. **Space stuck in "Starting" > 30 min**: | |
| - Try "Factory rebuild" from Space Settings | |
| - If still stuck, contact HF support via [Discord](https://discord.gg/hugging-face-879548962464493619) | |
| 2. **Demo starts before Space is ready**: | |
| - Show local demo on your NVIDIA GPU machine instead | |
| - "Let me show you on my development machine while the cloud version warms up" | |
| 3. **GPU unavailable error**: | |
| - Try `a10g-small` instead of `t4-small` (different GPU pool) | |
| - Wait 15 minutes and retry | |
| --- | |
| ## zerogpu: why it doesn't work for us | |
| ZeroGPU offers free, dynamic GPU allocation on H200 GPUs. However: | |
| | Requirement | ZeroGPU | Our Need | | |
| |-------------|---------|----------| | |
| | SDK Support | Gradio SDK only | Docker SDK (for DeepISLES container) | | |
| | Docker containers | ❌ NOT supported | ✅ Required | | |
| | Custom CUDA environment | ❌ NOT supported | ✅ Required (nnU-Net) | | |
| **Source**: [ZeroGPU Documentation](https://huggingface.co/docs/hub/en/spaces-zerogpu), [Community request for Docker support](https://huggingface.co/spaces/zero-gpu-explorers/README/discussions/27) | |
| **Verdict**: ZeroGPU is incompatible with DeepISLES. We must use Docker SDK + paid GPU hardware. | |
| --- | |
| ## hugging face spaces constraints | |
| ### sdk options | |
| | SDK | Use Case | Docker Access | GPU Support | | |
| |-----|----------|---------------|-------------| | |
| | `gradio` | Standard Gradio apps | **NO** | Via hardware upgrade | | |
| | `docker` | Custom containers | **YES** | Via hardware upgrade | | |
| | `static` | HTML/JS only | **NO** | N/A | | |
| **Key insight**: The Gradio SDK **cannot run Docker containers**. Our pipeline requires the DeepISLES Docker image, creating a fundamental incompatibility. | |
| ### hardware tiers | |
| | Tier | vCPU | RAM | Cost | GPU | | |
| |------|------|-----|------|-----| | |
| | cpu-basic (free) | 2 | 16GB | $0 | None | | |
| | cpu-upgrade | 8 | 32GB | $0.03/hr | None | | |
| | t4-small | 4 | 15GB | $0.40/hr | T4 (16GB) | | |
| | t4-medium | 8 | 30GB | $0.60/hr | T4 (16GB) | | |
| | a10g-small | 4 | 15GB | $1.05/hr | A10G (24GB) | | |
| | a10g-large | 12 | 46GB | $3.15/hr | A10G (24GB) | | |
| **Source**: [Hugging Face Spaces GPU Upgrades](https://huggingface.co/docs/hub/spaces) | |
| ### storage limits | |
| | Type | Limit | Behavior | | |
| |------|-------|----------| | |
| | Ephemeral (root fs) | 50GB | Lost on restart | | |
| | Persistent (`/data`) | 20GB-1TB | Paid tiers ($5-$100/mo) | | |
| | Build cache | Varies | Can cause "storage limit exceeded" | | |
| **Best practice**: Set `HF_HOME=/data/.huggingface` to cache models in persistent storage. | |
| > ⚠️ **Important**: `HF_HOME` must be set in the Space's **Settings → Repository secrets** UI, not just in code. Environment variables set only in Python code won't persist across container restarts. | |
| **Source**: [Spaces Persistent Storage](https://huggingface.co/docs/hub/en/spaces-storage) | |
| ### build limits | |
| | Limit | Value | Notes | | |
| |-------|-------|-------| | |
| | Build timeout | 30-45 minutes | Large dependencies may fail | | |
| | Build cache | Part of 50GB ephemeral | Can cause "storage limit exceeded" | | |
| | Startup timeout | 30 minutes (default) | Configurable via `startup_duration_timeout` | | |
| | Idle sleep | 48 hours | Free Spaces sleep after inactivity | | |
| **Warning**: Heavy scientific stacks (PyTorch, large C extensions) may hit build timeout. Monitor build logs closely. | |
| --- | |
| ## gradio 6 constraints (december 2025) | |
| > **Note**: Gradio 6.0 was released in late November 2025. Our codebase was written for Gradio 5.x but is largely compatible. | |
| ### key breaking changes from gradio 5 → 6 | |
| | Change | Gradio 5.x | Gradio 6.x | Our Status | | |
| |--------|------------|------------|------------| | |
| | Theme/CSS/JS placement | `gr.Blocks(theme=..., css=..., js=...)` | `demo.launch(theme=..., css=..., js=...)` | ✅ Already correct in `app.py:170` | | |
| | HTML padding default | `padding=True` | `padding=False` | ⚠️ Minor visual change | | |
| | Chatbot message format | Tuple `[["user", "bot"]]` | Dict `{"role": ..., "content": ...}` | N/A - Not used | | |
| | `show_api` parameter | `show_api=True/False` | `footer_links=["api", "gradio", "settings"]` | N/A - Not customized | | |
| | Event `api_name=False` | `api_name=False` | `api_visibility="private"` | N/A - Not used | | |
| ### new in gradio 6 | |
| 1. **Custom Web Components**: Write custom components in pure HTML/JS inline in Python via `gradio cc` | |
| 2. **Vibe Mode**: `gradio --vibe app.py` for AI-assisted app editing | |
| 3. **Performance**: Significantly lighter and faster | |
| 4. **Security**: Trail of Bits audit improvements carried forward | |
| 5. **Server-Side Rendering (SSR)**: Faster initial loads, better SEO | |
| > ⚠️ **SSR Consideration**: With SSR enabled, JavaScript that references `window` or `document` may fail during server-side render. Ensure NiiVue initialization checks `typeof window !== 'undefined'` before accessing browser APIs. | |
| ### javascript execution in `gr.HTML` | |
| **CRITICAL ISSUE**: The `gr.HTML` component does **not** execute JavaScript in `<script>` tags in the standard way. | |
| #### current implementation (viewer.py:262-324) | |
| ```python | |
| def create_niivue_html(...) -> str: | |
| return f""" | |
| <div style="width:100%; height:{height}px; ..."> | |
| <canvas id="niivue-canvas" style="width:100%; height:100%;"></canvas> | |
| </div> | |
| <script type="module"> | |
| const niivueModule = await import('https://unpkg.com/@niivue/niivue@0.65.0/dist/index.js'); | |
| // ... NiiVue initialization | |
| </script> | |
| """ | |
| ``` | |
| #### the problem | |
| From the [Gradio documentation](https://www.gradio.app/guides/custom-CSS-and-JS) and [HF Forums](https://discuss.huggingface.co/t/gradio-html-component-with-javascript-code-dont-work/37316): | |
| > "The `gr.HTML` component doesn't support loading scripts via traditional `<script>` tags. This prevents JavaScript functions from being accessible to inline event handlers." | |
| #### recommended fix | |
| Use `gr.Blocks(js=...)` or `demo.load(_js=...)` to inject JavaScript: | |
| ```python | |
| NIIVUE_INIT_JS = """ | |
| async () => { | |
| // Wait for NiiVue module to load | |
| const niivueModule = await import('https://unpkg.com/@niivue/niivue@0.65.0/dist/index.js'); | |
| globalThis.Niivue = niivueModule.Niivue; | |
| } | |
| """ | |
| def create_app() -> gr.Blocks: | |
| with gr.Blocks(js=NIIVUE_INIT_JS) as demo: | |
| # ... components | |
| return demo | |
| ``` | |
| Then in the HTML component, reference the global: | |
| ```python | |
| def create_niivue_html(volume_url: str, ...) -> str: | |
| return f""" | |
| <div id="niivue-container-{uuid}" style="..."> | |
| <canvas id="niivue-canvas-{uuid}"></canvas> | |
| </div> | |
| <script> | |
| (async function() {{ | |
| if (typeof globalThis.Niivue === 'undefined') {{ | |
| console.error('NiiVue not loaded'); | |
| return; | |
| }} | |
| const nv = new globalThis.Niivue({{...}}); | |
| await nv.attachTo('niivue-canvas-{uuid}'); | |
| // ... | |
| }})(); | |
| </script> | |
| """ | |
| ``` | |
| **Note**: Even this may not work reliably. Testing on HF Spaces is required. | |
| #### alternative: gradio custom components (`gradio cc`) | |
| For production deployments, Gradio 6 supports first-class **Custom Components** via the `gradio cc` CLI. This is the recommended "production" solution (vs. the `js=` hack for MVP). | |
| ```bash | |
| # Create a NiiVue custom component | |
| gradio cc create NiiVueViewer --template HTML | |
| # Development server with hot reload | |
| gradio cc dev | |
| # Build for distribution | |
| gradio cc build | |
| # Publish to PyPI and HF Spaces | |
| gradio cc publish | |
| ``` | |
| **Pros**: | |
| - First-class support, proper state management | |
| - No hacky string interpolation | |
| - Reusable across projects | |
| **Cons**: | |
| - Requires Node.js build step | |
| - Higher complexity than `js=` parameter | |
| - Overkill for MVP | |
| **Source**: [Custom Components In Five Minutes](https://www.gradio.app/guides/custom-components-in-five-minutes) | |
| #### alternative: `gradio-iframe` component | |
| The [`gradio-iframe`](https://pypi.org/project/gradio-iframe/) package (v0.0.10) provides an iframe component that may execute JavaScript more reliably: | |
| ```python | |
| from gradio_iframe import iFrame | |
| viewer = iFrame( | |
| value="<html>...NiiVue code...</html>", | |
| label="NiiVue Viewer" | |
| ) | |
| ``` | |
| **Warning**: This is experimental and "not fully tested" per the maintainer. Use with caution. | |
| ### css restrictions | |
| Custom CSS should use `elem_id` and `elem_classes` rather than query selectors: | |
| > "The use of query selectors in custom JS and CSS is not guaranteed to work across Gradio versions as the Gradio HTML DOM may change." | |
| **Source**: [Custom CSS and JS Guide](https://www.gradio.app/guides/custom-CSS-and-JS) | |
| ### security (gradio 5 audit, inherited by v6) | |
| The Trail of Bits security audit was performed on **Gradio 5.0**. All fixes are inherited by Gradio 6.x: | |
| - **CVE-2024-47872**: XSS via HTML/JS/SVG file uploads (fixed in 5.0.0) | |
| - File type restrictions enforced server-side | |
| - Our app uses `gradio>=6.0.0` - we're covered | |
| > **Note**: There was no separate Gradio 6 audit. The security improvements from Gradio 5 persist in v6. | |
| **Source**: [A Security Review of Gradio 5](https://huggingface.co/blog/gradio-5-security) | |
| --- | |
| ## readme.md yaml configuration | |
| ### required fields for gradio spaces | |
| ```yaml | |
| --- | |
| title: Stroke DeepISLES Demo | |
| emoji: 🧠 | |
| colorFrom: blue | |
| colorTo: purple | |
| sdk: gradio | |
| sdk_version: "6.0.2" # Latest stable as of Dec 2025 | |
| python_version: "3.11" | |
| app_file: app.py | |
| pinned: false | |
| license: mit | |
| short_description: "Ischemic stroke lesion segmentation using DeepISLES" | |
| # Optional but recommended | |
| models: | |
| - isleschallenge/deepisles # If we reference it | |
| datasets: | |
| - YongchengYAO/ISLES24-MR-Lite | |
| tags: | |
| - medical-imaging | |
| - stroke | |
| - segmentation | |
| - neuroimaging | |
| - niivue | |
| # For CPU-only demo mode | |
| suggested_hardware: cpu-basic | |
| # If we need cross-origin isolation (e.g., SharedArrayBuffer) | |
| # custom_headers: | |
| # cross-origin-embedder-policy: require-corp | |
| # cross-origin-opener-policy: same-origin | |
| --- | |
| ``` | |
| ### configuration reference | |
| | Field | Type | Description | | |
| |-------|------|-------------| | |
| | `sdk` | string | `gradio`, `docker`, or `static` | | |
| | `sdk_version` | string | Gradio version (e.g., "5.0.0") | | |
| | `python_version` | string | Python version (e.g., "3.11") | | |
| | `app_file` | string | Entry point (default: `app.py`) | | |
| | `suggested_hardware` | string | Hardware for duplicators | | |
| | `disable_embedding` | bool | Prevent iframe embedding | | |
| | `custom_headers` | dict | COEP/COOP/CORP headers | | |
| **Source**: [Spaces Configuration Reference](https://huggingface.co/docs/hub/en/spaces-config-reference) | |
| --- | |
| ## dependencies | |
| ### requirements.txt for hf spaces | |
| HF Spaces uses `requirements.txt`, not `pyproject.toml` for dependency installation. | |
| ```text | |
| # requirements.txt for HF Spaces | |
| # Core - Tobias's fork with BIDS + NIfTI lazy loading | |
| git+https://github.com/CloseChoice/datasets.git@feat/bids-loader-streaming-upload-fix | |
| # HuggingFace | |
| huggingface-hub>=0.25.0 | |
| # NIfTI handling | |
| nibabel>=5.2.0 | |
| numpy>=1.26.0 | |
| # Configuration | |
| pydantic>=2.5.0 | |
| pydantic-settings>=2.1.0 | |
| # UI - Gradio 6.x (latest stable as of Dec 2025) | |
| gradio>=6.0.0,<7.0.0 | |
| matplotlib>=3.8.0 | |
| # Networking | |
| requests>=2.0.0 | |
| ``` | |
| ### potential issues | |
| 1. **Git dependencies**: HF Spaces supports `git+https://...` in requirements.txt | |
| 2. **C extensions**: nibabel/numpy compile fine on HF Spaces | |
| 3. **Size**: No bloated dependencies (no PyTorch required for demo mode) | |
| --- | |
| ## deployment paths | |
| ### hardware requirements | |
| | Component | Requirement | Notes | | |
| |-----------|-------------|-------| | |
| | GPU | NVIDIA with CUDA 11.3+ | **Mandatory** - no CPU/MPS fallback | | |
| | VRAM | 4GB minimum, 12GB+ recommended | For parallel processing | | |
| | Docker | Docker + nvidia-container-toolkit | Required for DeepISLES | | |
| | Python | 3.8+ (3.11 recommended) | Per project config | | |
| > ⚠️ **Apple Silicon (M1/M2/M3) is NOT supported.** DeepISLES requires NVIDIA CUDA. | |
| ### path 1: local nvidia gpu (primary development) | |
| For day-to-day development and testing on your own NVIDIA GPU machine. | |
| ```bash | |
| # 1. Ensure Docker + nvidia-container-toolkit installed | |
| docker run --rm --gpus all nvidia/cuda:11.3-base nvidia-smi | |
| # 2. Pull DeepISLES image | |
| docker pull isleschallenge/deepisles | |
| # 3. Run the app | |
| uv run python -m stroke_deepisles_demo.ui.app | |
| ``` | |
| **Pros**: | |
| - Free (you own the hardware) | |
| - Fast iteration | |
| - No network dependency | |
| **Cons**: | |
| - Requires NVIDIA GPU hardware | |
| ### path 2: hf spaces docker sdk + gpu (on-demand demos) | |
| For showcasing to others. Spin up when needed, pause when done. | |
| #### dockerfile for hf spaces | |
| ```dockerfile | |
| # Dockerfile for HF Spaces | |
| # CRITICAL: DeepISLES code lives at /app/src/ in the base image. | |
| # We install our demo at /home/user/demo to avoid overwriting DeepISLES. | |
| FROM isleschallenge/deepisles:latest | |
| # HF Spaces runs containers with user ID 1000 | |
| RUN useradd -m -u 1000 user 2>/dev/null || true | |
| # IMPORTANT: Use /home/user/demo for our app, NOT /app | |
| WORKDIR /home/user/demo | |
| # Add our application | |
| COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt | |
| RUN pip install --no-cache-dir -r requirements.txt | |
| COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml | |
| COPY --chown=1000:1000 src/ /home/user/demo/src/ | |
| COPY --chown=1000:1000 app.py /home/user/demo/app.py | |
| RUN pip install --no-cache-dir --no-deps -e . | |
| # Environment variables for HF Spaces + direct invocation | |
| ENV HF_SPACES=1 | |
| ENV DEEPISLES_DIRECT_INVOCATION=1 | |
| ENV DEEPISLES_PATH=/app | |
| USER user | |
| EXPOSE 7860 | |
| ENTRYPOINT [] | |
| CMD ["python", "-m", "stroke_deepisles_demo.ui.app"] | |
| ``` | |
| #### readme.md configuration | |
| ```yaml | |
| --- | |
| title: Stroke DeepISLES Demo | |
| emoji: 🧠 | |
| colorFrom: blue | |
| colorTo: purple | |
| sdk: docker | |
| app_port: 7860 | |
| suggested_hardware: t4-small | |
| pinned: false | |
| license: mit | |
| --- | |
| ``` | |
| #### cost management: pause/restart api | |
| ```python | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| SPACE_ID = "your-username/stroke-deepisles-demo" | |
| # PAUSE - stops billing immediately | |
| api.pause_space(SPACE_ID) | |
| # RESTART - spin up for demo | |
| api.restart_space(SPACE_ID) | |
| # AUTO-SLEEP after 30 min inactivity | |
| api.set_space_sleep_time(SPACE_ID, sleep_time=1800) | |
| ``` | |
| #### billing breakdown | |
| | State | Billed? | How to Enter | | |
| |-------|---------|--------------| | |
| | Running | ✅ $0.40/hr (T4) | `restart_space()` or visitor wakes it | | |
| | Sleeping | ❌ $0 | Auto after `sleep_time` inactivity | | |
| | Paused | ❌ $0 | `pause_space()` - only owner can restart | | |
| **Typical demo session**: 30-60 minutes = **$0.20-$0.40** | |
| **Monthly cost if paused**: **$0.00** | |
| --- | |
| ## niivue integration analysis | |
| ### current implementation | |
| Our viewer uses NiiVue loaded from unpkg CDN with base64 data URLs: | |
| ```python | |
| # viewer.py:289-324 | |
| return f""" | |
| <div style="width:100%; height:{height}px; ..."> | |
| <canvas id="niivue-canvas" style="width:100%; height:100%;"></canvas> | |
| </div> | |
| <script type="module"> | |
| const niivueModule = await import('https://unpkg.com/@niivue/niivue@0.65.0/dist/index.js'); | |
| const Niivue = niivueModule.Niivue; | |
| // ... | |
| await nv.loadVolumes(volumes); | |
| </script> | |
| """ | |
| ``` | |
| ### potential issues | |
| 1. **Script execution**: `<script type="module">` may not execute in `gr.HTML` | |
| 2. **Canvas element IDs**: Hardcoded `id="niivue-canvas"` will conflict if multiple viewers | |
| 3. **CSP headers**: External CDN might be blocked by Content Security Policy | |
| 4. **Memory**: Base64 NIfTI files loaded entirely into browser memory | |
| ### recommended fixes | |
| ```python | |
| import uuid | |
| def create_niivue_html(volume_url: str, mask_url: str | None = None, *, height: int = 400) -> str: | |
| """Create HTML/JS for NiiVue viewer with unique IDs.""" | |
| canvas_id = f"niivue-canvas-{uuid.uuid4().hex[:8]}" | |
| # ... rest of implementation with unique canvas_id | |
| ``` | |
| ### webgl compatibility | |
| NiiVue requires WebGL2. Most modern browsers support it, but: | |
| - HF Spaces renders in iframes | |
| - Some iframe security policies restrict WebGL | |
| - Cross-origin isolation may be needed for SharedArrayBuffer | |
| **Test required**: Verify NiiVue WebGL works in HF Spaces iframe environment. | |
| --- | |
| ## memory and performance | |
| ### memory considerations | |
| | Resource | Size | Concern | | |
| |----------|------|---------| | |
| | DWI NIfTI (ISLES24-MR-Lite) | ~2-5 MB | Low | | |
| | Base64 encoded | ~3-7 MB | ~1.33x overhead | | |
| | Multiple volumes in browser | ~15-20 MB | Moderate | | |
| | Matplotlib figures | ~1-5 MB | Low | | |
| | Free tier RAM | 16 GB | Sufficient | | |
| ### optimization strategies | |
| 1. **Lazy loading**: Don't load all cases at startup | |
| 2. **Cleanup**: Clear matplotlib figures after rendering | |
| 3. **Pagination**: Limit case dropdown to reasonable number | |
| 4. **Compression**: NIfTI files are already gzipped | |
| --- | |
| ## testing checklist | |
| Before deploying to HF Spaces, verify: | |
| ### local testing | |
| - [ ] `uv run python app.py` launches without errors | |
| - [ ] Case dropdown populates | |
| - [ ] NiiVue viewer renders (in browser, not headless) | |
| - [ ] Matplotlib plots display correctly | |
| - [ ] No import-time side effects (network calls) | |
| ### hf spaces testing | |
| - [ ] Create private Space first | |
| - [ ] Verify dependencies install | |
| - [ ] Check JavaScript execution in `gr.HTML` | |
| - [ ] Test NiiVue WebGL rendering | |
| - [ ] Monitor memory usage | |
| - [ ] Test on mobile browsers (if applicable) | |
| ### known issues to monitor | |
| 1. **Startup timeout**: Default is 30 minutes, may need adjustment | |
| 2. **Sleep behavior**: Free Spaces sleep after 48h of inactivity | |
| 3. **Build cache**: May cause "storage limit exceeded" | |
| --- | |
| ## deployment procedure | |
| ### step 1: verify local nvidia gpu setup | |
| ```bash | |
| # Verify NVIDIA driver and Docker GPU support | |
| docker run --rm --gpus all nvidia/cuda:11.3-base nvidia-smi | |
| # Pull DeepISLES image | |
| docker pull isleschallenge/deepisles | |
| # Test local inference | |
| uv run stroke-demo run --case sub-stroke0001 | |
| ``` | |
| ### step 2: create dockerfile for hf spaces | |
| ```dockerfile | |
| # Dockerfile | |
| # CRITICAL: DeepISLES code lives at /app/src/ in the base image. | |
| # We install our demo at /home/user/demo to avoid overwriting DeepISLES. | |
| FROM isleschallenge/deepisles:latest | |
| # HF Spaces runs containers with user ID 1000 | |
| RUN useradd -m -u 1000 user 2>/dev/null || true | |
| # IMPORTANT: Use /home/user/demo for our app, NOT /app | |
| WORKDIR /home/user/demo | |
| # Install additional dependencies | |
| COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt | |
| RUN pip install --no-cache-dir -r requirements.txt | |
| # Copy application code | |
| COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml | |
| COPY --chown=1000:1000 src/ /home/user/demo/src/ | |
| COPY --chown=1000:1000 app.py /home/user/demo/app.py | |
| RUN pip install --no-cache-dir --no-deps -e . | |
| # Environment variables for HF Spaces + direct invocation | |
| ENV HF_SPACES=1 | |
| ENV DEEPISLES_DIRECT_INVOCATION=1 | |
| ENV DEEPISLES_PATH=/app | |
| USER user | |
| EXPOSE 7860 | |
| ENTRYPOINT [] | |
| CMD ["python", "-m", "stroke_deepisles_demo.ui.app"] | |
| ``` | |
| ### step 3: create requirements.txt | |
| ```bash | |
| cat > requirements.txt << 'EOF' | |
| git+https://github.com/CloseChoice/datasets.git@feat/bids-loader-streaming-upload-fix | |
| huggingface-hub>=0.25.0 | |
| nibabel>=5.2.0 | |
| numpy>=1.26.0 | |
| pydantic>=2.5.0 | |
| pydantic-settings>=2.1.0 | |
| gradio>=6.0.0,<7.0.0 | |
| matplotlib>=3.8.0 | |
| requests>=2.0.0 | |
| EOF | |
| ``` | |
| ### step 4: update readme.md for docker sdk | |
| ```yaml | |
| --- | |
| title: Stroke DeepISLES Demo | |
| emoji: 🧠 | |
| colorFrom: blue | |
| colorTo: purple | |
| sdk: docker | |
| app_port: 7860 | |
| suggested_hardware: t4-small | |
| pinned: false | |
| license: mit | |
| --- | |
| ``` | |
| ### step 5: deploy to private space | |
| ```bash | |
| # Create Docker Space with GPU | |
| huggingface-cli repo create stroke-deepisles-demo --type space --sdk docker | |
| # Push code | |
| git remote add space https://huggingface.co/spaces/YOUR_USERNAME/stroke-deepisles-demo | |
| git push space main | |
| ``` | |
| ### step 6: configure cost management | |
| ```python | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| SPACE_ID = "YOUR_USERNAME/stroke-deepisles-demo" | |
| # Set auto-sleep after 30 min of inactivity | |
| api.set_space_sleep_time(SPACE_ID, sleep_time=1800) | |
| # After demo: pause to stop all billing | |
| api.pause_space(SPACE_ID) | |
| # Before next demo: restart | |
| api.restart_space(SPACE_ID) | |
| ``` | |
| ### step 7: monitor and iterate | |
| - Check build logs (Docker builds can take 10-20 min) | |
| - Test inference end-to-end | |
| - Verify NiiVue visualization works | |
| - Pause Space when done testing | |
| --- | |
| ## decision matrix | |
| | Approach | Real Inference | Cost | Complexity | Use Case | | |
| |----------|----------------|------|------------|----------| | |
| | Local NVIDIA GPU | ✅ | $0 | Low | **Primary development** | | |
| | HF Spaces Docker + GPU (on-demand) | ✅ | ~$0.40/demo | Medium | **Showcasing to others** | | |
| | ~~Demo Mode (pre-computed)~~ | ❌ Fake | $0 | Low | ~~Rejected - defeats purpose~~ | | |
| | ~~HF Spaces Gradio SDK (free)~~ | ❌ No Docker | $0 | Low | ~~Cannot run DeepISLES~~ | | |
| | ~~ZeroGPU (free H200)~~ | ❌ No Docker | $0 | Low | ~~Only supports Gradio SDK~~ | | |
| --- | |
| ## sources | |
| ### official documentation | |
| - [Gradio Spaces](https://huggingface.co/docs/hub/en/spaces-sdks-gradio) | |
| - [Gradio 6 Migration Guide](https://www.gradio.app/main/guides/gradio-6-migration-guide) | |
| - [Custom CSS and JS](https://www.gradio.app/guides/custom-CSS-and-JS) | |
| - [Custom Components In Five Minutes](https://www.gradio.app/guides/custom-components-in-five-minutes) | |
| - [Spaces Configuration Reference](https://huggingface.co/docs/hub/en/spaces-config-reference) | |
| - [Spaces Persistent Storage](https://huggingface.co/docs/hub/en/spaces-storage) | |
| - [Manage Spaces - HF Hub](https://huggingface.co/docs/huggingface_hub/main/en/guides/manage-spaces) | |
| - [A Security Review of Gradio 5](https://huggingface.co/blog/gradio-5-security) | |
| - [Trail of Bits Gradio Audit](https://blog.trailofbits.com/2024/10/10/auditing-gradio-5-hugging-faces-ml-gui-framework/) | |
| - [Docker Spaces](https://huggingface.co/docs/hub/spaces-sdks-docker) | |
| - [ZeroGPU Documentation](https://huggingface.co/docs/hub/en/spaces-zerogpu) | |
| ### forum discussions (cold start verification) | |
| - [Slow Space Cold Boot](https://discuss.huggingface.co/t/slow-space-cold-boot/72154) - 2 min baseline confirmed | |
| - [T4 startup taking 45+ minutes](https://discuss.huggingface.co/t/staring-up-t4-instances-is-taking-45-minutes/139567) - Feb 2025 resource issues | |
| - [Space stuck at Starting](https://discuss.huggingface.co/t/hf-space-stuck-at-starting/170911) - Nov 2025 edge case | |
| - [Space stuck in Paused](https://discuss.huggingface.co/t/space-stuck-in-paused/169467) - Oct 2025 edge case | |
| - [ZeroGPU Docker request](https://huggingface.co/spaces/zero-gpu-explorers/README/discussions/27) - Community asking for Docker support | |
| - [Gradio HTML component with javascript code don't work](https://discuss.huggingface.co/t/gradio-html-component-with-javascript-code-dont-work/37316) | |
| ### packages | |
| - [NiiVue npm package](https://www.npmjs.com/package/@niivue/niivue) - v0.65.0 (latest as of Dec 2025) | |
| - [gradio-iframe PyPI](https://pypi.org/project/gradio-iframe/) - v0.0.10 (experimental) | |
| - [DeepISLES Docker Hub](https://hub.docker.com/r/isleschallenge/deepisles) | |
| --- | |
| ## appendix: friction points summary | |
| ### high priority (must fix before deployment) | |
| 1. **JavaScript execution in `gr.HTML`** | |
| - Current: `` embedded in HTML string | |
| - Risk: May not execute at all | |
| - Fix: Use `gr.Blocks(js=...)` or `demo.load(_js=...)` | |
| - Testing: Required on actual HF Spaces environment | |
| 2. **Docker + GPU requirement** | |
| - Current: Pipeline requires `isleschallenge/deepisles` container with NVIDIA GPU | |
| - Risk: Gradio SDK cannot run Docker; Apple Silicon not supported | |
| - Fix: Use Docker SDK with GPU hardware (on-demand billing) | |
| ### medium priority (should fix) | |
| 3. **Unique canvas IDs** | |
| - Current: Hardcoded `id="niivue-canvas"` | |
| - Risk: Multiple viewers would conflict | |
| - Fix: Generate unique IDs with UUID | |
| 4. **Git dependency in requirements** | |
| - Current: `datasets @ git+https://...` in pyproject.toml | |
| - Risk: HF Spaces uses requirements.txt | |
| - Fix: Create requirements.txt with git URL | |
| ### low priority (nice to have) | |
| 5. **Memory optimization** | |
| - Current: Full NIfTI files in base64 | |
| - Risk: Could hit memory limits on complex cases | |
| - Fix: Implement streaming or pagination | |
| 6. **CDN reliability** | |
| - Current: NiiVue from unpkg.com | |
| - Risk: CDN downtime affects app | |
| - Fix: Consider bundling or alternative CDN | |
| --- | |
| ## appendix: operational runbook | |
| ### daily operations | |
| **After development session:** | |
| ```bash | |
| # Always pause to stop billing | |
| python -c " | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| api.pause_space('YOUR_USERNAME/stroke-deepisles-demo') | |
| print('Space paused - billing stopped') | |
| " | |
| ``` | |
| **Before scheduled demo:** | |
| ```bash | |
| # T-30 minutes: Start warm-up | |
| python -c " | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| api.restart_space('YOUR_USERNAME/stroke-deepisles-demo') | |
| print('Warming up... check status in 5 min') | |
| " | |
| # T-25, T-20, T-15, T-10, T-5 minutes: Check status | |
| python -c " | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| info = api.space_info('YOUR_USERNAME/stroke-deepisles-demo') | |
| print(f'Status: {info.runtime.stage}') | |
| # BUILDING -> Wait | |
| # RUNNING_BUILDING -> Almost ready | |
| # RUNNING -> Ready to demo! | |
| " | |
| ``` | |
| **After demo:** | |
| ```bash | |
| # Immediately pause to stop billing | |
| python -c " | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| api.pause_space('YOUR_USERNAME/stroke-deepisles-demo') | |
| print('Demo complete - billing stopped') | |
| " | |
| ``` | |
| ### troubleshooting | |
| | Symptom | Diagnosis | Resolution | | |
| |---------|-----------|------------| | |
| | Status stuck on "BUILDING" > 45 min | Build timeout | Check build logs, optimize Dockerfile | | |
| | Status stuck on "STARTING" > 30 min | Resource issue | Factory rebuild, or try different hardware | | |
| | Status stuck on "PAUSED" after restart | API issue | Wait 5 min, retry, or use UI | | |
| | "Scheduling failure" error | GPU unavailable | Try later or different hardware tier | | |
| | "Storage limit exceeded" | Build cache full | Clear cache, reduce image layers | | |
| ### cost tracking | |
| ```bash | |
| # Check current month's usage | |
| # Visit: https://huggingface.co/settings/billing | |
| # Estimate cost per demo: | |
| # T4-small: $0.40/hr × 0.5 hr = $0.20 per 30-min demo | |
| # T4-medium: $0.60/hr × 0.5 hr = $0.30 per 30-min demo | |
| # A10G-small: $1.05/hr × 0.5 hr = $0.53 per 30-min demo | |
| ``` | |
| --- | |
| ## next steps | |
| > **Status**: Spec APPROVED - Ready for implementation | |
| 1. ~~Senior Review: Get approval on this spec~~ ✅ **APPROVED** | |
| 2. **Local Testing**: Verify full pipeline on local NVIDIA GPU machine | |
| 3. **Fix JavaScript Pattern**: Refactor NiiVue initialization for `gr.HTML` | |
| 4. **Create Dockerfile**: Build HF Spaces Docker image based on DeepISLES | |
| 5. **Create requirements.txt**: Generate from pyproject.toml | |
| 6. **Deploy to Private Space**: Test Docker SDK + GPU on HF Spaces | |
| 7. **Configure Auto-Sleep**: Set `sleep_time=1800` (30 min) to minimize costs | |
| 8. **Pre-Demo Test**: Practice warm-up procedure (20-30 min cold start) | |
| 9. **Demo & Pause**: Show to stakeholders, then `pause_space()` to stop billing | |
| 10. **Public Release**: Make Space public when stable (keep paused when not demoing) | |