Optical Flow NodeSet
The Optical Flow NodeSet (OpticalFlowNodeSet, workspace/nodesets/model/model_opticalflow.py) wraps RAFT (Recurrent All-Pairs Field Transforms, shipped in torchvision.models.optical_flow) as a dense motion primitive: a pair of consecutive RGB frames in, a per-pixel displacement field out. It fills the optical-flow coverage gap โ the frame-to-frame motion cue that ego-motion estimation, moving-object detection, and short-horizon dynamics all build on, without a hand-tuned pyramid.
env: ac-fm (shared FM env) ยท backend: torchvision RAFT (no HF download โ weights ship with torchvision) ยท variants: raft_large (default) / raft_small
| Primitive | Purpose |
|---|---|
model_opticalflow__estimate_flow |
Dense RAFT flow between two same-resolution frames โ flow (JSON envelope of an (H, W, 2) pixel-displacement field). |
1. One primitive: flow A โ B
The tool takes exactly two frames โ image_a and image_b, the "before" and "after" โ and returns the motion carrying each pixel of A to its match in B. This is the atomic optical-flow operation; a graph that wants flow over a clip loops it across consecutive pairs. Keeping the node to a single pair (rather than a whole video) mirrors the house "one pure primitive" style and keeps peak memory bounded to one frame pair.
2. Frames in โ same resolution, uint8
Each port accepts a {rgb_base64} dict (or a raw base64 string). Both frames are decoded to HWC uint8 RGB arrays and must share one resolution โ a size mismatch or a malformed frame degrades the node to an empty output rather than guessing. The checkpoint's own preprocessing transform (weights.transforms()) converts to float and normalizes to [-1, 1]; it does not resize.
3. Weights & the divisible-by-8 rule
RAFT requires spatial dims divisible by 8, so each frame is zero-padded up to the next multiple of 8, run, then the flow is cropped back to the original (H, W) โ preserving true pixel scale (no resize-and-rescale). The model returns its iterative-refinement list; the last entry is the final flow:
# weights ship with torchvision โ no HF download, offline-friendly. weights = Raft_Large_Weights.DEFAULT # or Raft_Small_Weights net = raft_large(weights=weights).eval().to(device) transform = weights.transforms() # normalize to [-1,1], no resize # โฆ pad both frames to a multiple of 8 โฆ flow = net(frame_a, frame_b)[-1] # final iterate, (1, 2, Hpad, Wpad) flow = flow[:, :, :H, :W] # crop padding back
raft_large is the accurate default; raft_small is ~10ร smaller and faster (coarser). Each variant is a lazy singleton in a registry, frozen and moved to the device once; inference is single-flight per engine.
4. Flow envelope
The output is a TEXT JSON envelope โ the flow as a raw C-contiguous float32 buffer, base64-encoded (byte-exact across the server-mode HTTP boundary, ~4ร smaller than a JSON float list):
{"shape": [480, 640, 2], "dtype": "float32", "b64": "<C-contiguous float32 buffer>", "variant": "raft_large"}
flow[y, x] is the displacement in pixels carrying pixel (x, y) of frame A to its match in frame B: channel 0 = dx (+ right), channel 1 = dy (+ down). The field is at the original input resolution.
5. Canvas Node
model_opticalflow__estimate_flow
| Field | Detail |
|---|---|
| Inputs | image_a (ANY โ {rgb_base64} dict or raw base64), image_b (ANY โ same size as A) |
| Outputs | flow (TEXT โ the ยง4 envelope; "" on degraded) |
| Config | variant (select raft_large | raft_small, default raft_large) |
| Backend call | pad to /8 โ model(a, b)[-1] under no_grad โ crop โ (H, W, 2) |
6. Server Mode
parallelism = "shared" (a stateless flow estimator โ one server across eval workers); server_python = conda_env_python("ac-fm", "OPTICAL_FLOW_PYTHON"). Optical flow runs in the shared ac-fm env โ torchvision RAFT is native there and needs no external install or extra weights download. The file stays Python-3.8-parseable. Load: POST /api/components/nodesets/model_opticalflow/load?mode=server.
7. Environment Variables
| Variable | Default | Purpose |
|---|---|---|
OPTICAL_FLOW_PYTHON | ac-fm env python | Interpreter for the server subprocess |
OPTICAL_FLOW_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 missing/malformed frame, or a resolution mismatch between A and B yields "" on the node's output. An empty envelope is a clean "no flow this step" signal; consumers keep their own fallback and never receive a fabricated motion field.