CoTracker NodeSet
The CoTracker NodeSet (CotrackerNodeSet, workspace/nodesets/model/model_cotracker.py) wraps CoTracker3 (Meta) as a point-tracking primitive. It is the temporal counterpart to VGGT: where VGGT's track head follows query pixels across a co-visible view set in one feed-forward pass, CoTracker follows points densely through a video / frame sequence, handling occlusion and long-range motion. It fills the gap VGGT cannot โ temporal correspondence over a walk, the substrate for motion cues, visual-odometry priors, and dynamic-object handling.
env: ac-cotracker (dedicated) ยท backend: standalone cotracker package (git, CoTrackerPredictor) ยท variant: cotracker3_offline ยท weights: facebook/cotracker3 scaled_offline.pth (fetched lazily)
| Primitive | Purpose |
|---|---|
model_cotracker__track_grid |
Seed a grid_size ร grid_size point grid on a chosen frame and track every point through the clip โ tracks (JSON envelope of tracks / visibility). The "track everything" mode. |
model_cotracker__track_points |
Track caller-supplied query pixels (t, x, y) through the clip โ tracks (same envelope). |
1. Two primitives: grid vs query points
track_grid is the dense "track everything" mode โ it seeds a regular grid_size ร grid_size grid on grid_query_frame (so N = grid_sizeยฒ points) and follows them all. track_points is the targeted mode โ the caller supplies exactly the pixels to follow, each as (t, x, y) = the frame the point is specified on plus its coordinates (a bare (x, y) defaults to frame 0). Both return the same envelope shape; a graph wires whichever fits โ a dense grid for global motion, explicit queries to follow chosen landmarks.
2. Video in โ frames, 0โ255 float
The images port is an ordered list of frames (base64). They are decoded and stacked to a (1, T, 3, H, W) tensor in 0โ255 range โ CoTracker's own convention, with no [0,1] normalization โ and fed verbatim. All frames must share one resolution (CoTracker requires it); a size mismatch or a malformed frame degrades the node to an empty output rather than guessing.
3. Weights โ checkpoint, not a repo clone
CoTracker's canonical loader (torch.hub.load("facebookresearch/co-tracker", โฆ)) clones the whole upstream repo at runtime. Since the cotracker package is already pip-installed in the env, the nodeset instead downloads only the checkpoint into the torch-hub cache and constructs the predictor directly โ no repo clone, offline-friendly once cached:
# Load the offline predictor from a downloaded checkpoint โ the repo is NOT cloned. ckpt = os.path.join(torch.hub.get_dir(), "checkpoints", "scaled_offline.pth") if not os.path.exists(ckpt): torch.hub.download_url_to_file(CKPT_URL, ckpt) model = CoTrackerPredictor(checkpoint=ckpt, offline=True, window_len=60).to(device) # video: (1, T, 3, H, W) float in 0-255 (CoTracker's own convention, no normalization) tracks, visibility = model(video, grid_size=grid_size) # or queries=(1,N,3) as (t,x,y)
Only cotracker3_offline (the whole-clip predictor, window_len=60) is wired; the streaming online variant is reserved. The predictor is a lazy singleton per variant, frozen and moved to the device once.
4. Multi-array envelope
Each port is a TEXT JSON envelope โ the named arrays as raw C-contiguous float32 buffers, base64-encoded (byte-exact across the server-mode HTTP boundary, ~4ร smaller than a JSON float list), plus the variant, the frame size image_hw = [H, W], and num_frames:
{"variant": "cotracker3_offline", "image_hw": [520, 779], "num_frames": 5, "tracks": {"shape": [5, 64, 2], "dtype": "float32", "b64": "<C-contiguous float32 buffer>"}, "visibility": {"shape": [5, 64], "dtype": "float32", "b64": "โฆ"}}
tracks is (T, N, 2) โ the (x, y) of every tracked point on every frame; visibility is (T, N), a float 0/1 mask (cast from the model's bool). A point tracked off-frame keeps a predicted (extrapolated) position with visibility 0 โ so out-of-bounds coordinates on invisible points are expected, not an error.
5. Canvas Nodes
model_cotracker__track_grid
| Field | Detail |
|---|---|
| Inputs | images (ANY โ ordered list of {rgb_base64} dicts or raw base64 strings; T frames) |
| Outputs | tracks (TEXT โ the ยง4 envelope {tracks, visibility}; "" on degraded) |
| Config | variant (default cotracker3_offline) ยท grid_size (slider 1โ50, default 10; N = gยฒ) ยท grid_query_frame (slider, default 0) |
| Backend call | model(video, grid_size=โฆ, grid_query_frame=โฆ) under no_grad โ tracks/visibility, batch dim squeezed |
model_cotracker__track_points
| Field | Detail |
|---|---|
| Inputs | images (ANY โ as above), query_points (ANY โ list[[t,x,y]]; a [x,y] defaults t=0; or a JSON list) |
| Outputs | tracks (TEXT โ same envelope; "" on degraded) |
| Config | variant (default cotracker3_offline) |
| Backend call | model(video, queries=(1,N,3)) โ tracks/visibility for the supplied points |
Engines are lazy singletons in a registry keyed by variant; GPU inference is single-flight per engine (one in-flight forward bounds peak VRAM โ CoTracker's cost scales with clip length ร point count ร resolution).
6. Server Mode
parallelism = "shared" (stateless tracking primitives โ one server across eval workers); server_python = conda_env_python("ac-cotracker", "COTRACKER_PYTHON"). CoTracker runs in a dedicated env: it installs from git (the cotracker package is pinned to a commit, not on PyPI, and is not a transformers model), so it is kept out of the shared ac-fm stack for provenance and reproducibility. Unlike VGGT this is not a numpy<2-forced split โ CoTracker is numpy-2-compatible โ it is a cleanliness choice. The file stays Python-3.8-parseable. Load: POST /api/components/nodesets/model_cotracker/load?mode=server.
7. Environment Variables
| Variable | Default | Purpose |
|---|---|---|
COTRACKER_PYTHON | ac-cotracker env python | Interpreter for the server subprocess |
COTRACKER_DEVICE | auto (โ cuda when available) | Torch device for the loaded model |
8. Degraded Mode
A weight-load failure latches (_load_failed โ no retry storm); that, a frame missing/malformed base64, a frame-size mismatch, or (for track_points) absent/malformed query_points, yields "" on the node's output. An empty envelope is a clean "no tracks this step" signal; consumers keep their own fallback and never receive fabricated trajectories.