ComfyUI Node

LatLong Float Processor

Rotate an HDR panorama without clipping it to mush

By cedarconnor·Created about a year ago·Updated about 15 hours ago· 2
LatLong Float Processor
  • image
  • IMAGE
operation
yaw0.00
pitch0.00
roll0.00
fov90.00
width2048
height1024
interpolation

ComfyUI's image tensors are, for almost every practical purpose, 0–1 sRGB-encoded data. That's fine for a diffusion model, which was trained on exactly that, and it's wrong for an HDRI environment map, where the interesting information - the sun disc, the bright window - lives five or six stops above 1.0 and some of the values are negative.

Skyboxes are where this bites. There's a real 360 HDRI EXR LoRA lineage for exactly this job (Z-Image on one end, Blender, Unity and Nuke on the other), and the usual way people break it is rotating the panorama through a node that clamps to [0,1] on the way out. Everything above white flattens, the highlights die, and you only notice when the render is lit wrong.

Float Processor exists to not do that. The author's own description is blunt about the contract: it preserves finite scene-linear values, including negatives and values above one, and preview tone mapping is a separate node. Five operations, same geometry as the rest of the pack, no clamp at the end.

How it works

Each frame is converted to float32 and handed to one of the pack's solvers. The operation field picks which, and it also changes what width and height mean - this is the part worth reading twice:

  • rotate - yaw/pitch/roll, and width/height are ignored; the source dimensions are retained. CPU filtered and tiled.
  • perspective - a rectilinear view of width×height at fov, same camera basis as LatLong Extract Projection.
  • to_cubemap - width is the per-face size, not the atlas width. height is unused.
  • from_cubemap - width×height are the output panorama dimensions.
  • resize - both used, obviously, with the note that the pack's resize can keep a 2:1 panorama aspect.

interpolation offers lanczos, bicubic, bilinear and nearest. On this path the high-order filters are the CPU ones, and the README is explicit that CPU filters preserve floating values - which is why the default is Lanczos rather than bilinear. You're choosing CPU by picking a filter, and paying for it in time: validation on the author's hardware puts a 16384×8192 CPU rotation at about 75 seconds.

One IMAGE output. Values go through untouched, so a negative pixel comes out negative and a 12.0 highlight comes out as 12.0.

The part nobody tells you

Preserving float in the graph is only half the job, because what you save it with decides whether the HDR survives. The stock SaveImage writes display-referred images; if that's your terminal node, the float work was for nothing. You need a saver that actually writes float data out - some EXR-capable node from the VFX corner of the ecosystem (the OpenImageIO-based packs are the usual answer) or an NPZ dump you process outside ComfyUI.

Same story at the other boundary: the moment an HDR panorama goes through a diffusion model, it's 0–1 sRGB again, because that's what VAEs speak. So the honest shape of this node's job is geometry on linear data - rotating, converting and resizing an environment map without destroying the stops - not routing HDR through a sampler. If your pipeline is scene-linear end to end, the scene-linear section of the post-processing doc is the right mental model; it's a compositor world (OCIO, ACES, EXR) that mostly doesn't live in r/StableDiffusion, so expect to assemble it yourself.

For the other direction - actually looking at your HDR values in ComfyUI - see LatLong Tone Map Preview, which is the intended partner to this node.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/cedarconnor/comfyui-LatLong
cd comfyui-LatLong
python -m pip install -r requirements.txt

Manager: search "ComfyUI LatLong". ComfyUI's Python (3.10+), dependencies numpy, opencv-python, scipy, torch and Pillow, no model files. Restart, then look under LatLong/HDR.

Common issues

The input validator is strict and the error is worth knowing: it wants a non-empty B,H,W,C tensor with 1, 3 or 4 channels, and it rejects NaN or infinity outright. That's not pedantry - infinity propagates into every resampled neighbour and turns a frame into a black hole of NaNs. If you feed it a tone-mapped preview by mistake you'll get plausible-looking output that's subtly wrong, because you clipped before the geometry instead of after.

Two more: fov is horizontal and bounded to 1–179, so 180 is not a valid extraction. And the tiling bounds the geometry buffers, not the tensors - a 16K panorama is 1.5 GiB per frame in float32, and this node holds input and output, so 3 GiB before anything downstream sees it.

CategoryLatLong/HDR

Inputs (9)

NameTypeDefaultDescription
imageIMAGE
operationCOMBO5 options: rotate, perspective, to_cubemap, from_cubemap, resize
yawFLOAT0.00
pitchFLOAT0.00
rollFLOAT0.00
fovFLOAT90.001–179
widthINT20481–32768
heightINT10241–16384
interpolationCOMBO4 options: lanczos, bicubic, bilinear, nearest

Outputs (1)

NameTypeDescription
IMAGEIMAGE