Reframe 360 Camera
Point a real camera around inside a 360° image and pull the shot out
- equirectangular
- image
- camera_info
You have a 360° image and you want… a shot - a normal-looking perspective frame at the exact focal length and sensor size a real camera would use. That's this node. It's a virtual camera sitting inside the panorama's sphere - pan, tilt, roll, change the lens in millimetres, pick a fisheye projection - and out comes a proper perspective frame, plus a text readout of exactly what the camera was doing.
Why bother? pytorch360convert has done equirect→perspective for years and does it well, but it gives you no camera to grab and no lens in millimetres - just rectilinear. This is the camera layer. If you're pulling plates out of CC0 HDRIs, matching a real VFX lens, or framing a background before you composite, this is the one you reach for.
How it works
The panorama is mapped to the inside of a unit sphere. Your camera sits inside it and casts rays; wherever a ray hits the sphere is what that pixel sees. Sensor size and focal length pick the ray angle off the optical axis, yaw/pitch/roll orient the camera, and one grid_sample at the end does the actual resample. Since it's a single tensor op, batches come free - feed it video frames and every frame costs the same as a still.
The inputs that actually bite
equirectangular is your IMAGE in (a 2:1 panorama - it warns if the ratio is off). Past that, three groups:
- Lens.
sensoris a stack of real presets, from Full Frame down to Super16 and the iPhone main sensor, plus Custom withsensor_width_custom/sensor_height_custom. Focal length is meaningless without it: 18mm on Super16 is not 18mm on full frame.lens_projectionmatters when you go wide - rectilinear keeps straight lines straight but its math heads to infinity past ~150° FOV, so below about 12mm on full frame it's fisheye time. Stereographic gives the "little planet" look; equisolid is what most real fisheye lenses are (Blender's default); equidistant is the scientific one. - Camera.
yaw,pitch,rollpan, tilt and dutch the shot. Positive pitch looks up - the C4D/Blender/Nuke convention, opposite of some tools.perspective_warpslides the camera along its own Z axis as a fraction of sphere radius, but read the tooltip: it's a warp, not a dolly. No parallax, nothing occludes, and straight lines bow. Great on sky, clouds and foliage; terrible on architecture. There's no sphere-diameter control, because only the offset-to-radius ratio changes the image. - Output.
aspect_preset(16:9, 2.39:1 scope, 9:16 vertical, 4:5 social…) drivesoutput_heightfromoutput_width- the height widget hides when a preset is active.sensor_fitdecides which sensor dimension survives when the output aspect doesn't match it (same idea as Blender).interpolationdefaults to bicubic, which is right.
Outputs
image- the perspective frame. Wire it to Save Image, upscale, inpaint, whatever.camera_info- a STRING with focal length, sensor, projection, FOV, effective sensor, orientation, warp, and frame count. That's the "read exact numbers off the sliders" promise, ready for a text display node.
The preview
Drag to pan, shift-drag to roll, wheel to zoom the lens, double-click to reset orientation. It's a WebGL2 fragment shader, a direct GLSL port of the same projection.py maths, so it renders the fisheye projections and the warp honestly. Needs WebGL2; without it the sliders still work, you just lose the live view. One quirk: the preview box height is derived from your aspect ratio - drag the node taller and it snaps back, because any other height would letterbox or stretch. The panorama is cached server-side after the first run and fetched over HTTP, so the preview survives a page refresh; preview_max_width caps how big that cache gets.
Install
Shared with the pack - ComfyUI Manager (search "Reframe 360") or:
cd ComfyUI/custom_nodes
git clone https://github.com/senyilmaz/ComfyUI-Reframe360
pip install -r ComfyUI-Reframe360/requirements.txt
Restart after. Dependencies are just torch, pillow, requests, numpy - no model downloads. (The README's own clone command has a YOURNAME typo; use the repo above.)
Troubleshooting
- Input isn't 2:1? It warns and renders distorted anyway. Resize or crop first.
- Straight lines are bowing - that's
perspective_warpdoing its thing, or rectilinear pushed below ~12mm full frame. Set warp to 0. - Preview won't load - old browser or WebGL2 disabled; the sliders still work.
- A saved graph fails validation with something like
roll: could not convert 'rectilinear' to float- a workflow serialized widget values by position. New saves store values by name, and anything impossible is repaired to a default on load.
Inputs (16)
| Name | Type | Default | Description |
|---|---|---|---|
| equirectangular | IMAGE | — | |
| sensor | COMBO | Full Frame (36 x 24) | 11 options: Full Frame (36 x 24), Super35 (24.89 x 18.66), ARRI ALEXA 35 (27.99 x 19.22), ARRI ALEXA LF (36.70 x 25.54), RED KOMODO 6K (27.03 x 14.26), APS-C (23.6 x 15.6), +5 |
| sensor_fit | COMBO | auto | 3 options: auto, horizontal, vertical |
| sensor_width_custom | FLOAT | 36.000.1–200 | — |
| sensor_height_custom | FLOAT | 24.000.1–200 | — |
| focal_length | FLOAT | 18.01–300 | Millimetres. Meaningless without the sensor size above. |
| lens_projection | COMBO | rectilinear | Rectilinear keeps straight lines straight but falls apart below ~12mm on full frame. Use a fisheye projection for ultra-wide. |
| yaw | FLOAT | 0.0-180–180 | — |
| pitch | FLOAT | 0.0-90–90 | — |
| roll | FLOAT | 0.0-180–180 | — |
| perspective_warp | FLOAT | 0.000-0.95–0.95 | Slides the camera along its OWN Z axis, as a fraction of the sphere radius. Bends straight lines, gives no parallax. A warp, not a dolly. |
| aspect_preset | COMBO | 16:9 | 8 options: Custom, 16:9, 2.39:1 (scope), 1.85:1 (flat), 4:3, 1:1, +2 |
| output_width | INT | 192016–16384 | — |
| output_height | INT | 108016–16384 | — |
| interpolation | COMBO | bicubic | 3 options: bicubic, bilinear, nearest |
| preview_max_widthopt | INT | 2048256–8192 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| camera_info | STRING | — |