Skip to content

Auto-register video multicam rigs on web; drop the stale video reader - #1879

Merged
BryonLewis merged 4 commits into
mainfrom
fix/camera-registration
Sep 2, 2026
Merged

Auto-register video multicam rigs on web; drop the stale video reader#1879
BryonLewis merged 4 commits into
mainfrom
fix/camera-registration

Conversation

@romleiaj

@romleiaj romleiaj commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Running Auto Register on a rig whose cameras are videos failed on the girder worker before VIAME was ever invoked:

romleiaj and others added 4 commits September 2, 2026 11:59
Running Auto Register on a rig whose cameras are videos failed on the
girder worker before VIAME was ever invoked:

  ValueError: Image-pair subsets are only supported for image-sequence
  cameras (camera "G336")

The desktop backend extracts a video camera's chosen frames to stills so
the register pipes never need vidl_ffmpeg (#1854); the server side of
that feature only ever implemented the image-sequence half and raised
here instead. Port the extraction.

multicam_pipeline gains extract_video_frames -- per-frame ffmpeg at
frame/fps seconds, written as <camera>.frame_<N>.png. That name is a
contract with the desktop backend: both ingest paths parse it to recover
the frame. build_multicam_kwiver_settings now branches on media type
rather than rejecting anything but images, and an extracted camera takes
the identical image-list input path, so the pipe sees one input
mechanism for both media types. A dataset with no usable fps raises
rather than silently extracting t=0 for every frame.

run_pipeline reports extraction progress to the job log and checks for
cancellation between frames (a loop of short ffmpeg calls has nothing
else watching for it), and skips the video reader type and downsampler
settings once every input is an image list. It also downloads the
transcoded video for subset runs: useDataset hands the web viewer
media.video, so the frame://N the client sent index that timeline, and
extracting from sourceVideo would pair frame numbers against a different
one wherever the transcode shifted timing.

registration_output maps observation image names back from
<camera>.frame_<N>.png to frame://N before merging, so the stored
correspondences reference frames the client can resolve rather than
files that existed only in the job's work dir.

Desktop had the matching gap on the other side. runPipeline binds
-s input:video_reader:type=vidl_ffmpeg from the defaultDisplay camera's
type, before writeMultiCamStereoPipelineArgs replaces every input with
an image list -- so an extracted run reached the pipe with a video
reader pointed at input1_images.txt, and only on camera 1, since the
extraction branch sets no reader type for cameras 2..N. Whether that
actually broke depends on whether the pipe names camera 1's reader
`input` or `input1` (DIVE sets both spellings), which is why it survived
unnoticed: the feature was only ever exercised on image datasets.
videoSubsetCameras answers "will this run extract?" up front, and gates
the reader type, the downsampler rate, and the frameRange downsampler
block. Argument order is unchanged for every non-subset run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7q7QVPPVeWjkGA7cWNhXd
Two inconsistencies in the auto-register flow, both after the job is
launched.

Launching told the user to visit the Jobs tab, then the viewer's
navigation guard stopped them: hasUnsavedChanges covers the Camera
Registration panel's dirty state, and run() never saved or re-baselined
it, so any pre-run edit -- picked points, a disabled or deleted frame --
kept the guard armed for the whole job. The dataset is read-only while a
job runs on it, so the prompt offered only "Discard and Leave" for work
there was no way to save.

run() now persists the panel before the job exists, through the same
saveRegistration() the desktop close guard uses (a no-op when clean).
The save lands before the completion baseline is read, or the save's own
write would read as the job's first result and end the run immediately.

That also fixes Replace mode, which dropped prior matcher observations
from the store only. The job's output is merged into the SAVED
registration -- server-side ingest, or the desktop collector -- and the
result is rehydrated over the store, so those removals were being undone
by the reload rather than applied. Persisting first makes Replace mean
what it says on both platforms.

Second, a failed job left the panel claiming it was still running.
watchPipelineJob is optional in the Api and only desktop implemented it,
so web fell back to pollForResult, which by its own docstring cannot see
a job that failed: the status sat on "job running" until the 30 minute
timeout. Web already had the state -- useJobs tracks per-dataset job
status from girder's job notifications -- so watchPipelineJob is now
implemented against it and web takes the same awaitJobResult path
desktop does, reporting error and cancellation separately. The store
keeps one job per dataset, so a job already finished when the watch
starts is skipped by id, and the pipeline is not matched on (two
pipelines on one dataset are indistinguishable there; the caller
launches one).

Desktop's implementation is brought in line with the new one: it called
stop() from inside a watcher that can settle during its own immediate
pass, when the const is still in TDZ. Reaching it needs a matching job
that started in the same millisecond as the watch and had already ended,
so it is a latent trap rather than a live failure -- the added spec has
to freeze the clock to hit it at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7q7QVPPVeWjkGA7cWNhXd
A 3-cam run on a rig with a TIFF camera died before VIAME was invoked:

  Exception: unexpected metadata {... 'name': 'ir', 'type': 'large-image' ...}

.tif is in validLargeImageFormats rather than validImageFormats, so a
KAMERA rig's IR camera is typed large-image on web -- by validate_files
at import and by the assetstore importer alike. dive_tasks never
referenced that type at all: download_source_media handled image-sequence
and video and raised on anything else, and the single-camera path would
have died a few lines later on "Unknown input type". Pipelines on
large-image datasets were simply never implemented.

These cannot be fetched the way image sequences are. get_media gives
large-image entries a tile-metadata URL, because the viewer renders them
through girder's tile server, so following it downloads JSON rather than
an image. The new branch asks for the item's own file through
dive_dataset/<id>/media/<itemId>/download -- the same type-agnostic route
the image-sequence urls already use -- and writes it under the dataset's
own image name, which is what the image list carries and what a
registration observation is keyed on. The bytes are the original file
either way: convert_large_images only POSTs item/<id>/tiles, adding tile
views beside the file rather than replacing it.

Everywhere else the distinction is just "is this fed as an image list",
so constants gains ImageListTypes and the run's media-type tests use it:
frame-range filtering, the single-camera image-list branch, the input
manifest, and the multicam settings builder (which also resolves a
registration frame subset by name for these, and binds no video reader).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7q7QVPPVeWjkGA7cWNhXd
A 3-cam run over flat sea ice rejected all 14 candidate frames
(texture ~1.9) and fitted nothing, and the panel reported it the same way
it reports a clean fit: "Auto Register complete: review the registration
frames below." The reason was in the job log only:

  align_cameras: prefilter 7: ..._rgb.jpg texture=1.9 skipped=low_texture
  align_cameras: keeping 0 of 14 candidate frames
  align_cameras: pair rgb <-> ir: pooled fit failed (insufficient_points)

The data was already there. The pipeline records why it discarded each
candidate in the observation's stats.skipped, the parsers keep it, and
RegistrationFrameList renders it per row -- nothing summarized it, so a
run that achieved nothing looked like one that fitted the whole rig.
Worse on a re-run: the matcher is deterministic, so the merge changes
nothing and the run reported "the results matched the registration
already stored", which reads as up-to-date rather than failed twice.

summarizeRun reads the outcome back off the merged registration and
reportOutcome says it: no pair with a transform is a failure however
cleanly the job exited, so it reports through `error` (the panel's red
line) naming the reason and count, and that verdict is reached on the
unchanged-merge path too. A partial run names the rejected frames beside
what fitted; a clean run reads exactly as it did before.

Reason counts are per pair rather than summed -- every pair sees the same
candidate spread, so summing would report this 14-frame run as 42
rejections on a triplet -- and the partial line says "N of M pair(s)
fitted" rather than "fitted N of M", since a pair may carry a transform
this run had no hand in.

Pair-level stats ("error": "insufficient_points") are still dropped by
both parsers; surfacing those is a separate change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7q7QVPPVeWjkGA7cWNhXd
@romleiaj

romleiaj commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

fix/camera-registration — summary

Four commits on top of efe061e2 (Auto Camera Registration Pipeline, #1854).


1. 530e58b3 — Auto Register works on video rigs (web)

Was: running Auto Register on a rig whose cameras are videos failed
immediately with Image-pair subsets are only supported for image-sequence cameras (camera "XXX").

Why: DIVE Desktop extracts the chosen frames from each video into stills
before handing them to VIAME. The web/girder worker never got that half of the
feature — it just raised an error instead.

Now: the server extracts the frames with ffmpeg, exactly like desktop does,
and the results get mapped back to the right frame numbers when they're saved.
Also stopped the run from telling VIAME to expect a video once every input has
become a list of stills.


2. 13adf773 — Two Auto Register annoyances

Was:

  • The panel says "see the Jobs tab for progress", but leaving the page popped an
    unsaved-changes warning that only offered Discard — and the dataset is
    read-only while a job runs, so there was no way to save.
  • On web, if the job failed, the progress bar just kept spinning (for 30
    minutes).

Now:

  • Registration edits are saved automatically right before the job starts, so
    there's nothing to warn about. As a bonus this makes "Replace existing" work
    properly — previously its changes were silently undone.
  • Web now watches the actual job status like desktop does, so a failed or
    canceled job is reported straight away.

3. 67336ea8 — Pipelines run on TIFF (large-image) cameras

Was: a 3-cam run on a rig with a TIFF camera died with
unexpected metadata {... 'type': 'large-image' ...}.

Why: DIVE files .tif under "large-image" rather than "image sequence", and
the pipeline runner had no support for that type at all — it could only fetch
image sequences and videos.

Now: large-image cameras are downloaded and fed to VIAME the same way an
image sequence is. This covers single-camera and multi-camera runs, and training
gets it for free.


4. 167e3f03 — Tell the user why a run produced nothing

Was: a 3-cam run over flat sea ice rejected all 14 candidate frames and
fitted nothing, but the panel still said "Auto Register complete: review the
registration frames below."
The real reason was only in the job log. Re-running
it said "the results matched the registration already stored", which sounds
like everything is fine.

Now: the panel reports what the run actually achieved. If nothing fitted, it
shows a red line naming the reason and count, e.g.:

Auto Register fitted no camera pairs: every candidate frame was rejected
(14 low_texture). Try frames with more visible structure.

A partly successful run says how many pairs fitted and how many frames were
rejected. A clean run reads exactly as before.


Testing

All four are covered by unit tests. Full suites pass: 1406 client tests, 421
server tests, plus lint and typecheck.

@BryonLewis
BryonLewis self-requested a review September 2, 2026 18:38

@BryonLewis BryonLewis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave it a quick look and I think it is okay.

@BryonLewis
BryonLewis merged commit bca93b2 into main Sep 2, 2026
3 checks passed
@BryonLewis
BryonLewis deleted the fix/camera-registration branch September 2, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants