A Gaussian-splat segmentation playground, powered by SpAItial AI worlds
Generate a 3D world on SpAItial AI, view its Gaussian splat in the browser, segment objects in it on demand with SAM3, lift the 2D masks into the 3D Gaussians, then recolor / isolate those Gaussians — "segment once, reuse again".
SpAItial AI is a world-model company: describe or upload an
image and it generates a full 3D world you can explore and export. This demo is a showcase of
what you can build on top of those worlds — it loads a .ply Gaussian splat you export
straight from SpAItial AI.
This is a starting point to fork, not a product. It wires up a PlayCanvas splat viewer, a local Python SAM server, and a full 2D→3D lift + multi-view voting + true gsplat recolor pipeline so you can build your own splat-understanding tools on top.
The scene is static, so segmentation is "segment once, reuse forever" rather than per-frame. You point the camera (or run a batch sweep), the server runs a single SAM forward on the rendered frame, and the client lifts each 2D mask onto the splats it covers and stores the per-Gaussian index set. From then on the object lives in 3D — selectable, re-framable, recolorable — with no further inference.
See **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)** for the full write-up: component +
sequence + lift + batch-sweep + recolor + coordinate diagrams, the /segment API contract,
the 3D lift algorithm, per-Gaussian voting merge, camera/coordinate spaces, persistence, and
model tiers.
sam3 tier (see below). The mock and mobile_sam tiers need no HF account.npm install
The repo intentionally ships without any splat assets (they're heavy and gitignored). The recommended way to get one is to generate a world with SpAItial AI and export it:
.ply Gaussian splat — the app exports a ready-to-use,
PlayCanvas-compatible .ply directly (no conversion needed) — and drop it at
public/world.ply. The viewer derives the pivot, initial zoom, and spatial-grid bounds from
the splat centers at runtime — no per-scene config or manifest.public/mesh_simplified.ply. It is used only at startup to ground the initial camera on
the floor. If it's missing, the app still boots and grounds the spawn on the splat
bounds instead.SPLAT_URL at the top of
[src/main.ts](./src/main.ts) — change it for a different filename.So the full workflow is: create a world on SpAItial AI → export the splat .ply → export the
collision mesh → drop both into public/.
Doing it programmatically? The SpAItial developer API (
POST /v1/worlds) creates worlds from text or image input; download the splat from/v1/worlds/requests/:id/splatand start the simplified-mesh export withPOST /v1/worlds/requests/:id/exports/mesh-simplified. See About SpAItial AI.
Only needed for the developer API path, which returns .spz (the app already gives you a
.ply). Convert a raw .spz to a PlayCanvas-compatible .ply with:
python3 -m venv .venv-spz
.venv-spz/bin/pip install "spz @ git+https://github.com/nianticlabs/spz" numpy
.venv-spz/bin/python scripts/convert-spz-to-playcanvas-ply.py <input>.spz public/world.ply --max-splats 0
Pick a tier (details + latencies in [server/README.md](./server/README.md)):
cd server
# Tier 1 — mock: deterministic ellipses, ZERO ML deps. Instant, great for frontend work.
SAM_MODEL=mock ./run.sh # http://localhost:8765
# Tier 2 — mobile_sam / fallback: real masks, NO gated weights (MobileSAM points/boxes +
# FastSAM text via CLIP). Sub-second per segment on MPS.
SAM_MODEL=mobile_sam ./run.sh
# Tier 3 — sam3: gated, open-vocab "segment all <noun>" text segmentation.
# 1) Accept the license at https://huggingface.co/facebook/sam3
# 2) Authenticate, then run:
hf auth login # paste a Hugging Face token
SAM_MODEL=sam3 ./run.sh # ~3.4 GB download on first run; ~13 s/call on MPS
run.sh creates server/.venv, installs requirements.txt, and launches uvicorn on
:8765. The viewer falls back to the built-in mock mask if the server is unreachable, so
npm run dev always shows something.
npm run dev
Open the printed Vite URL.
G) — type a concept (e.g. sofa), press G, and the current frame is
segmented and lifted into the splats. Concept-only (open-vocab text) — no point/click prompts.OffscreenCanvas) plus the
camera pose at trigger time.gsplatModifyVS shader-chunk override and a per-splat id texture.Full detail, diagrams, and the API contract: **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)**.
| Input | Action |
|---|---|
| Drag | orbit (yaw/pitch) around the pivot |
| Wheel | zoom |
| W A S D / arrows | pan the pivot |
| Q / E | lower / raise the pivot |
| Type a concept + G | segment the current view (open-vocab text prompt) |
| Batch Segment | one-time 16-view look-around sweep over the concept list |
| Gaussian view select | point highlight / recolor + dim / isolate |
16 × concepts calls) is a leave-it-running job (e.g. 8 concepts ≈ ~28 min on SAM3).
This is why segmentation is on-demand and lifted-once, not a per-frame loop. mobile_sam is
sub-second per segment.hf auth login), ~3.4 GB.**mobile_sam text needs CLIP** weights (FastSAM + CLIP); on SSL-restricted networks the
CLIP download can fail — point/box prompts still work without it.This project's code is MIT licensed — fork it freely.
Important: the SAM3 model weights are not MIT. They are distributed under Meta's SAM license and are gated on Hugging Face — you must accept that license yourself to download and use them. This repo ships no weights.
Credits:
world.ply) and collision meshes (mesh_simplified.ply) this demo loads are created and
exported with SpAItial AI. This project exists to showcase those worlds.fallback tier).sam3 tier).9 commits
TypeScript
70.0%
Python
21.5%
CSS
5.7%
HTML
2.1%
A Gaussian-splat segmentation playground, powered by SpAItial AI worlds
Generate a 3D world on SpAItial AI, view its Gaussian splat in the browser, segment objects in it on demand with SAM3, lift the 2D masks into the 3D Gaussians, then recolor / isolate those Gaussians — "segment once, reuse again".
SpAItial AI is a world-model company: describe or upload an
image and it generates a full 3D world you can explore and export. This demo is a showcase of
what you can build on top of those worlds — it loads a .ply Gaussian splat you export
straight from SpAItial AI.
This is a starting point to fork, not a product. It wires up a PlayCanvas splat viewer, a local Python SAM server, and a full 2D→3D lift + multi-view voting + true gsplat recolor pipeline so you can build your own splat-understanding tools on top.
The scene is static, so segmentation is "segment once, reuse forever" rather than per-frame. You point the camera (or run a batch sweep), the server runs a single SAM forward on the rendered frame, and the client lifts each 2D mask onto the splats it covers and stores the per-Gaussian index set. From then on the object lives in 3D — selectable, re-framable, recolorable — with no further inference.
See **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)** for the full write-up: component +
sequence + lift + batch-sweep + recolor + coordinate diagrams, the /segment API contract,
the 3D lift algorithm, per-Gaussian voting merge, camera/coordinate spaces, persistence, and
model tiers.
sam3 tier (see below). The mock and mobile_sam tiers need no HF account.npm install
The repo intentionally ships without any splat assets (they're heavy and gitignored). The recommended way to get one is to generate a world with SpAItial AI and export it:
.ply Gaussian splat — the app exports a ready-to-use,
PlayCanvas-compatible .ply directly (no conversion needed) — and drop it at
public/world.ply. The viewer derives the pivot, initial zoom, and spatial-grid bounds from
the splat centers at runtime — no per-scene config or manifest.public/mesh_simplified.ply. It is used only at startup to ground the initial camera on
the floor. If it's missing, the app still boots and grounds the spawn on the splat
bounds instead.SPLAT_URL at the top of
[src/main.ts](./src/main.ts) — change it for a different filename.So the full workflow is: create a world on SpAItial AI → export the splat .ply → export the
collision mesh → drop both into public/.
Doing it programmatically? The SpAItial developer API (
POST /v1/worlds) creates worlds from text or image input; download the splat from/v1/worlds/requests/:id/splatand start the simplified-mesh export withPOST /v1/worlds/requests/:id/exports/mesh-simplified. See About SpAItial AI.
Only needed for the developer API path, which returns .spz (the app already gives you a
.ply). Convert a raw .spz to a PlayCanvas-compatible .ply with:
python3 -m venv .venv-spz
.venv-spz/bin/pip install "spz @ git+https://github.com/nianticlabs/spz" numpy
.venv-spz/bin/python scripts/convert-spz-to-playcanvas-ply.py <input>.spz public/world.ply --max-splats 0
Pick a tier (details + latencies in [server/README.md](./server/README.md)):
cd server
# Tier 1 — mock: deterministic ellipses, ZERO ML deps. Instant, great for frontend work.
SAM_MODEL=mock ./run.sh # http://localhost:8765
# Tier 2 — mobile_sam / fallback: real masks, NO gated weights (MobileSAM points/boxes +
# FastSAM text via CLIP). Sub-second per segment on MPS.
SAM_MODEL=mobile_sam ./run.sh
# Tier 3 — sam3: gated, open-vocab "segment all <noun>" text segmentation.
# 1) Accept the license at https://huggingface.co/facebook/sam3
# 2) Authenticate, then run:
hf auth login # paste a Hugging Face token
SAM_MODEL=sam3 ./run.sh # ~3.4 GB download on first run; ~13 s/call on MPS
run.sh creates server/.venv, installs requirements.txt, and launches uvicorn on
:8765. The viewer falls back to the built-in mock mask if the server is unreachable, so
npm run dev always shows something.
npm run dev
Open the printed Vite URL.
G) — type a concept (e.g. sofa), press G, and the current frame is
segmented and lifted into the splats. Concept-only (open-vocab text) — no point/click prompts.OffscreenCanvas) plus the
camera pose at trigger time.gsplatModifyVS shader-chunk override and a per-splat id texture.Full detail, diagrams, and the API contract: **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)**.
| Input | Action |
|---|---|
| Drag | orbit (yaw/pitch) around the pivot |
| Wheel | zoom |
| W A S D / arrows | pan the pivot |
| Q / E | lower / raise the pivot |
| Type a concept + G | segment the current view (open-vocab text prompt) |
| Batch Segment | one-time 16-view look-around sweep over the concept list |
| Gaussian view select | point highlight / recolor + dim / isolate |
16 × concepts calls) is a leave-it-running job (e.g. 8 concepts ≈ ~28 min on SAM3).
This is why segmentation is on-demand and lifted-once, not a per-frame loop. mobile_sam is
sub-second per segment.hf auth login), ~3.4 GB.**mobile_sam text needs CLIP** weights (FastSAM + CLIP); on SSL-restricted networks the
CLIP download can fail — point/box prompts still work without it.This project's code is MIT licensed — fork it freely.
Important: the SAM3 model weights are not MIT. They are distributed under Meta's SAM license and are gated on Hugging Face — you must accept that license yourself to download and use them. This repo ships no weights.
Credits:
world.ply) and collision meshes (mesh_simplified.ply) this demo loads are created and
exported with SpAItial AI. This project exists to showcase those worlds.fallback tier).sam3 tier).9 commits
TypeScript
70.0%
Python
21.5%
CSS
5.7%
HTML
2.1%