Perspective -> Panorama (Universal)
Stretch a normal video frame into a 360° equirectangular panorama
- images
- panorama_images
- mask
- info
This is the node the whole pack is built around. Feed it a normal perspective image or video frame and it re-projects that content onto a sphere and unwraps it into a 2:1 equirectangular panorama - the flat format VR players and panorama viewers actually consume. If you've ever wanted to take ordinary footage and turn it into something you can look around inside, this is the piece that gets you there. It's a faithful open port of the first step of hybridworkflow's method, the r/StableDiffusion post that kicked off the whole 360° VR-from-regular-video trend in late 2025.
A quick reality check on what it doesn't do: projecting a perspective frame onto a sphere leaves big empty regions above and below - the sky and ground you never saw. Filling those is outpainting, your job with a regular inpainting workflow. This node's second output is specifically there to help with that, which we'll get to.
How it works
Under the hood it's a reverse equirectangular projection. The node builds a grid of spherical coordinates for the output panorama, rotates them by the camera's roll and pitch (ZYX Euler angles), and runs each point through a pinhole camera model using the focal length derived from vfov - unless you override it with an explicit focal_length in pixels. It then samples the source frame at those projected coordinates with OpenCV's remap, which is where the interpolation quality choices kick in. It's plain, well-trodden geometry - no neural network in the conversion itself. The mask is computed once per batch and reused, and if your OpenCV build has CUDA, interpolation can run on the GPU.
The inputs that matter
images- your frame or video batch. It must have an aspect ratio between 1:2 (portrait) and 2:1 (landscape); anything more extreme throws an error.output_resolution- four choices: 3840×1920, 4096×2048, 5760×2880, 7680×3840. 5760×2880 is the default and the sweet spot; 7680×3840 is for final renders with the VRAM to spare, the lower two for quick previews.interpolation-lanczos(default, sharpest, slowest),cubic, orbilinear. This is where aliasing lives: lanczos uses an 8×8 kernel and is the fix for jagged edges, especially near FOV boundaries. The README recommends it and so do I; switch down only if it's genuinely slow.roll,pitch,vfov- the three calibration knobs. If you want the output level and centered, don't hand-tune these - wire them from the pack's Camera Calibration node instead. It estimates all three from a single frame, which is dramatically better than the defaults (which assume a specific source).
The output you shouldn't ignore
panorama- the equirectangular result at your chosen resolution.mask_single- a binary mask (white = valid projected area, black = the empty bands). This is the underrated output: hook it into an inpainting workflow as your mask so the model fills exactly the missing sky/ground instead of repainting your whole panorama. It's the same "project + mask + outpaint" pattern the original method relies on.info- a text string with the resolution and interpolation used.
Installation
cd ComfyUI/custom_nodes
git clone https://github.com/9nate-drake/ComfyUI-PanoTools.git
cd ComfyUI-PanoTools
pip install -r requirements.txt
Restart ComfyUI and find it at Add Node > PanoTools > Perspective to Panorama. Dependencies are just opencv-python, kornia, and matplotlib - torch and numpy come with ComfyUI. Manager support is flagged "coming soon," so clone it. If you use the Camera Calibration node's geocalib_auto method, expect one automatic ~110–220 MB weight download on first run (needs internet).
Common issues
- "ERROR: Input must be between portrait(1:2) and landscape(2:1)" - your image is too extreme in one direction. Resize or crop it into range.
- Panorama looks rotated or off-center - calibration problem, not a projection bug. Run Camera Calibration and wire its roll/pitch/vfov in, or nudge them manually.
- Jagged edges / aliasing - you're on
bilinear. Move tocubicorlanczos, and raise the output resolution if the artifacts are at FOV edges.
Realistic expectations: the projection itself is a solved problem and this node does it cleanly. The quality of your final VR panorama lives or dies on the outpainting step after it - the mask output exists to make that step as easy as possible.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| roll | FLOAT | 0.00-180–180 | — |
| pitch | FLOAT | 0.00-89.9–89.9 | — |
| vfov | FLOAT | 60.001–179 | — |
| output_projection | COMBO | fisheye_1_1 | 2 options: fisheye_1_1, panorama_2_1 |
| auto_canvas | BOOLEAN | false | — |
| canvas_scale | FLOAT | 1.501–4 | — |
| pano_width | INT | 2048256–16384 | — |
| pano_height | INT | 1024256–16384 | — |
| tight_crop | BOOLEAN | true | — |
| crop_margin | FLOAT | 0.100–1 | — |
| crop_mode | COMBO | crop_and_resize | 2 options: crop_and_resize, crop_only |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| panorama_images | IMAGE | — |
| mask | MASK | — |
| info | STRING | — |