Spaces:
Runtime error
Runtime error
Commit History
-
Split viewer fetch timing into TTFB, transfer, and throughput
The console reported "download 26.0s, decode+upload 0.4s" -- the opposite shape to what I predicted from the server side, where a curl of the same route ran at 14.3 MB/s. A single "download" number cannot distinguish a server that is slow to start responding from a link that is slow to carry the bytes, and those need opposite fixes, so it now reports time-to-first-byte and body transfer separately, plus the achieved MB/s and the content-encoding the browser saw. content-encoding is included deliberately: a proxy applying gzip to an already gzipped .nii.gz would burn container CPU re-compressing incompressible data, which would show up as TTFB rather than transfer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>973c650 -
Centre the viewer status text so it clears the pane labels
#status sat at top:8px/left:12px, directly under the "Coronal" pane label at top:10px/left:10px, so "Downloading volume..." overlapped it. The four pane labels own the corners, so the status moves to the centre of the canvas as a pill with a backdrop, which is also where a loading indicator reads best. Two follow-ons: #status:empty is transparent so no empty pill floats over the image when idle, and the download/decode timing shows for 3.5s then fades rather than sitting permanently on top of the volume. setStatus() clears the fade class so a re-render is visible again. Also moved the viewer_copy import inside the DOWNCAST_VIEWER branch. It ran unconditionally, so NV_GENERATE_DOWNCAST_VIEWER=0 still required nibabel to be importable -- which defeats the point of an escape hatch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>3855d49 -
Serve int16 to the viewer; time the browser side
Measured why the image appears ~20s after the server is done. Pulling a real generated volume off the Space through the file route: download is 1.5s at 14.3 MB/s, so the network was never the problem. The file is 384^3 float32, which means the browser gunzips 21.7 MB into a 226 MB array in JavaScript and uploads that as a 3D WebGL texture. That is the wait. The viewer now streams an int16 companion. On a real 384^3 CT: 226 MB -> 113 MB in memory, 21.7 -> 18.0 MB on the wire, 0.90s to produce, max absolute error 0.0153 HU (0.0008% of range) with shape, affine and value range preserved. CT is Hounsfield units and natively integral; nibabel writes scl_slope/scl_inter for anything needing rescale, which niivue honours. The float32 original is untouched and is still what the Download button serves -- downcasting the artefact users take away would be wrong. Masks are label maps, already integral, so they pass through. NV_GENERATE_DOWNCAST_VIEWER=0 opts out, and any failure falls back to the…3452775
-
Stream volumes by URL, cache checkpoints, reclaim archive space, quiet logs
Stream the viewer payload instead of inlining it. render_viewer() now hands niivue a /gradio_api/file= link resolved against document.baseURI, rather than base64-encoding the volume and mask into the page. A 46 MB CT was becoming ~61 MB of HTML built and shipped synchronously; measured on a test volume the payload drops 58x. app.launch() gains allowed_paths for the output dir. NV_GENERATE_VIEWER_MODE=base64 restores the old path if a deployment blocks file serving. Cache checkpoints across runs. scripts/inference.py issues five torch.load() calls per CT generation -- ~3.35 GB re-read and re-deserialised every time. _cached_torch_load() memoises them by (path, size, mtime) with LRU eviction under a byte budget (NV_GENERATE_CKPT_CACHE_GIB, default 8). This is host RAM, not VRAM: CT's weights are ~3.35 GB against a 96 GB xlarge slice, so VRAM was never the limit -- the 48-80 GB figure is activations, not parameters. Reclaim the mask archive. Once extracted, the 11.6 GB .zip is never rea…f265752 -
Move CPU work off the GPU, and log phase timings
The logs showed a CT run writing its NIfTI output ~10-15s before the image appeared. That gap was the mask label scan (nibabel + np.unique over the whole volume) and the viewer's base64 encode of both the image and mask — neither of which touches CUDA, but both of which ran inside the @spaces.GPU function and so were billed against the ZeroGPU allocation. Split for all three models. The decorated function now does inference only and hands the result to a gr.State; a following .then() does the label scan and the viewer encode on CPU. CT additionally gains generate(scan_labels=False) so the label pass can be deferred, and resolve_anatomy_labels() to run it afterwards. Added utils/timing.py: a nesting phase() context manager and a Stopwatch, all output prefixed [nv-generate] so it greps cleanly out of Gradio/MONAI/tqdm noise. Instrumented startup (clone, per-model prefetch, mask extraction, UI build) and generation (inference, label scan, viewer encode with payload size). A run now read…261940d
-
Initial commit: NV-Generate Gradio showcase
A Hugging Face Spaces app that surfaces NVIDIA Medtech's three open-weight 3D medical image generators (CT, MR, MR Brain) under one console-style UI, all running the latest MAISI-v2 rectified-flow variants. Architecture ============ - app.py — Gradio Blocks entrypoint. Hero card landing → per-model workspace switched via gr.Group visibility. Auto-clones the NV-Generate-CTMR upstream repo on first run so the same flow works locally and on HF Spaces. - pipelines/ base.py — GenerationRequest / GenerationResult dataclasses. _runner.py — Shared driver: chdir into upstream, write modified configs into configs/_temp/, invoke scripts.diff_model_infer or scripts.inference, harvest the resulting NIfTI(s). ct.py — Paired image + 132-class mask (rflow-ct). mr.py — Multi-contrast MR image-only (rflow-mr). mr_brain.py — Brain MR multi-sequence (rflow-mr-brain). - viewer/ niivue_embed.py — Render…ab1db83