-
v1.5.0 — Train-till-Plateau Stable
released this
2026-05-27 12:02:49 +00:00 | 53 commits to main since this releaseBrush trains for
--total-steps(default 30 000) regardless of whether the splat already converged. v1.5.0 adds an opt-in patience-stop: hold out ~10 % of frames, monitor PSNR while training, and SIGTERM Brush when the curve flattens. On a typical converging capture this can save 30-50 % of the Brush stage.Added
PlateauMonitorintrain.py— pure-logic class that polls<output_dir>/eval_<step>/directories, computes mean PSNR viacompute_eval_psnr, maintains the(step, psnr)history, and decidesshould_stoponce the lastpatienceconsecutive Δ-PSNR values are all belowmin_delta_psnrand the last step is ≥min_steps. Idempotent; missing/in-progress eval dirs are handled gracefully.compute_eval_psnr(eval_dir, frames_dir)— cv2-based mean PSNR across rendered eval images vs originals. Pairs by filename stem so Brush's<orig>.pnglines up with ourframes/<orig>.jpg. Originals get downscaled (cv2.INTER_AREA) to the render resolution before MSE.- Six new
[brush]config fields:plateau_enabled(bool, defaultfalse),plateau_eval_split_every(2-50, default 10),plateau_eval_every(100-10000, default 1000),plateau_min_steps(default 5000),plateau_patience(1-20, default 3),plateau_min_delta_psnr(0 < x ≤ 5, default 0.05). Cross-field validator rejectsplateau_min_steps > max_stepsonly when the feature is enabled (so a CI-config loweringmax_stepsdoesn't have to touch plateau fields). build_brush_commandextension: whenplateau_enabled, appends--eval-split-every,--eval-every,--eval-save-to-disk, and ties--export-everytoplateau_eval_everyso every eval checkpoint has a fresh PLY for the SIGTERM-safe stop mechanism.run_brushorchestration: spawns a daemonPlateauMonitorthread, polls every 5 s, sendsproc.terminate()onshould_stop. The non-zero returncode from our own SIGTERM is recognised and not re-raised asCalledProcessError. Edge case: SIGTERM before any export →RuntimeErrorwith an actionable hint.
Discovery (in spec doc)
A 250-step Brush probe against
max_strasse/brush_datasetconfirmed three load-bearing assumptions, all documented indocs/superpowers/specs/2026-05-27-v15-train-till-plateau-design.md:- Brush emits no stdout/stderr in subprocess mode (TUI suppressed) — stdout-PSNR parsing is not viable, hence the filesystem-watch approach.
--eval-save-to-diskis reliable and writeseval_<step>/<orig_filename>.png.--eval-split-every Nis deterministic — every Nth frame, sorted by filename.
Tests
- 13 new unit tests (5 in
test_config.py, 6 intest_train.pyfor PSNR helper, 6 intest_train.pyforPlateauMonitor, 2 intest_train.pyforbuild_brush_commandbranches). All purely synthetic — no Brush invocation, no real-world frames. - 347 tests passing (up from 328 at v1.4.6), ruff clean, mypy clean on
train.py.
UX
In the default config (
plateau_enabled = false), v1.5.0 changes nothing. Enable via your user config:[brush] plateau_enabled = true # defaults are fine; tune patience / min_delta_psnr if you want aggressive stopsWhen triggered,
pipeline.logwill contain atrain.plateau_detectedevent with the full history oftrain.evalevents leading up to the stop. The final scene.ply is whichever export was written most recently before SIGTERM (one perplateau_eval_everysteps).Default-on candidacy
v1.5.0 ships opt-in. After real-world validation on 2-3 captures, a follow-up release will likely flip the default to
true.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.4.6 — Final Polish Stable
released this
2026-05-27 11:35:09 +00:00 | 60 commits to main since this releaseCloses the v1.4 line: small coverage gaps + early-warning UX + repo hygiene, so v1.5 starts from a clean baseline.
Added
- Pre-flight viewer-config check (
cli._warn_if_viewer_misconfigured). When[viewer] auto_open=true+target="supersplat-local"but the dist isn't built, the warning fires at the start ofprocess/resume/add-video/rescue— not after the ~5 h run finishes and the auto-open silently falls back. Doctor still catches the same case independently. 4 unit tests. - Test coverage for the v1.4.5 deprecation warning at
target="supersplat"(remote). Verifies theviewer.remote_supersplat_deprecatedlogger.warning fires for the remote path and stays silent for the local one. 2 new tests intest_viewer.py.
Removed
- Orphaned v1.3 hero assets —
docs/assets/max_strasse_hero.{gif,mp4,webm,jpg}(≈9 MB). The README has pointed atmax_strasse_autobisect_hero.*since v1.4.4; the v1.3 originals are still reachable via thev1.3.0git tag for historical purposes.
Fixed
- v1.4.0 Codeberg release-page body PATCH'd to remove the stale "no real-world smoke against a non-trivial structurally-failing capture" line — refuted by the v1.4.4 max_strasse end-to-end success (490/493 cams, 5 h 36 min).
Tests
- 328 tests passing (up from 322 at v1.4.5), ruff clean, mypy clean on the four core modules.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Pre-flight viewer-config check (
-
v1.4.5 — Quality Sweep Stable
released this
2026-05-27 09:13:11 +00:00 | 61 commits to main since this releaseFollow-up to the v1.4.0–v1.4.4 burst — non-feature improvements that accumulated during the v1.4 work: code hygiene, observability, disk reclaim, and a docs refresh that catches up with the local-viewer default.
Added
autosplat cleanup-rescue <capture_dir>— reclaim the per-proberescue/probes/<clip_id>/workspaces (typically ~1-3 GB per successful rescue).--keep-clips/--remove-clips(default keep, so resume/add-video keep working),--dry-runto preview. 4 CliRunner tests.train.heartbeatevents inpipeline.logevery 5 min during Brush training. The ~1-2 h Brush stage previously emitted nothing betweentrain.brush.startandtrain.done; non-TTY runs (watch daemon, WebUI, ssh) now have a visible pulse. Pure helper_make_train_heartbeat_emitter()so the throttling is unit-tested without a real Brush run (3 tests).- Deprecation warning when
[viewer] target = "supersplat"(remote). The remote editor is blocked by browsers' Mixed-Content policy (HTTPS page can't fetch HTTP localhost PLY) — the warning points users at the v1.4.4supersplat-localdefault.
Changed
cli.serve --with-supersplatshares_serve_local_and_blockwith the auto-open path. The helper grew anopen_browser=Falsekwarg so--no-open-browserstill works.
Fixed
- All remaining mypy strict noise in
watcher.pyandviewer.py— 18 errors in watcher (untypeddict,bytes|strevent paths,Observernot-a-type) + 2 in viewer (_Handler.__init__/log_messageannotations). TheObserverissue fixed by typing aswatchdog.observers.api.BaseObserver; the bytes/str by a tiny_as_str()helper.mypy src/autosplat/viewer.py src/autosplat/watcher.pyis now clean.
Docs
GETTING-STARTED.mdSection "Look at the result" rewritten to reflect the v1.4.4 local-viewer default (auto-open at127.0.0.1:3000, blocking server with Ctrl-C,setup_supersplat.shas required first-time step).CONFIGURATION.md[viewer]section expanded — all five keys listed, the auto-open lifecycle described step-by-step, daemon/WebUI exception noted.CAPTURE-GUIDE.mdgains a "Reclaiming disk after a successful rescue" subsection pointing at the newcleanup-rescuecommand.ARCHITECTURE.mdgains a "Documentation convention" subsection: v1.4+ usesdocs/superpowers/specs/YYYY-MM-DD-<topic>-design.mdinstead of post-hocPHASE-N-*.mdreports. Reasons recorded.
Tests
- 322 tests passing (up from 315 at v1.4.4), ruff clean, mypy clean on the two modules touched.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
released this
2026-05-27 07:32:33 +00:00 | 62 commits to main since this releaseAfter the v1.4.2/v1.4.3 viewer hotfixes, the default
target="supersplat"(remote editor at playcanvas.com) still produced an empty editor for users on modern browsers: HTTPS pages cannot fetch HTTP localhost resources (Mixed-Content blocking). The user could work around it with--with-supersplat; v1.4.4 just makes that the default.Changed
[viewer] targetdefault flipped to"supersplat-local". When the dist is built (bash scripts/setup_supersplat.sh— already part of repo setup),autosplat process/rescuefinish by starting both a local SuperSplat HTTP server and a local PLY server, openinghttp://127.0.0.1:3000?load=http://127.0.0.1:8765/scene.plyin the browser, and blocking until Ctrl-C. Everything is HTTP-on-localhost — no mixed-content blocking, no.plydownload dialog, no manual drag-and-drop.- Graceful fallback when the dist is missing. If
target="supersplat-local"buttarget/supersplat/dist/index.htmldoesn't exist, the viewer prints a clear console hint to run the setup script and falls back to a no-op (the existingautosplat doctorwarning already covers this).
Added
viewer._serve_local_and_block()— shared helper that wrapsserve_supersplat_local()+ browser-open + signal-handled block. Re-used by the auto-open path;cli.serve --with-supersplatkeeps its inline equivalent for now.
Tests
test_open_in_viewer_supersplat_local_no_browserrewritten to assert the dist-missing fallback (no browser, hint message).- New
test_open_in_viewer_supersplat_local_opens_local_servers_when_dist_presentverifies the dist-present path: fake dist withindex.html, both servers start, browser receives ahttp://127.0.0.1:*?load=http://127.0.0.1:*/scene.plyURL,stop_eventkeeps the test from blocking. test_load_default_config_parses_cleanlyupdated to expect the new default target.- 315 tests passing.
Real-world validation
First end-to-end smoke against
max_strasse.MP4— the same 5:35 drone pass that v1.2.0 left at 5/244 cameras and that v1.3.0 only rescued after a manual 4-clip cut.autosplat rescue max_strasse.MP4produced a 2.0 GB scene.ply with 490/493 cameras registered (99.4 %) in 5 h 36 min on M5, fully automatic. Pipeline trail in the logs:bisection.start duration_s=335.6 bisection.probe clip=0 cameras_registered=120 passed=true bisection.probe clip=1 cameras_registered=115 passed=true bisection.combine_start leaves=[0, 1] pipeline.adaptive_retry reason="low_camera_ratio: 0.03 < 0.5" matcher=sequential sfm.done cams=490 points=368582 quality_gate.passed ratio=0.9939 matcher=exhaustive train.done duration_s=8398 pipeline.done duration_s=20168Fly-through: YouTube. Updated hero asset in
docs/assets/max_strasse_autobisect_hero.*.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
released this
2026-05-27 06:44:11 +00:00 | 63 commits to main since this releaseFollow-up to v1.4.2 — the auto-open path was fixed for
process/rescue, butautosplat serve(without--with-supersplat) still opened the rawhttp://127.0.0.1:8765/scene.plyURL in the browser. Browsers have no MIME handler for.ply, so the result was a download prompt instead of a rendered splat.Fixed
autosplat serve(no--with-supersplat) opens the remote SuperSplat editor, not the raw PLY URL. New_remote_supersplat_url_for(ply_url)helper wraps the local PLY URL inhttps://playcanvas.com/supersplat/editor?load=<encoded-ply-url>— the same patternviewer.open_in_vieweruses fortarget="supersplat". The local PLY server keeps running so the remote editor can fetch the file.--no-open-browserstill suppresses the browser launch for headless / CI use.
Tests
- 2 new tests in
test_cli_serve.pycover URL construction (default port, high port). End-to-end CliRunner tests of the serve loop are intentionally avoided because they involve threading + signals + sockets and add fragility for no extra coverage — the helper is pure. - 314 tests passing.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
released this
2026-05-27 06:25:24 +00:00 | 64 commits to main since this releasePre-v1.4.2,
autosplat processopened SuperSplat in the browser with?load=http://127.0.0.1:8765/scene.plybut never actually started a server on port 8765 — SuperSplat tried to fetch, silently failed, and the editor opened blank. Drag-and-drop the file manually was the only workaround. The pattern existed since the earliest CLI but was first surfaced by the real-world v1.4.0 max_strasse rescue run (5 h 36 min total, 490/493 cams = 99.4 %, then SuperSplat opened empty).Fixed
viewer.open_in_viewernow actually serves the PLY. For remote targets (supersplat/playcanvas) the function wrapsserve_directory()around the browser-open and blocks on athreading.Eventthat a SIGINT/SIGTERM handler sets — so the local HTTP server stays up as long as the user wants to look at the splat, then shuts down cleanly when they press Ctrl-C. Console prints the viewer URL + PLY URL upfront so the user knows what's running.
Changed
pipeline.run_pipelineno longer calls the viewer. Blocking inside the pipeline orchestrator would stall the watch-folder daemon between captures and prevent the WebUI's JobRunner from marking jobs done. CLI commands (process,resume,add-video,rescue) now invoke a new_open_viewer_if_configured()helper after their Done summary, so the user sees the result first and then learns the server URL. Daemon and WebUI already had the same broken viewer path — removing it there is a no-op for users (it was never opening anything anyway).
Tests
- 2 new end-to-end viewer tests: one verifies
serve_directoryis actually called insideopen_in_viewer; the other fetches the PLY over HTTP from the running server to prove the bytes are reachable. test_cli_rescuegetstest_rescue_invokes_viewer_after_doneverifying the wiring at the CLI layer.- Removed three obsolete
patch("autosplat.pipeline.viewer_mod.open_in_viewer")intest_pipeline(the attribute no longer exists). - 311 tests passing, ruff clean, mypy clean on viewer.py + pipeline.py.
UX note
autosplat process video.mp4now blocks at the very end with a console message:Viewer: https://playcanvas.com/supersplat/editor?load=… Serving PLY at http://127.0.0.1:8765/scene.ply. Press Ctrl-C when finished to stop the local server.To opt out — e.g. for CI runs or when SuperSplat is started manually — set
[viewer] auto_open = falseor[viewer] target = "none"in the config.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.4.1 — Bisection Polish Stable
released this
2026-05-26 15:36:26 +00:00 | 66 commits to main since this releaseFollow-up to v1.4.0 — a probe-performance cap, a manual
rescueCLI command, per-clip progress visibility in the WebUI, and an optional smart-split at the motion peak. Plus the pre-existingdict | Nonemypy noise inpipeline.pyis finally cleared.Added
autosplat rescue <video | capture_dir>— manual trigger for the bisection path. Bypasses sequential/exhaustive matching when you already know a video is structurally hostile. Two modes (fresh-video / existing-capture); multi-video captures need an explicit--videobecause bisection is single-video only. 5 CliRunner tests cover both modes plus the rejection branches.- Probe-stage
target_framescap — new[retry] bisect_probe_target_framesconfig (default 120, range 30-1000). Exhaustive matcher cost scales as n²/2, so the override cuts probe-stage compute by ~4× compared to the pipeline-wide default of 250. Applied automatically insideprobe_clipalongside the existing matcher=exhaustive override. - WebUI per-clip bisection progress —
WatcherState.InProgress.detail(optional, backwards-compat),update_stage(stage, detail)API, end-to-end propagation throughwebui.state.list_capturesand the dashboard's active-job line: "Active job · bisect · probing clip 0_1 (depth 2/3)". Stage transitions reset the detail; legacy state.json without the field still loads cleanly. - Smart-split at motion peak — opt-in
[retry] bisect_smart_split(defaultfalse). When enabled,find_motion_peaksamples 30 frames per cut range, runs dense Farneback optical flow (downsampled to 320 px wide for speed), and places the cut at the strongest motion event instead of at the midpoint. Output is clamped to[min_clip_s, duration - min_clip_s]so a peak near an edge can't produce a sub-min sub-clip. Falls back to midpoint cleanly when OpenCV can't open the file or the motion signal is too flat (<10 % peak-to-trough ratio).
Fixed
bisect_recursivelyper-branch ffmpeg resilience (from the late-v1.4 fix sweep) —cut_videoraisingsubprocess.CalledProcessErroron a corrupt sub-range is now caught, logged asbisection.cut_aborted_branch, and treated as a failed probe; the sibling branch still runs.read_source_video_from_lognow returns the most recentpipeline.start(from the late-v1.4 fix sweep) — after bisection appends a freshpipeline.startwith the leaf-clip list,autosplat resumeandautosplat add-videoon a bisected capture see the current state instead of silently re-feeding the original failed input.run_pipeline/run_pipeline_with_adaptive_retrydict type-args — pre-existing mypy strict findings (lines 143 and 493, bothdict | None) finally typed asdict[str, Any] | None.mypy src/autosplat/{pipeline,bisection}.pyis now clean.
Tests
- 22 new tests across
test_bisection.py(smart-split + probe-perf + per-clip-state),test_pipeline.py(multi-pipeline.startreader),test_watcher.py(InProgress.detail),test_cli_rescue.py(the new command), andtests/webui/test_captures.py(detail propagation). Total 310 unit tests passing (uv run pytest -q), up from 291 in v1.4.0.
Docs
- README status line, mermaid, and release table updated to reflect v1.4.1.
- CAPTURE-GUIDE, CONFIGURATION, TROUBLESHOOTING already covered the bisection path; nothing new needed there for this point release.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
released this
2026-05-26 15:20:25 +00:00 | 72 commits to main since this releaseWhen
sequential → exhaustiveadaptive-retry exhausts itself (retry_hint=None), the pipeline now automatically binary-subdivides the source video, probes each leaf clip with a cheap preprocess+SfM-only run, and combines the surviving leaves through the existing multi-video path. Automates the manual 4-clip workflow that rescuedmax_strassein v1.3.0 — no new command, no new flag, just a longer run when the matcher swap isn't enough.Added
- Auto-bisection-rescue — third escalation in
run_pipeline_with_adaptive_retry, gated onretry_hint=None, single-video input, andcfg.retry.bisect_enabled(defaulttrue). - New module
src/autosplat/bisection.py—BisectionClipfrozen dataclass,build_ffmpeg_cut_command+cut_video(stream-copy, no re-encode),probe_clip(forcescolmap.matcher=exhaustivebecause sequential is unreliable on shorts),bisect_recursively(DFS halt-on-success-per-branch), andrescue_via_bisectionorchestrator. - Three new
[retry]config fields with conservative defaults:bisect_enabled=true,bisect_min_clip_s=60.0(range 10–600),bisect_max_depth=3(range 1–6 — depth 3 = ≤ 8 leaves). Disablebisect_enabledfor fast-fail in CI. - Bisection artefacts persist under
<capture_dir>/rescue/clips/<stem>_part_<clip_id>.mp4(physical sub-clips) and<capture_dir>/rescue/probes/<clip_id>/{frames,colmap}(per-clip SfM workspace) so a partial run is debuggable.clip_idis depth-encoded (0,0_1,0_1_0) — leading digit is the first/second half, deeper underscores trace each recursion step. - 20 new tests in
tests/test_bisection.py+ 4 intests/test_pipeline.py. Two opt-in real-binary integration tests sit behindAUTOSPLAT_BISECTION_E2E=1plus the existingneeds_ffmpeg/needs_colmapmarkers. Total 293 unit tests passing (uv run pytest -q, up from 265 in v1.3.0). - Docs:
CAPTURE-GUIDE.mdgains an "Auto-bisection internals (v1.4+)" section with the rescue/ layout andclip_idsemantics;CONFIGURATION.md[retry]table grows three rows;TROUBLESHOOTING.mdreplaces the old "if exhaustive also fails, you're stuck" branch with the actual escalation path and structured log events to watch for.
Fixed
bisect_recursivelyper-branch ffmpeg resilience —cut_videoraisingsubprocess.CalledProcessErroron a corrupt sub-range (broken keyframe, container quirk) is now caught, logged asbisection.cut_aborted_branch, and treated as a failed probe; the sibling branch still runs. Previously the whole rescue would abort with a rawCalledProcessError.read_source_video_from_lognow returns the most recentpipeline.start— after bisection appends a freshpipeline.startwith the leaf-clip list,autosplat resumeandautosplat add-videoon a bisected capture see the current state (the leaves) instead of silently re-feeding the original failed input. Pre-v1.4 captures with only one start event behave identically.
Design Notes
- Why a separate module?
pipeline.pywas already 600 LOC; bisection's ~330 LOC of recursion + ffmpeg-cut + probe logic gets isolated inbisection.pyand exposes only the orchestrator topipeline.py. Each unit is testable without the others (the recursion is monkeypatch-friendly via the_probe_fninjection point). - Why no WebUI per-clip progress in v1.4? Bisection runs inside the existing
sfmstage from the state-machine's perspective; events surface via structured logs only. A WebUIbisect_probestage with per-clip progress is a v1.4.1 candidate. - Why no standalone
autosplat rescuecommand? The user-chosen trigger is the auto-escalation path — a normalautosplat process …just keeps running. A standalone command can be added in v1.4.1 if the auto-path proves clumsy. - Why no smart-split? Midpoint binary cuts are the v1.4 strategy. Smart-split at motion-change (OpenCV optical flow) is a v1.4.1 candidate and would only help when the rotation event is concentrated rather than smeared across the clip.
Follow-ups (resolved post-v1.4.0)
Probe-stage compute cost (250 frames × exhaustive matcher)→ resolved in v1.4.1 via[retry] bisect_probe_target_frames=120(~4× faster probes).No real-world smoke against a non-trivial structurally-failing capture→ resolved in v1.4.4 with themax_strasse.MP4end-to-end run: 490/493 cameras (99.4 %), 2.0 GB scene.ply, 5 h 36 min on M5. Fly-through video.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Auto-bisection-rescue — third escalation in
-
v1.3.0 — Multi-Video Rescue Stable
released this
2026-05-24 16:22:03 +00:00 | 89 commits to main since this releasev1.3.0 — Multi-Video Rescue
When a single drone pass can't be reconstructed (180° turn, 360° spin,
rotation-heavy footage), v1.2.0 told you to re-shoot. v1.3.0 lets you
combine multiple passes into one capture — and ships the shoot rules
that say when this works.Added
- Multi-video captures —
autosplat process v1.mp4 v2.mp4 [...]
combines frames from N videos into one capture; COLMAP solves the
combined set. Each video gets a per-source frame-naming prefix
(<stem>_frame_NNNNN.jpg) so passes don't clobber each other. WebUI's
new-capture form accepts a multi-line textarea. autosplat add-video <capture_dir> <video>— append another pass
to an existing capture and rebuild frames + SfM + training with the
larger set. Mirrored in the WebUI as an "Add video" form on the
capture detail page.--target-frames NCLI flag (onprocess+resume) — per-run
override forpreprocess.target_frames. Useful for long videos where
the default cap of 250 subsamples too aggressively (a 30-min
walkthrough otherwise drops to 1 frame every 7s).docs/CAPTURE-GUIDE.md— empirical shoot rules from v1.2.0
smoke testing. Translation > rotation, smooth turns, ≥80% overlap,
textured surfaces, even lighting. Includes case data from real
failed captures so users can interpret their own quality-gate output.
Fixed / improved
Path | list[Path]union type throughoutrun_pipeline,
JobRunner, and the adaptive-retry wrapper. Every existing
single-video caller stays unchanged.- No special-case for "add to single-video capture." When you add a
video, all frames re-extract with the consistent per-source naming.
Trades one re-preprocess run (~1-2 min) for zero migration logic. - No multi-video schema for
pipeline.log-readers older than v1.3.0
— the newvideos: [...]field is additive; readers expecting
video: strstill see the legacy form when only one video was used.
Tests
265 unit tests (263 passed, 2 opt-in E2E skipped) — +19 over v1.2.0.
ruff check src/ tests/→ "All checks passed!".Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Multi-video captures —
-
v1.2.0 — Resume & Recovery Stable
released this
2026-05-24 14:26:46 +00:00 | 93 commits to main since this releaseResume & Recovery
Pipeline failures used to be terminal: every botched run had to be re-started from scratch (re-extracting frames, re-running COLMAP) and the user had to remember to swap the matcher by hand. v1.2.0 makes the pipeline self-healing — a partial capture can pick up where it died, the WebUI exposes a Resume button, and a low-camera SfM result auto-retries with the exhaustive matcher even after a process crash.
Added
- Adaptive matcher retry in CLI + WebUI — the watcher daemon's Phase-3 retry mechanism (
QualityGateFailure → retry_hint → swap matcher) was only wired into the long-runningwatchdaemon.autosplat processand the WebUI'sJobRunnernow share it via a new in-processrun_pipeline_with_adaptive_retrywrapper. A 180° drone turn that breaks the sequential matcher with 3/244 cameras self-rescues with exhaustive, frames intact. autosplat resume <capture_dir>CLI command — continues a previous capture from on-disk state.detect_completed_stagesinspectsframes/,colmap/sparse/0/,training/*.ply,output/scene.plyand computes the skip-set;read_source_video_from_logscrapes the original video path frompipeline.log(no new manifest file — works for every existing capture). Newcapture_dir_overrideparameter onrun_pipelineso the resumed run keeps its original date-stamped directory instead of forking<today>_<stem>.- WebUI Resume button — failed captures show a Resume button (replacing the broken-for-real-captures Retry that routed through
/process).POST /captures/{id}/resumecalls a newJobRunner.start_resume_jobthat spins up a worker thread runningresume_capture(...). Shares the existing cancellation/persistence/runs.jsonl path. - New-capture run-start flow (V12-1) — the dashboard's "New capture" button now actually starts a pipeline run from a video path (it previously linked back to the captures list).
GET/POST /captures/newwith path validation (.mp4/.mov/.m4v, file-exists check). - Persistent job history via
runs.jsonl(V12-2) — the JobRunner appends one record per finalized job to<captures_dir>/<id>/runs.jsonl(status, start/finish timestamps, error). On startup,load_history()rehydrates the in-memory history so a WebUI restart no longer wipes the "Recent jobs" view. Append-only, crash-safe. - Responsive WebUI layout + mobile sidebar (V12-3) — three breakpoints (Desktop ≥1024 px / Tablet ≤1023 px / Mobile ≤767 px). Sidebar narrows then slides off-canvas; stat-tiles collapse 4→2 columns; hamburger button toggles
.mobile-sidebar-openon body with tap-to-close backdrop; tables scroll horizontally on mobile. - URL-param developer mode (V12-4) — reactivates the latent aspect subthemes (
?aspect=shugo|gunshi|kantoku|sensei) and the HTMX-annot debug overlay (?dev=1) without adding a settings UI. Both persist in localStorage; URL-params set/clear them.
Fixed
- Quality gate runs on resumed SfM — resuming a capture whose previous SfM stage produced a bad sparse model (e.g. only 3/244 cameras) used to silently skip the quality gate and let Brush train on garbage — the gate lived inside the SfM
elsebranch.run_pipelinenow re-parsessparse/0via_parse_mapper_statswhensfmis inskip_stagesand feeds the synthesizedSfmResultintocheck_sfm_quality. The adaptive-retry wrapper also drops"sfm"fromskip_stageson retry so the matcher swap actually re-runs SfM. - Stale-job liveness reconciliation — jobs whose worker thread died mid-run (e.g. host sleep) used to hang as phantom
runningentries forever.JobRunner._reconcilenow flips them tofailedwithinterrupted — the run ended without producing a resulton the next read. - Version-source consistency —
pyproject.tomlandsrc/autosplat/__init__.pynow agree (both1.2.0).
Design Notes
- Adaptive retry vs. resume — orthogonal mechanisms. Adaptive retry (in-process) catches
QualityGateFailureduring a live pipeline and retries the next stage with the hint set; frames stay in memory. Resume (cross-process) is the answer when the process is fully gone (sleep / crash / Ctrl-C): it reads disk artifacts and the original log to reconstruct the run. - No manifest file for resume. Source video is scraped from the
pipeline.startJSON event inpipeline.log. Works for every existing capture without any migration. - Resume refuses on a complete capture. If
output/scene.plyexists,resume_captureraises immediately instead of silently redoing the whole pipeline.
Tests
246 unit tests (244 passed, 2 opt-in E2E skipped) — +40 over v1.1.2.
For the full changelog see
CHANGELOG.md.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Adaptive matcher retry in CLI + WebUI — the watcher daemon's Phase-3 retry mechanism (