Nodes/Mickmumpitz-Nodes/Perspective to Pano Warp
ComfyUI Node

Perspective to Pano Warp

Turn one phone photo into a 360° canvas you can outpaint

By mickmumpitz·Created 8 months ago·Updated 17 days ago· 48
Perspective to Pano Warp
  • image
  • control_video
  • control_mask
  • inpaint_mask
  • erp_guide
width1408
height704
length1
h_fov_deg70.0
yaw_deg0
pitch_deg0
mask_feather0
supersample2.0
grid_spacing_deg10
guide_opacity0.55
fill_color#000000

The headline act of Mickmumpitz's panorama set, and the node the whole workflow hangs off. You give it an ordinary rectilinear photo - a phone snap, a screenshot, a render - and it projects it onto a 2:1 equirectangular canvas the way a real 360° camera would have seen it. Straight lines curve toward the edges, angles compress, and everything the camera didn't see stays a hole. That hole is the point: it's the region you're going to outpaint.

Why the warp exists

A panorama pipeline has a chicken-and-egg problem. You want to generate the 360° surroundings of a photo, but diffusion models are trained on flat rectilinear images, not equirect projections. If you just outpaint sideways from the raw photo you get a flat wide image, not a spherical one, and it won't tile when you wrap it. The warp fixes the input side: it places your photo onto the sphere correctly, and hands you the masks a sampler needs to fill the rest. The output even tiles left-to-right, because that's what "equirectangular" means.

How it works

It's a pinhole-to-ERP forward warp done properly in PyTorch. For every pixel of the ERP canvas the node computes a viewing ray (longitude/latitude), rotates that ray by your yaw_deg / pitch_deg, then projects it back into the source image using a focal length derived from h_fov_deg. The source gets area-downsampled to its on-canvas footprint first (supersample, default 2) so the warp resamples near 1:1 - that's what kills the moiré you'd otherwise get. Known pixels keep the photo; unknown pixels get fill_color (black by default).

The inputs that matter

  • h_fov_deg - the horizontal field of view of your source photo. Photos don't store this, so you must supply it. Phone main ~70, wide ~90, ultrawide ~110–120. Too small and the photo sits in a tight window; too big and it smears across half the canvas.
  • width / height - keep width = 2×height for a full pano (defaults 1408×704 are a good start).
  • length - frames in the still batch (N = 1+4k). 1 for a single still; 5 gives a video model a little temporal extent.
  • pitch_deg - camera tilt. If the horizon comes out wrong, this is the first value to change.
  • fill_color - only tints the control_video; the masks are untouched, so an inpainting sampler still regenerates the region. If your model reacts badly to a black init, try #808080.

The outputs and where they go

  • control_video - your photo placed on the sphere (the "plate"). Feeds the generator as a reference/control input.
  • inpaint_mask - white = hole to generate, black = your photo. This goes straight into an inpainting sampler.
  • control_mask - the same info as an RGB image, if your model wants a mask as an image.
  • erp_guide - a technical chart, no photo: lat/lon grid, degree labels, floor/ceiling perspective curves, and a dotted box showing exactly where your photo sits. Feed it to the generator as a pure geometry hint for the 360° layout.

One subtlety worth knowing: mask_feather ramps into the known side only, keeping every true hole fully white. The comment in the source says a ramp there previously let the sampler blend the black init latent back in and left a dark ring - that's the bug this avoids.

Install and gotchas

It's part of Mickmumpitz-Nodes (MIT), and needs no model downloads - the pack's dependencies are just numpy, Pillow and opencv-python. Install via ComfyUI Manager (search "Mickmumpitz") or:

cd ComfyUI/custom_nodes
git clone https://github.com/mickmumpitz/ComfyUI-Mickmumpitz-Nodes.git

Restart ComfyUI and the node lives under Mickmumpitz/Panorama as "Perspective to Pano Warp".

The usual failure is a wrong FOV, which you'll see as the photo either huddled in the middle or stretched past the edges - fix it by typing your camera's real FOV rather than trusting a guess. If the image is tilted or the horizon's off, that's pitch_deg. Everything else is the fun part: feed control_video and inpaint_mask to an outpainting pass, then clean up with the pack's Harmonize Boundary and Seam Roll nodes.

CategoryMickmumpitz/Panorama

Inputs (12)

NameTypeDefaultDescription
imageIMAGE
widthINT140864–8192ERP canvas width. Keep 2:1 (width = 2*height) for a full pano.
heightINT70432–4096
lengthINT11–257Frames in the still batch (N = 1+4k). 1 = single ERP still (fastest); 5 gives a video model a little temporal extent.
h_fov_degFLOAT70.010–170HORIZONTAL field of view of the source photo, in degrees. Photos do not carry this, so it is set here. Too small places the photo too tightly, too large smears it across the canvas. Match your camera: phone main ~70, wide ~90, ultrawide ~110-120.
yaw_degoptFLOAT0-180–180Where on the sphere the photo points. 0 is canvas centre, 180 is the wrap seam.
pitch_degoptFLOAT0-90–90Camera tilt. 0 for a level shot. If the horizon comes out wrong, this is the value to change first.
mask_featheroptINT00–128Feather (px) on the inpaint_mask edge, ramping INTO the known side only. Keep at 0 for models trained on hard edges; a ramp there is content mixed towards the fill colour.
supersampleoptFLOAT2.01–4Anti-alias quality. The source is heavily minified into the ERP footprint; this area-prefilters it to footprint x this factor before a bicubic warp, killing aliasing/moire. 2 is a good default, 3-4 for very high-res sources, 1 = off (old bilinear-ish).
grid_spacing_degoptINT105–90Lat/lon grid spacing (deg) on the 'erp_guide' chart. The guide is a TECHNICAL equirect reference (no photo): lat/lon grid + degree labels, floor/ceiling perspective curves, and a dotted FOV box marking exactly where the image sits. Feed it to a generator as a pure geometry hint for the 360 layout.
guide_opacityoptFLOAT0.550–10 = draw only the lat/lon grid + FOV box; >0 = also draw the floor + ceiling perspective curves (how a ground/ceiling plane distorts into equirect).
fill_coloroptSTRING#000000Colour of the UNKNOWN region on the ERP canvas (everything outside the photo's footprint). Hex '#RRGGBB' / '#RGB', or 'r,g,b' with 0-255 components. Default black. This only tints control_video -- the masks are unchanged, so an inpainting sampler still regenerates the region regardless. Handy when a model reacts badly to black init (try '#808080' mid-grey).

Outputs (4)

NameTypeDescription
control_videoIMAGE
control_maskIMAGE
inpaint_maskMASK
erp_guideIMAGE