AgentCanvas / Pages / Developer Guide / Nodesets / Model / CoTracker NodeSet
2026-07-07

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

FieldDetail
Inputsimages (ANY โ€” ordered list of {rgb_base64} dicts or raw base64 strings; T frames)
Outputstracks (TEXT โ€” the ยง4 envelope {tracks, visibility}; "" on degraded)
Configvariant (default cotracker3_offline) ยท grid_size (slider 1โ€“50, default 10; N = gยฒ) ยท grid_query_frame (slider, default 0)
Backend callmodel(video, grid_size=โ€ฆ, grid_query_frame=โ€ฆ) under no_grad โ†’ tracks/visibility, batch dim squeezed

model_cotracker__track_points

FieldDetail
Inputsimages (ANY โ€” as above), query_points (ANY โ€” list[[t,x,y]]; a [x,y] defaults t=0; or a JSON list)
Outputstracks (TEXT โ€” same envelope; "" on degraded)
Configvariant (default cotracker3_offline)
Backend callmodel(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

VariableDefaultPurpose
COTRACKER_PYTHONac-cotracker env pythonInterpreter for the server subprocess
COTRACKER_DEVICEauto (โ†’ 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.

AgentCanvas docs