Nodes/ComfyUI CV/cv2.warpPolar
ComfyUI Node

cv2.warpPolar

Unwrap a circle into a straight line (and why that's useful)

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.warpPolar
  • src
  • dsize
  • center
  • result
◄maxRadius0.0000►
◄flagsINTER_LINEAR►

Two things make this node worth knowing. The first is aesthetic: unwrap a spinning object and its rotation becomes a straight horizontal slide, which is a look you can't get any other cheap way. The second is mathematical, and it's the reason polar and log-polar warps exist at all in computer vision: transform the image and the problem transforms with it. Rotating an object about the centre becomes a translation in polar space, and scale becomes a translation in log-polar space. Translation is the one thing cross-correlation finds trivially - the pack even ships cv2.phaseCorrelate - so the classic "find rotation and scale between these two images" pipeline is: unwrap both, then look for a shift.

How the mapping works

warpPolar computes dst(ρ, φ) = src(x, y) with the origin at center. The scaling constants are defined straight from the destination size, which is the part people get wrong:

Kangle = dsize.height / 2π
Klin   = dsize.width  / maxRadius      (linear polar)
Klog   = dsize.width  / log_e(maxRadius)  (semilog, with WARP_POLAR_LOG)

So rows are angle and columns are radius. A dsize of (maxRadius, 360) gives you one row per degree of rotation, which is the sheet you want if you're going to scroll along rotation. Set maxRadius too small and you crop the circle you cared about; too large and you spend most of the canvas on nothing.

Inputs

  • src - IMAGE, MASK, NPARRAY, or LATENT (it's on the pack's latent-safe list, so a latent comes back as a latent, frame 0, values untouched).
  • dsize - the output canvas as one CV_TUPLE (w, h). Do not leave it at (0, 0). Unlike warpAffine, where zero means "same as the source", this function has nothing sensible to infer - you get a nonsense-sized output instead of an error. Give it real numbers.
  • center - the transformation centre, as a CV_TUPLE point (x, y). CV Split Point / CV Tuple author those values; for a full-circle unwrap it's the centre of your circle, not the centre of the frame.
  • maxRadius - the radius of the bounding circle, in pixels. It sets both the extent and the inverse magnitude scale.
  • flags - interpolation (INTER_LINEAR, INTER_CUBIC, INTER_LANCZOS4…) plus one of the polar modes and optionally WARP_INVERSE_MAP. The tooltip is blunt about the mode: WARP_POLAR_LOG switches to semilog polar (off = linear), and WARP_INVERSE_MAP "maps back from (semi)log-polar to cartesian" - that's your inverse transform, and it's how you put an effect back onto the original geometry.

Output: one value echoing src's type.

Two implementation notes worth having in your head: the function can't operate in place (irrelevant here - the node always returns a new array), and it's implemented on top of remap, so it's not free. It is per-frame batch safe, so an IMAGE batch gets looped frame by frame and re-stacked.

Practical recipes

  • Circular fisheye / 360 ring unwrap: center at the circle's centre, maxRadius = the circle's pixel radius, dsize = (maxRadius, 720) for half-degree rows, INTER_LINEAR | WARP_POLAR_LINEAR.
  • "Spinning thing" effect: the same unwrap, then animate a vertical offset in the source (or just crop-scroll the result). Rotation reads as motion.
  • Rotation/scale matching: linear polar on both images then cv2.phaseCorrelate for rotation; log-polar for rotation and scale.
  • Putting it back: WARP_POLAR_LOG | WARP_INVERSE_MAP (or linear) returns you to cartesian, and you're back to a normal image for the rest of the graph.

Installing it

Part of ComfyUI CV. Manager → search ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
# restart ComfyUI

Python ≥3.12 and a recent V3-API ComfyUI. The pack is curated against OpenCV 5.0.0.93; other versions may behave differently.

What goes wrong

  • A tiny or garbage-shaped output. You left dsize at (0, 0). Fix it first, every time.
  • The circle isn't straight. Your center is off by a few pixels - half of a polar unwrap's quality is finding the true centre, and there's no automatic way to do it.
  • It looks like a smear. maxRadius is bigger than the actual circle, so you're unwrapping empty space (or, worse, reading a different ring of the image). Crop or measure first.
  • Inverse looks wrong. Remember WARP_INVERSE_MAP has to be combined with a polar mode bit, not used alone; the flags render as one toggle per bit in the UI.
  • Blocking the whole run. Polar remaps on a 4K frame take a moment; there's no cancel mid-call for this wrapper, so try it on a downscaled preview before pointing it at a batch of 200 frames.
Categoryimage/CV/low-level/cv2 W

Inputs (5)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3Source image. The image output(s) echo this input's format. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.
dsizeCV_TUPLE0,0The destination image size (see description for valid options). One value with 2 components (w, h) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
centerCV_TUPLE0,0The transformation center. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
maxRadiusFLOAT0.0000-1e+38–1e+38The radius of the bounding circle to transform. It determines the inverse magnitude scale parameter too.
flagsSTRINGINTER_LINEARA combination of interpolation methods, #InterpolationFlags + #WarpPolarMode. - Add #WARP_POLAR_LINEAR to select linear polar mapping (default) - Add #WARP_POLAR_LOG to select semilog polar mapping - Add #WARP_INVERSE_MAP for reverse mapping. cv2.warpPolar flags: one of INTER_LINEAR, INTER_NEAREST, INTER_CUBIC, INTER_LANCZOS4 plus any of WARP_POLAR_LOG, WARP_INVERSE_MAP, WARP_FILL_OUTLIERS, pipe-joined (e.g. "INTER_LINEAR | WARP_POLAR_LOG"). In the UI this renders as a dropdown with one toggle per flag.

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.