Commit
·
da08b3c
1
Parent(s):
8e0cd11
Refactor documentation for Docker and Gradio implementation. Removed outdated permission fix code and fallback matplotlib rendering section. Updated Gradio approach to use direct Base64 injection instead of FastAPI endpoints, enhancing simplicity and performance.
Browse files
docs/specs/03-phase-2-deepisles-docker.md
CHANGED
|
@@ -795,23 +795,6 @@ def build_docker_command(
|
|
| 795 |
return cmd
|
| 796 |
```
|
| 797 |
|
| 798 |
-
Alternative: Fix permissions after Docker completes (less clean but works):
|
| 799 |
-
|
| 800 |
-
```python
|
| 801 |
-
def fix_docker_output_permissions(output_dir: Path) -> None:
|
| 802 |
-
"""Fix permissions on Docker-created files."""
|
| 803 |
-
import subprocess
|
| 804 |
-
# Only needed if running as non-root and files are root-owned
|
| 805 |
-
try:
|
| 806 |
-
subprocess.run(
|
| 807 |
-
["sudo", "chown", "-R", f"{os.getuid()}:{os.getgid()}", str(output_dir)],
|
| 808 |
-
check=True,
|
| 809 |
-
capture_output=True,
|
| 810 |
-
)
|
| 811 |
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
| 812 |
-
pass # sudo not available or not needed
|
| 813 |
-
```
|
| 814 |
-
|
| 815 |
### critical: gpu availability check
|
| 816 |
|
| 817 |
**Reviewer feedback (valid)**: We check for Docker daemon but not NVIDIA Container Runtime. A user might have Docker but lack GPU passthrough setup.
|
|
|
|
| 795 |
return cmd
|
| 796 |
```
|
| 797 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 798 |
### critical: gpu availability check
|
| 799 |
|
| 800 |
**Reviewer feedback (valid)**: We check for Docker daemon but not NVIDIA Container Runtime. A user might have Docker but lack GPU passthrough setup.
|
docs/specs/05-phase-4-gradio-ui.md
CHANGED
|
@@ -91,17 +91,17 @@ Key patterns from Tobias's implementation:
|
|
| 91 |
nv.opts.show3Dcrosshair = true;
|
| 92 |
```
|
| 93 |
|
| 94 |
-
###
|
| 95 |
|
| 96 |
-
For our demo, we use
|
| 97 |
- **Gradio** for case selection dropdown and "Run Segmentation" button
|
| 98 |
-
- **
|
| 99 |
- **NiiVue via `gr.HTML`** for interactive 3D visualization
|
| 100 |
|
| 101 |
This gives us:
|
| 102 |
- Gradio's nice UI components for inputs
|
| 103 |
-
- Proven NiiVue rendering from Tobias's implementation
|
| 104 |
-
- No iframe complexity
|
| 105 |
|
| 106 |
### concrete implementation
|
| 107 |
|
|
@@ -169,44 +169,6 @@ def create_niivue_viewer_html(
|
|
| 169 |
"""
|
| 170 |
```
|
| 171 |
|
| 172 |
-
### fallback: matplotlib 2d slices
|
| 173 |
-
|
| 174 |
-
For environments where WebGL fails, provide matplotlib fallback:
|
| 175 |
-
|
| 176 |
-
```python
|
| 177 |
-
import matplotlib.pyplot as plt
|
| 178 |
-
import nibabel as nib
|
| 179 |
-
|
| 180 |
-
def render_slices_fallback(nifti_path: Path, mask_path: Path | None = None) -> Figure:
|
| 181 |
-
"""Render 3-panel slice view with optional mask overlay."""
|
| 182 |
-
img = nib.load(nifti_path)
|
| 183 |
-
data = img.get_fdata()
|
| 184 |
-
|
| 185 |
-
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
| 186 |
-
|
| 187 |
-
# Middle slices
|
| 188 |
-
ax_slice = data.shape[2] // 2
|
| 189 |
-
cor_slice = data.shape[1] // 2
|
| 190 |
-
sag_slice = data.shape[0] // 2
|
| 191 |
-
|
| 192 |
-
axes[0].imshow(data[:, :, ax_slice].T, cmap='gray', origin='lower')
|
| 193 |
-
axes[0].set_title('Axial')
|
| 194 |
-
axes[1].imshow(data[:, cor_slice, :].T, cmap='gray', origin='lower')
|
| 195 |
-
axes[1].set_title('Coronal')
|
| 196 |
-
axes[2].imshow(data[sag_slice, :, :].T, cmap='gray', origin='lower')
|
| 197 |
-
axes[2].set_title('Sagittal')
|
| 198 |
-
|
| 199 |
-
if mask_path:
|
| 200 |
-
mask = nib.load(mask_path).get_fdata()
|
| 201 |
-
# Overlay in red with alpha
|
| 202 |
-
for ax, sl in zip(axes, [mask[:,:,ax_slice].T, mask[:,cor_slice,:].T, mask[sag_slice,:,:].T]):
|
| 203 |
-
ax.imshow(sl, cmap='Reds', alpha=0.5, origin='lower')
|
| 204 |
-
|
| 205 |
-
return fig
|
| 206 |
-
```
|
| 207 |
-
|
| 208 |
-
**Recommendation**: Use NiiVue as primary (proven working), matplotlib as fallback.
|
| 209 |
-
|
| 210 |
## interfaces and types
|
| 211 |
|
| 212 |
### `ui/app.py`
|
|
@@ -369,7 +331,8 @@ def create_niivue_html(
|
|
| 369 |
template = f"""
|
| 370 |
<div id="gl" style="width:100%; height:{height}px;"></div>
|
| 371 |
<script type="module">
|
| 372 |
-
|
|
|
|
| 373 |
const nv = new Niivue({{ show3Dcrosshair: true }});
|
| 374 |
nv.attachToCanvas(document.getElementById('gl'));
|
| 375 |
const volumes = [{{ url: '{volume_url}' }}];
|
|
@@ -802,9 +765,7 @@ if __name__ == "__main__":
|
|
| 802 |
|
| 803 |
```toml
|
| 804 |
# Add to pyproject.toml dependencies
|
| 805 |
-
"matplotlib>=3.8.0",
|
| 806 |
-
"fastapi>=0.115.0", # For API endpoints if using hybrid approach
|
| 807 |
-
"uvicorn[standard]>=0.32.0", # For local development
|
| 808 |
```
|
| 809 |
|
| 810 |
## reference implementation
|
|
|
|
| 91 |
nv.opts.show3Dcrosshair = true;
|
| 92 |
```
|
| 93 |
|
| 94 |
+
### implementation approach: gradio + direct base64 injection
|
| 95 |
|
| 96 |
+
For our demo, we use:
|
| 97 |
- **Gradio** for case selection dropdown and "Run Segmentation" button
|
| 98 |
+
- **Direct Base64 data URLs** injected into HTML (no separate API endpoints)
|
| 99 |
- **NiiVue via `gr.HTML`** for interactive 3D visualization
|
| 100 |
|
| 101 |
This gives us:
|
| 102 |
- Gradio's nice UI components for inputs
|
| 103 |
+
- Proven NiiVue rendering pattern from Tobias's implementation
|
| 104 |
+
- No iframe complexity, no proxy issues in HF Spaces
|
| 105 |
|
| 106 |
### concrete implementation
|
| 107 |
|
|
|
|
| 169 |
"""
|
| 170 |
```
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
## interfaces and types
|
| 173 |
|
| 174 |
### `ui/app.py`
|
|
|
|
| 331 |
template = f"""
|
| 332 |
<div id="gl" style="width:100%; height:{height}px;"></div>
|
| 333 |
<script type="module">
|
| 334 |
+
const niivueModule = await import('https://unpkg.com/@niivue/niivue@0.57.0/dist/index.js');
|
| 335 |
+
const Niivue = niivueModule.Niivue;
|
| 336 |
const nv = new Niivue({{ show3Dcrosshair: true }});
|
| 337 |
nv.attachToCanvas(document.getElementById('gl'));
|
| 338 |
const volumes = [{{ url: '{volume_url}' }}];
|
|
|
|
| 765 |
|
| 766 |
```toml
|
| 767 |
# Add to pyproject.toml dependencies
|
| 768 |
+
"matplotlib>=3.8.0", # For static slice rendering in viewer.py
|
|
|
|
|
|
|
| 769 |
```
|
| 770 |
|
| 771 |
## reference implementation
|