|
Download README.md from SmaugC137/splasher: direct link, hf CLI and curl.
- Browser
- Download file 8.49 kB
-
https://huggingface.co/spaces/SmaugC137/splasher/resolve/main/README.md
- Command line
-
hf download hf://spaces/SmaugC137/splasher/README.md
-
curl -L -o README.md https://huggingface.co/spaces/SmaugC137/splasher/resolve/main/README.md
8.49 kB
| title: Splasher | |
| emoji: 💦 | |
| colorFrom: indigo | |
| colorTo: blue | |
| sdk: docker | |
| app_port: 7860 | |
| pinned: false | |
| license: mit | |
| short_description: Label synchronized 3D/image channels into a 2D BEV grid | |
| # Splasher | |
| > **Live demo** — this Hugging Face Space runs `splasher demo --serve` (the synthetic | |
| > source) inside a Docker container. The 3D cloud, camera and BEV grid are all interactive. | |
| > Note: the demo serves a **single shared session**, so concurrent visitors edit the same | |
| > grid. See the [GitHub repo](https://github.com/augustin-bresset/splasher) to run it locally. | |
| A **labeling** tool with a generic core: you give it a *synchronous dataset* — at each | |
| timestamp, a **pack of named channels** (3D point cloud, camera image, pose, …) — and you | |
| label either a **top-down 2D grid (BEV)**, the **3D points** directly, or both. | |
| First use case: **traversability**. But nothing is hard-wired: no imposed class schema, no | |
| imposed world semantics, and **no mandatory dependency on any dataset format**. apairo is | |
| just one optional input adapter. | |
| ## Idea | |
| - Several synchronized channels, shown as references: you roam freely in the 3D cloud and | |
| look at the camera images. | |
| - You choose which **channels** to display (the *Channels* dock: show/hide each cloud or | |
| camera available in the source — multiple cameras and clouds supported). | |
| - You **draw the grid of cells** (top-down view): its extent and cell size, created | |
| explicitly via **"New grid"**. Undo is **per frame**. | |
| - You **drag a rectangle** with the mouse on the top-down view. Depending on the target: | |
| - **Grid**: fills the covered cells with the active class (output = raster of IDs). | |
| - **Points**: assigns the class to the 3D points in the rectangle (output = per-point labels). | |
| - Interaction: **left-drag = apply**, **right-drag = erase / deselect**, | |
| **Shift-drag or middle-button = pan**, **wheel = zoom**. An **ego marker** (X = forward, | |
| Y = left) is drawn at the origin in both the BEV and the 3D views, using the same axis | |
| convention so the two read consistently. | |
| - **Selection** mode (desktop-style): left-drag adds cells to the selection, right-drag | |
| removes them (non-contiguous selections allowed), then you **apply** the class to the whole | |
| selection at once. Changing the grid asks for **confirmation** if a labeling already exists. | |
| - **Accumulation**: you can accumulate ±N frames **registered by their poses** into the | |
| current frame's frame of reference (a denser cloud helps labeling). The grid and labels | |
| stay **per frame**: a brush stroke on the accumulated cloud is **de-accumulated** back to | |
| each source frame. (Requires a `POSE` channel.) | |
| ## Architecture (swappable GUI) | |
| Layers from generic to specific — each depends only on the previous one: | |
| ``` | |
| splasher/ | |
| core/ pure numpy model (grid, label targets, BEV projection, accumulation…) | |
| engine/ headless Session: all the state + operations, with no UI dependency. | |
| Returns a *semantic* ViewState (points + per-point labels + channel, | |
| BEV field, grid raster, selection, images) — not pixels. | |
| server/ FastAPI backend on the same Session + serves the web front; desktop app. | |
| web/ web front (vanilla, zero build) — the ONLY front (packaged in the wheel). | |
| vendor/ Three.js hosted locally (offline). | |
| ``` | |
| **One front, one engine.** The web front is served by the backend; the desktop app | |
| (`splasher` without `--serve`) opens that same front in a **native webview** (pywebview, | |
| Spotify/Electron style) — so the desktop *is* the web front, in a window. | |
| The `Session` (`splasher.engine.Session`) is the flexibility point: it draws nothing, it | |
| exposes `view_state()` + commands (`paint_rect`, `select_rect`, `apply_selection`, | |
| `set_frame`, `set_accum_radius`, `commit_grid`, `save`/`load`, …). Plugging in another front | |
| = consuming this `Session` (the web does it over HTTP). **Colorization stays on the front | |
| side**: each front draws the primitives its own way (reference helpers in | |
| `splasher.engine.render`, mirrored in JS under `splasher/web/src`). | |
| ## Installation | |
| ```bash | |
| cd ~/dev/splasher | |
| uv sync # core + engine only (numpy) | |
| uv sync --extra app # + desktop app (FastAPI + uvicorn + pywebview) | |
| uv sync --extra api # + headless web/API server (FastAPI + uvicorn) | |
| uv sync --extra apairo # + apairo adapter (optional) | |
| ``` | |
| ## Demo (zero external data) | |
| ```bash | |
| uv run python examples/demo_arraysource.py # desktop app (native window, `app` extra) | |
| splasher # empty: file-viewer mode (browse + open files) | |
| splasher demo # synthetic dataset | |
| splasher demo --serve # web server → http://127.0.0.1:8077 (`api` extra) | |
| ``` | |
| **File viewer**: launched empty, *Open file…* browses the filesystem and opens individual | |
| point clouds (`.npy`/`.bin`/`.pcd`) and images into resizable views, with a clear error for | |
| unreadable / unsupported files. Independent from the dataset/labeling mode. | |
| Dark brutalist black-&-blue design. Labelable top-down (BEV) view (underlay mode: | |
| height / density / intensity) + **3D/camera panels you can add, resize, and each bind to a | |
| channel** (3D color-by: height / intensity). Sensor placements (from `ChannelSpec.placement`) | |
| are drawn as markers in 3D. Classes are fully editable (⚙ in the *Classes* panel). Three.js | |
| is hosted locally (`splasher/web/vendor`) → works offline. | |
| > Native desktop window: the `app` extra ships a Qt WebEngine (Chromium) backend, so | |
| > `uv sync --extra app && splasher demo` opens a real native window out of the box. If no | |
| > backend can start, it falls back to the default browser and keeps serving. | |
| ## Input | |
| The core consumes a `Source`: `__len__`, `__getitem__(i) -> Frame`, `channels()`. | |
| `ArraySource` builds one from in-memory numpy arrays. `ApairoSource` (`apairo` extra) wraps | |
| any synchronous apairo dataset. | |
| ### apairo | |
| ```bash | |
| uv sync --extra apairo --extra app # adapter + desktop | |
| splasher /path/to/dataset --adapter apairo # all channels | |
| splasher /path/to/dataset --adapter apairo --channels lidar,cam_front,pose # only these | |
| splasher /path/to/dataset --adapter apairo --reference lidar --tolerance 0.05 # sync an async dataset | |
| ``` | |
| Two ways to pick what you work on: **load everything** (a synchronous dataset) and choose | |
| what to *display* in the UI (the *Clouds (BEV)* toggles + **Add view** per channel), or | |
| **select channels at load time** with `--channels a,b,c`. An asynchronous dataset needs | |
| `--reference <channel>` (and optionally `--tolerance`) to be synchronized first. | |
| ## API (back/front mode) | |
| `splasher demo --serve [--host H --port P]` starts a FastAPI server driven by the same | |
| `Session` as the desktop app. Main endpoints (each command returns the updated `ViewState` | |
| so a front renders in a single round-trip): | |
| | Method | Route | Purpose | | |
| |--------|-------|---------| | |
| | `GET` | `/api/session` | ~static description (channels, classes, n_frames) | | |
| | `GET` | `/api/view` | current render state | | |
| | `POST` | `/api/frame`, `/api/class`, `/api/tool`, `/api/targets`, `/api/accum`, `/api/visibility` | settings | | |
| | `POST` | `/api/paint`, `/api/select`, `/api/selection/apply`, `/api/selection/clear`, `/api/clear`, `/api/undo` | labeling | | |
| | `POST` | `/api/grid`, `/api/save`, `/api/load` | grid & I/O | | |
| numpy arrays travel as `{dtype, shape, data(base64)}` (`splasher.server.protocol`), decodable | |
| directly into a `TypedArray` on the JavaScript side. Interactive docs at `/docs`. | |
| ## File-viewer mode | |
| Launched empty (`splasher`), *Open file…* browses the filesystem (type a path, **Tab** to | |
| complete) and opens point clouds (`.npy`/`.bin`/`.pcd`) and images (`.png`/`.jpg`/… or | |
| `.npy` `HxWxC`) into resizable views. The **Clouds (BEV)** panel selects which open clouds | |
| feed the BEV (multiple = combined), with a color mode (height/intensity/normal). The BEV | |
| grid and its labels are **independent** of the displayed cloud — switching/combining clouds | |
| never wipes them. **Export** writes the grid raster to a single `.npy` (default name | |
| `<cloud>_bev.npy`); **Save** writes a full session folder. | |
| ## Development | |
| ```bash | |
| uv sync --extra api # core + engine + server (for the test suite) | |
| uv run --extra api pytest -q # run the tests | |
| ``` | |
| The core/engine import without any UI dependency; tests are pure-numpy + FastAPI's | |
| `TestClient`. CI runs the suite on Python 3.11 and 3.12 (`.github/workflows/ci.yml`). | |