Nodes/Atlas Camera/Atlas Conditioning Bundle πŸŽ›
ComfyUI Node

Atlas Conditioning Bundle πŸŽ›

Hand the video model pixel motion, not the words 'slow dolly left'

By mikejamesvfxΒ·Created 4 months agoΒ·Updated 18 days agoΒ· 3
Atlas Conditioning Bundle πŸŽ›
  • solve
  • source_image
  • camera_path
  • exclude_mask
  • bundle
  • projected
  • depth_preview
  • normal_preview
  • flow_preview
  • position_preview
  • valid
  • ghost
  • report
β—„resolution1024β–Ί
β—„hole_dilate_px0β–Ί
β—„last_n0β–Ί
β—„stride1β–Ί
β—„emit_flowtrueβ–Ί
β—„emit_normalstrueβ–Ί
β—„emit_positiontrueβ–Ί

Every hosted video platform ships a "camera control" box that takes a sentence. Slow dolly left, 35mm, a bit of tilt. The model then improvises what that means for your shot. Atlas Conditioning Bundle πŸŽ› is the other approach: it computes the exact displacement of every pixel and hands that over instead.

What it does

Feed it a solve that carries serialized projection meshes, the plate, and optionally a camera path. It renders each view of the move ONCE and reads metric depth, world normals, world position, forward and backward flow, per-frame K, and the ghost class map off the same z-buffer that chose the render's colours. That's the design point - no two passes can disagree with each other, or with the RGB, about what the geometry is.

Flow is derived, never estimated. With depth and two calibrated cameras the displacement is closed-form, and the unit test pins it against -fxΒ·t/Z at exactly 0.0e+00 error. A learned flow network here would be estimating a quantity Atlas already knows. The occlusion flag tests the target frame's own depth rather than inferring occlusion from flow inconsistency, which is strictly stronger: it knows which pixels became occluded, not just which look weird.

Why ATLAS_CONDITIONING is its own socket

You'll notice you can't wire bundle into an IMAGE node, and that's deliberate. Metric depth in metres and flow in pixels both leave [0, 1], and every preview or save hop would quantise them. ComfyUI refusing the wire is the protection, not a bug. This is a different animal from a ControlNet depth pass, where a normalised per-frame map is exactly what you want.

Practical consequence worth knowing before you build: in this pack, ATLAS_CONDITIONING is consumed by AtlasWriteConditioningEXR and AtlasAdherenceScore. So today this node is a measurement-and-export front end - conditioning you can inspect, score, and hand to a comp - not a drop-in for every video pack's control input.

Inputs and outputs

Required: solve (must carry serialized projection meshes - run AtlasDeriveProjectionGeometry or clean-plate layers first; without geometry the node refuses), source_image (the plate).

Optional, and these are the ones you'll actually touch:

  • camera_path - the move to measure. Without it you only get the solved camera rendered, which has no flow and no disocclusion by definition. Fine as an identity check, useless as conditioning.
  • resolution (1024, long edge) - the rasterizer is pure numpy and O(faces Γ— pixels) per frame, and it renders every frame. On long moves over dense relief meshes, keep this moderate or use stride.
  • hole_dilate_px (0) - grows the uncovered region before classifying. A z-buffered render leaves one-pixel seams along silhouettes; a few px turns speckle into coherent blobs. It only ever marks more as unseen, so it can't invent coverage.
  • exclude_mask - sky on a matte, a SkyDome. Excluded pixels classify LOW_CONFIDENCE and leave the fractions entirely.
  • last_n (0 = every frame) and stride - measure the tail of a move, or every Nth frame. Flow is then computed between the KEPT frames, so magnitudes are per-stride, and the report says so rather than letting you misread it.
  • emit_flow, emit_normals, emit_position (all true).

Outputs: bundle β†’ AtlasWriteConditioningEXR or AtlasAdherenceScore; projected; depth_preview, normal_preview, flow_preview, position_preview (display only); valid (MASK); ghost (MASK); report (JSON).

One detail that saves a support thread: the previews print their normalisation constants in report. Depth near/far is per sequence, not per frame, because a per-frame stretch makes a dolly look static; flow is at a fixed pixel scale, because an auto-scaled preview hides the magnitude change you're inspecting.

Install

cd <ComfyUI>/custom_nodes
git clone https://github.com/mikejamesvfx/atlas-camera.git

Or Manager β†’ atlas-camera (ComfyUI Registry). Restart. No pip step for the core nodes; the solve that feeds this wants numpy, so you're fine on a stock ComfyUI. MIT, Python 3.10+. The quickstart example in the repo (examples/atlas_input_quickstart_workflow.json) is the sane way to see the upstream half before wiring this in.

Traps

resolution Γ— frame count is the real cost, so a 200-frame move at 2048 will make you think the node hung. And a keyed zoom behaves correctly-but-surprisingly: radial flow, a stationary eye, and legitimately no disocclusion, because a focal change moves no eye and reveals nothing.

CategoryAtlas/08 Β· Look & Render

Inputs (11)

NameTypeDefaultDescription
solveATLAS_SOLVEA solve carrying serialized projection meshes β€” run AtlasDeriveProjectionGeometry or clean-plate layers upstream. Without geometry there is nothing to measure and this node refuses.
source_imageIMAGEThe plate. Textures the primary geometry, and sets the baseline the class map is measured against.
camera_pathoptATLAS_CAMERA_PATHThe move to measure along. Without one only the solved camera is rendered, which has no flow and no disocclusion by definition.
resolutionoptINT1024256–4096Long edge. The rasterizer is pure numpy and O(faces x pixels) PER FRAME, and this renders every frame β€” keep it moderate on long moves over dense relief meshes, or use stride.
hole_dilate_pxoptINT00–64Grow the uncovered region before classifying. A z-buffered render leaves one-pixel seams along silhouettes; a few px turns speckle into coherent blobs. Only ever marks MORE as unseen, so it cannot invent coverage.
exclude_maskoptMASKRegion already handled by something other than geometry β€” sky on a matte or a SkyDome. Excluded pixels are classified LOW_CONFIDENCE and left out of the fractions entirely.
last_noptINT00–4096Measure only the move's last N frames (0 = every frame). The end of a move is where disocclusion peaks.
strideoptINT11–64Measure every Nth frame. Flow is then computed between the KEPT frames, so its magnitudes are per-stride, not per-source-frame β€” the report says so.
emit_flowoptBOOLEANtrueForward and backward flow, derived from geometry. Costs one back-projection and two projections per frame.
emit_normalsoptBOOLEANtrueβ€”
emit_positionoptBOOLEANtrueβ€”

Outputs (9)

NameTypeDescription
bundleATLAS_CONDITIONINGβ€”
projectedIMAGEβ€”
depth_previewIMAGEβ€”
normal_previewIMAGEβ€”
flow_previewIMAGEβ€”
position_previewIMAGEβ€”
validMASKβ€”
ghostMASKβ€”
reportSTRINGβ€”