Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: CI

on:
push:
branches: [main]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
viewer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: astral-sh/setup-uv@v6
- run: bun install --frozen-lockfile
- run: bunx --bun tsc --noEmit
- run: bun run format:check
- run: bun run test

site:
runs-on: ubuntu-latest
defaults:
run:
working-directory: site
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
- run: bun install --frozen-lockfile
- run: bunx --bun tsc --noEmit
- run: bun run format:check

admin:
runs-on: ubuntu-latest
defaults:
run:
working-directory: admin
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
- run: bun install --frozen-lockfile
- run: bunx --bun tsc --noEmit

python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv run --locked --only-group dev ruff check scripts worker orchestrator
- run: uv lock --check
20 changes: 10 additions & 10 deletions docs/DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,18 @@ and recovery without duplicate Marble generations.
```sh
bun run build
bun run format:check
bun run test:shared-assets
bun run test:pull-assets
bun run test:publish-hook
bun run test:marble
bun run test:audio
bun run lint:python
bun run test
```

CI (`.github/workflows/ci.yml`) runs these, plus type checks in `site/` and `admin/`, on every
pull request. `bun run test` covers every unit test that needs no browser or server. The browser
checks need Chrome and a running server:

```sh
bun run test:audio-browser
bun run test:audio-package
bun run test:person-motion
bun run test:static-colliders
bun run test:walk-collision
bun run test:walk-clearance-browser
bun run test:xr-turning
bun run test:xr-browser
```

Python checks and formatting need `uv`; viewer-only use needs just Bun.
Expand Down
1 change: 0 additions & 1 deletion orchestrator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import argparse
import json
import os
import shlex
import subprocess
import sys
import time
Expand Down
1 change: 0 additions & 1 deletion orchestrator/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

import argparse
import os
import sys
import time
from pathlib import Path

Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"prepare:worlds": "bun scripts/prepare-worlds.ts",
"runs:publish": "node scripts/publish-runs.ts",
"assets:pull": "bun scripts/pull-assets.ts",
"test": "bun run test:unit && bun run test:node",
"test:unit": "bun test ./scripts/test-admin-blocks.ts ./scripts/test-bottle-agent-client.ts ./scripts/test-bottle-agent.ts ./scripts/test-bottle-floor.ts ./scripts/test-bottle-physics.ts ./scripts/test-bottle-scene.ts ./scripts/test-bottle-visual.ts ./scripts/test-fourd-audio.ts ./scripts/test-gaussian-ply.ts ./scripts/test-interaction-approach.ts ./scripts/test-object-track.ts ./scripts/test-person-motion-roundtrip.ts ./scripts/test-person-motion.ts ./scripts/test-person-size.ts ./scripts/test-person-stance.ts ./scripts/test-person-visibility.ts ./scripts/test-playback-time.ts ./scripts/test-prepared-world-server.ts ./scripts/test-prepared-world.ts ./scripts/test-pull-assets.ts ./scripts/test-quest-view.ts ./scripts/test-recorded-object.ts ./scripts/test-scene-config.ts ./scripts/test-scene-session.js ./scripts/test-shared-assets.ts ./scripts/test-static-colliders.ts ./scripts/test-walk-collision.ts ./scripts/test-xr-animation-cadence.ts ./scripts/test-xr-body-geometry.ts ./scripts/test-xr-body.ts ./scripts/test-xr-hands.ts ./scripts/test-xr-physical-walk.ts ./scripts/test-xr-render-policy.ts ./scripts/test-xr-replay-button.ts ./scripts/test-xr-scene-sidebar.ts ./scripts/test-xr-teleport.ts",
"test:node": "node --test scripts/test-publish-transport.ts scripts/test-publish-policy.ts",
"lint:python": "uv run --locked --only-group dev ruff check scripts worker orchestrator",
"test:pull-assets": "bun test ./scripts/test-pull-assets.ts",
"test:shared-assets": "bun test ./scripts/test-shared-assets.ts",
"test:publish-transport": "node --test scripts/test-publish-transport.ts",
Expand All @@ -36,8 +40,8 @@
"capture:audio": "bun scripts/capture-audio-demo.ts",
"test:person-motion": "bun test ./scripts/test-person-motion.ts ./scripts/test-gaussian-ply.ts ./scripts/test-person-motion-roundtrip.ts ./scripts/test-person-stance.ts ./scripts/test-person-visibility.ts",
"format": "bun run format:python && bun run format:web",
"format:check": "uv run --locked --group dev ruff format --check scripts worker && bunx --bun prettier --check \"src/**/*.{ts,js}\" \"scripts/**/*.{ts,js}\" \"server/**/*.ts\" \"*.html\" vite.config.ts package.json tsconfig.json",
"format:python": "uv run --locked --group dev ruff format scripts worker",
"format:check": "uv run --locked --only-group dev ruff format --check scripts worker orchestrator && bunx --bun prettier --check \"src/**/*.{ts,js}\" \"scripts/**/*.{ts,js}\" \"server/**/*.ts\" \"*.html\" vite.config.ts package.json tsconfig.json",
"format:python": "uv run --locked --only-group dev ruff format scripts worker orchestrator",
"format:web": "bunx --bun prettier --write \"src/**/*.{ts,js}\" \"scripts/**/*.{ts,js}\" \"server/**/*.ts\" \"*.html\" vite.config.ts package.json tsconfig.json"
},
"dependencies": {
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ required-version = "==0.16.8"
target-version = "py311"
line-length = 100
cache-dir = ".context/ruff-cache"

[tool.ruff.lint]
# Pyflakes and syntax errors only for now; widen once the backlog is cleared.
select = ["E9", "F"]
1 change: 0 additions & 1 deletion scripts/anchor_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ def check_obs_mask(clip: str, sidecar: Path | None, frames: int = 3) -> dict:
`inMask` stayed at 0.99: on that clip the shadow really is re-observed. A clip where `inMask`
stays near 1 AND the mask changes nothing is the one to distrust.
"""
import cv2
import export_observation_confidence as eo

wd = ROOT / "public" / "worlds" / WORLD_DIR.get(clip, f"{clip}-4d")
Expand Down
2 changes: 1 addition & 1 deletion scripts/finetune_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import numpy as np

sys.path.insert(0, str(Path(__file__).resolve().parent))
from bake_video_colours import ROOT, SH_C0, SPZ_COLOR_SCALE, read_spz # noqa: E402
from bake_video_colours import ROOT, SPZ_COLOR_SCALE, read_spz # noqa: E402
from finetune_export import quat_mul, rotmat_to_quat_xyzw # noqa: E402


Expand Down
2 changes: 1 addition & 1 deletion scripts/frame_align.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ def scale_report(clip):
Stature is the body's longest principal extent, which a rotation does not change, so the scale
moves only because the camera ruler moved.
"""
from place_solve import world_splats, person_frames, fit_plane
from place_solve import world_splats, person_frames

world, pi3x, spz, floor, preset_scale = CLIPS[clip]
fa = json.load(open(ROOT / "public/worlds" / world / "framealign.json"))
Expand Down
2 changes: 1 addition & 1 deletion scripts/motion_jitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
splat i at frame t is the same body point: position differences are real motion.
"""

import argparse, json, sys
import argparse, json
import numpy as np

HDR_END = b"end_header\n"
Expand Down
1 change: 0 additions & 1 deletion scripts/package_multiperson.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
orthonormal,
quat_from_matrix,
quat_mul,
transform_ply,
)
from sfm_frame import camera0_reframe, describe as describe_frame # noqa: E402

Expand Down
5 changes: 2 additions & 3 deletions scripts/place_solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,15 +751,15 @@ def main():
tracks = load_tracks(cfg)
print(f"[{a.clip}] {len(tracks)} track(s), world {cfg['world']}", flush=True)

feats, bodyH0, low0 = [], None, None
feats, low0 = [], None
rng = np.random.default_rng(0)
for tid, seq, tr, _man in tracks:
fr = list(person_frames(seq))
op0 = fr[0][0]
ys = np.sort(op0[:, 1])
l0 = float(ys[int(len(ys) * 0.01)])
if low0 is None:
low0, bodyH0 = l0, float(fr[0][1][:, 1].max() - fr[0][1][:, 1].min())
low0 = l0
feats.append([tid, fr, tr, l0, json.load(open(seq))])
print(f" {tid}: {len(fr)} frames, frame-0 low {l0:.4f}", flush=True)

Expand Down Expand Up @@ -803,7 +803,6 @@ def main():
measure_pad(fr, scale, pos0 + tr, pad, mpu=mpu) for _t, fr, tr, _l, _s in feats
]
normal = pad["n"]
probe_xyz = None
else:
probe = []
for _t, fr, tr, _l, _s in feats:
Expand Down
2 changes: 1 addition & 1 deletion scripts/prepare_lhm_person.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
refused by the GPU.
"""

import argparse, hashlib, json, os, subprocess, sys
import argparse, hashlib, json, os, sys
from pathlib import Path

import cv2
Expand Down
8 changes: 4 additions & 4 deletions scripts/run_clip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2504,8 +2504,8 @@ def summary(self, wall):
if ps:
sc, res = ps["sizeCheck"], ps["residualCm"]
print(
f" place: add &place=1 for the per-sample solve "
f"(contact residual "
" place: add &place=1 for the per-sample solve "
"(contact residual "
+ ", ".join(
f"{res['before'][k]['rms']:.1f}->{res['after'][k]['rms']:.1f}"
for k in res["before"]
Expand Down Expand Up @@ -2690,8 +2690,8 @@ def status(dep):
return 1
if gated:
print(
f"\nSTOPPED AT THE CLEAN-REVIEW GATE before spending any credits. Look at the "
f"frames listed above, then re-run the same command with --gate-pass."
"\nSTOPPED AT THE CLEAN-REVIEW GATE before spending any credits. Look at the "
"frames listed above, then re-run the same command with --gate-pass."
)
return 2
return 0
Expand Down
2 changes: 1 addition & 1 deletion scripts/score_reference_frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
detector substitutes for this: torchvision's keypoint R-CNN reads bedroom f198 at 0.999.
"""

import argparse, json, sys
import argparse, json
from pathlib import Path

import cv2
Expand Down
4 changes: 1 addition & 3 deletions scripts/world_collision.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
uv run --locked --group inference python scripts/world_collision.py --world elevator-4d --spz public/marble-elevator-clean.spz
"""

import argparse, gzip, json, os, struct, sys
import argparse, gzip, json, os, struct
import numpy as np

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand Down Expand Up @@ -367,8 +367,6 @@ def ransac_walls(P, n, d, max_walls, tol, min_inliers, iters, rng, floor_pts):
bs = np.floor(s / 0.1).astype(np.int64)
bh = np.floor(h / 0.1).astype(np.int64)
occ = np.unique(bs * 100000 + bh)
ubs = np.unique(bs)
ubh = np.unique(bh)
# a bin column counts only when it holds enough of the sheet, so a stray splat a metre
# away does not stretch the extent
cols = np.bincount(bs - bs.min())
Expand Down
2 changes: 0 additions & 2 deletions scripts/world_ruler.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
import json
import os
import struct
import subprocess
import sys
from pathlib import Path

Expand Down Expand Up @@ -882,7 +881,6 @@ def person_masks_for(clip_name: str, ds_shape=None):
tdirs = sorted((p.parent).glob("track_*"))
src = {}
for td in tdirs:
t = int(td.name.split("_")[1])
for f in json.loads((td / "motion.json").read_text())["frames"]:
src.setdefault(f["sample"], f["sourceIndex"])
out = {}
Expand Down
2 changes: 1 addition & 1 deletion worker/stages/dense_pi3x.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

from __future__ import annotations
import argparse, gc, hashlib, json, os, shutil, sys, time
import argparse, gc, hashlib, json, os, sys, time
from pathlib import Path
import cv2
import numpy as np
Expand Down
3 changes: 1 addition & 2 deletions worker/stages/track_people.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def main():

estimator = PoseEstimator("./pretrained_models/human_model_files", device="cuda")
cap = cv2.VideoCapture(a.video)
tracks, closed, next_id = [], [], 0
tracks, next_id = [], 0
overlay_samples = {0, len(indices) // 2, len(indices) - 1}
mask_store = {}
unmatched_rcnn_total = 0
Expand All @@ -246,7 +246,6 @@ def main():
break
raw = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
h, w = raw.shape[:2]
diag = float(np.hypot(w, h))
padded, ow, oh = estimator.img_center_padding(raw)
tensor, annotation = estimator._preprocess(padded)
pl, pt, factor, _, _ = annotation
Expand Down
2 changes: 1 addition & 1 deletion worker/stages/vlm_judge.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
Requires OPENAI_API_KEY (see ~/.openai-env).
"""

import argparse, base64, json, os, ssl, sys, time, urllib.error, urllib.request
import argparse, base64, json, os, ssl, time, urllib.error, urllib.request

CTX = ssl.create_default_context()
try:
Expand Down
Loading