docs: update readme for engine/web/desktop
Browse files
README.md
CHANGED
|
@@ -1,51 +1,128 @@
|
|
| 1 |
# Splasher
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
frame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
## Installation
|
| 34 |
|
| 35 |
```bash
|
| 36 |
cd ~/dev/splasher
|
| 37 |
-
uv sync #
|
| 38 |
-
uv sync --extra
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
```
|
| 40 |
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
```bash
|
| 44 |
-
uv
|
|
|
|
|
|
|
|
|
|
| 45 |
```
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
(extra `apairo`) enveloppe tout dataset apairo synchrone.
|
|
|
|
| 1 |
# Splasher
|
| 2 |
|
| 3 |
+
A **labeling** tool with a generic core: you give it a *synchronous dataset* — at each
|
| 4 |
+
timestamp, a **pack of named channels** (3D point cloud, camera image, pose, …) — and you
|
| 5 |
+
label either a **top-down 2D grid (BEV)**, the **3D points** directly, or both.
|
| 6 |
+
|
| 7 |
+
First use case: **traversability**. But nothing is hard-wired: no imposed class schema, no
|
| 8 |
+
imposed world semantics, and **no mandatory dependency on any dataset format**. apairo is
|
| 9 |
+
just one optional input adapter.
|
| 10 |
+
|
| 11 |
+
## Idea
|
| 12 |
+
|
| 13 |
+
- Several synchronized channels, shown as references: you roam freely in the 3D cloud and
|
| 14 |
+
look at the camera images.
|
| 15 |
+
- You choose which **channels** to display (the *Channels* dock: show/hide each cloud or
|
| 16 |
+
camera available in the source — multiple cameras and clouds supported).
|
| 17 |
+
- You **draw the grid of cells** (top-down view): its extent and cell size, created
|
| 18 |
+
explicitly via **"New grid"**. Undo is **per frame**.
|
| 19 |
+
- You **drag a rectangle** with the mouse on the top-down view. Depending on the target:
|
| 20 |
+
- **Grid**: fills the covered cells with the active class (output = raster of IDs).
|
| 21 |
+
- **Points**: assigns the class to the 3D points in the rectangle (output = per-point labels).
|
| 22 |
+
- Interaction: **left-drag = apply**, **right-drag = erase / deselect**,
|
| 23 |
+
**Shift-drag or middle-button = pan**, **wheel = zoom**. An **ego marker** (X = forward,
|
| 24 |
+
Y = left) is drawn at the origin in both the BEV and the 3D views, using the same axis
|
| 25 |
+
convention so the two read consistently.
|
| 26 |
+
- **Selection** mode (desktop-style): left-drag adds cells to the selection, right-drag
|
| 27 |
+
removes them (non-contiguous selections allowed), then you **apply** the class to the whole
|
| 28 |
+
selection at once. Changing the grid asks for **confirmation** if a labeling already exists.
|
| 29 |
+
- **Accumulation**: you can accumulate ±N frames **registered by their poses** into the
|
| 30 |
+
current frame's frame of reference (a denser cloud helps labeling). The grid and labels
|
| 31 |
+
stay **per frame**: a brush stroke on the accumulated cloud is **de-accumulated** back to
|
| 32 |
+
each source frame. (Requires a `POSE` channel.)
|
| 33 |
+
|
| 34 |
+
## Architecture (swappable GUI)
|
| 35 |
+
|
| 36 |
+
Layers from generic to specific — each depends only on the previous one:
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
splasher/
|
| 40 |
+
core/ pure numpy model (grid, label targets, BEV projection, accumulation…)
|
| 41 |
+
engine/ headless Session: all the state + operations, with no UI dependency.
|
| 42 |
+
Returns a *semantic* ViewState (points + per-point labels + channel,
|
| 43 |
+
BEV field, grid raster, selection, images) — not pixels.
|
| 44 |
+
server/ FastAPI backend on the same Session + serves the web front; desktop app.
|
| 45 |
+
web/ web front (vanilla, zero build) — the ONLY front.
|
| 46 |
+
vendor/ Three.js hosted locally (offline).
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
**One front, one engine.** The web front is served by the backend; the desktop app
|
| 50 |
+
(`splasher` without `--serve`) opens that same front in a **native webview** (pywebview,
|
| 51 |
+
Spotify/Electron style) — so the desktop *is* the web front, in a window.
|
| 52 |
+
|
| 53 |
+
The `Session` (`splasher.engine.Session`) is the flexibility point: it draws nothing, it
|
| 54 |
+
exposes `view_state()` + commands (`paint_rect`, `select_rect`, `apply_selection`,
|
| 55 |
+
`set_frame`, `set_accum_radius`, `commit_grid`, `save`/`load`, …). Plugging in another front
|
| 56 |
+
= consuming this `Session` (the web does it over HTTP). **Colorization stays on the front
|
| 57 |
+
side**: each front draws the primitives its own way (reference helpers in
|
| 58 |
+
`splasher.engine.render`, mirrored in JS under `web/src`).
|
| 59 |
|
| 60 |
## Installation
|
| 61 |
|
| 62 |
```bash
|
| 63 |
cd ~/dev/splasher
|
| 64 |
+
uv sync # core + engine only (numpy)
|
| 65 |
+
uv sync --extra app # + desktop app (FastAPI + uvicorn + pywebview)
|
| 66 |
+
uv sync --extra api # + headless web/API server (FastAPI + uvicorn)
|
| 67 |
+
uv sync --extra apairo # + apairo adapter (optional)
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
## Demo (zero external data)
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
uv run python examples/demo_arraysource.py # desktop app (native window, `app` extra)
|
| 74 |
+
splasher # empty: file-viewer mode (browse + open files)
|
| 75 |
+
splasher demo # synthetic dataset
|
| 76 |
+
splasher demo --serve # web server → http://127.0.0.1:8077 (`api` extra)
|
| 77 |
```
|
| 78 |
|
| 79 |
+
**File viewer**: launched empty, *Open file…* browses the filesystem and opens individual
|
| 80 |
+
point clouds (`.npy`/`.bin`/`.pcd`) and images into resizable views, with a clear error for
|
| 81 |
+
unreadable / unsupported files. Independent from the dataset/labeling mode.
|
| 82 |
+
|
| 83 |
+
Dark brutalist black-&-blue design. Labelable top-down (BEV) view (underlay mode:
|
| 84 |
+
height / density / intensity) + **3D/camera panels you can add, resize, and each bind to a
|
| 85 |
+
channel** (3D color-by: height / intensity). Sensor placements (from `ChannelSpec.placement`)
|
| 86 |
+
are drawn as markers in 3D. Classes are fully editable (⚙ in the *Classes* panel). Three.js
|
| 87 |
+
is hosted locally (`web/vendor`) → works offline.
|
| 88 |
+
|
| 89 |
+
> Native desktop window: the `app` extra ships a Qt WebEngine (Chromium) backend, so
|
| 90 |
+
> `uv sync --extra app && splasher demo` opens a real native window out of the box. If no
|
| 91 |
+
> backend can start, it falls back to the default browser and keeps serving.
|
| 92 |
+
|
| 93 |
+
## Input
|
| 94 |
+
|
| 95 |
+
The core consumes a `Source`: `__len__`, `__getitem__(i) -> Frame`, `channels()`.
|
| 96 |
+
`ArraySource` builds one from in-memory numpy arrays. `ApairoSource` (`apairo` extra) wraps
|
| 97 |
+
any synchronous apairo dataset.
|
| 98 |
+
|
| 99 |
+
### apairo
|
| 100 |
|
| 101 |
```bash
|
| 102 |
+
uv sync --extra apairo --extra app # adapter + desktop
|
| 103 |
+
splasher /path/to/dataset --adapter apairo # all channels
|
| 104 |
+
splasher /path/to/dataset --adapter apairo --channels lidar,cam_front,pose # only these
|
| 105 |
+
splasher /path/to/dataset --adapter apairo --reference lidar --tolerance 0.05 # sync an async dataset
|
| 106 |
```
|
| 107 |
|
| 108 |
+
Two ways to pick what you work on: **load everything** (a synchronous dataset) and choose
|
| 109 |
+
what to *display* in the UI (the *Clouds (BEV)* toggles + **Add view** per channel), or
|
| 110 |
+
**select channels at load time** with `--channels a,b,c`. An asynchronous dataset needs
|
| 111 |
+
`--reference <channel>` (and optionally `--tolerance`) to be synchronized first.
|
| 112 |
+
|
| 113 |
+
## API (back/front mode)
|
| 114 |
+
|
| 115 |
+
`splasher demo --serve [--host H --port P]` starts a FastAPI server driven by the same
|
| 116 |
+
`Session` as the desktop app. Main endpoints (each command returns the updated `ViewState`
|
| 117 |
+
so a front renders in a single round-trip):
|
| 118 |
+
|
| 119 |
+
| Method | Route | Purpose |
|
| 120 |
+
|--------|-------|---------|
|
| 121 |
+
| `GET` | `/api/session` | ~static description (channels, classes, n_frames) |
|
| 122 |
+
| `GET` | `/api/view` | current render state |
|
| 123 |
+
| `POST` | `/api/frame`, `/api/class`, `/api/tool`, `/api/targets`, `/api/accum`, `/api/visibility` | settings |
|
| 124 |
+
| `POST` | `/api/paint`, `/api/select`, `/api/selection/apply`, `/api/selection/clear`, `/api/clear`, `/api/undo` | labeling |
|
| 125 |
+
| `POST` | `/api/grid`, `/api/save`, `/api/load` | grid & I/O |
|
| 126 |
|
| 127 |
+
numpy arrays travel as `{dtype, shape, data(base64)}` (`splasher.server.protocol`), decodable
|
| 128 |
+
directly into a `TypedArray` on the JavaScript side. Interactive docs at `/docs`.
|
|
|