Nodes/ComfyUI-CoCoTools_IO/CoCo Z Normalize
ComfyUI Node

CoCo Z Normalize

Taming raw depth ranges so ControlNet can actually read them

By Conor-Collins·Created about a year ago·Updated 5 months ago· 105
CoCo Z Normalize
  • image
  • normalized_depth_image
min_depth0.00
max_depth1.00

Depth maps from a 3D render are liars in a specific way: the raw Z values are scene-linear and can span several orders of magnitude. Your near wall might sit at 0.02, the far hills at 300, and the pixels in between mean nothing until you map them into a sane range. Diffusion models and ControlNet preprocessors expect depth as a 0-1 (usually 8-bit) grayscale, and if you've ever piped raw Z straight into a depth ControlNet you know it produces garbage. CoCo Z Normalize is the tiny utility that rescales that chaos into something usable.

How it works

It's a plain min-max remap with a clamp: (value - min_depth) / (max_depth - min_depth), then everything is clipped into [0, 1]. You supply the two numbers that bracket your actual scene range, and the node does the arithmetic across the whole batch. Two details worth knowing:

  • If the input is single-channel, it replicates the depth to RGB - because the IMAGE type is three-channel and most downstream nodes (and previews) assume it. You get the same depth in every channel, not a colorized map.
  • It's fully batch-aware, so a loaded EXR sequence of depth frames normalizes in one pass.

The inputs are image (IMAGE), min_depth (FLOAT, default 0, ±10000, step 0.01) and max_depth (FLOAT, default 1). The single output is normalized_depth_image (IMAGE). If max_depth <= min_depth it raises a clear error rather than silently doing something dumb.

The knob that matters: your range

Everything hinges on setting min_depth and max_depth to bracket your data. The defaults (0 to 1) are correct only if your depth is already roughly in that range. The pack's own example workflow uses small values like 0.04 and 0.1 for an already-normalized depth EXR - tight ranges work fine when your data is tight. For raw Z from Blender you'll want min_depth at your near plane and max_depth near your far value; get it wrong and you'll just clip the near or far end, which shows up as crushed black or blown white. There's no auto-detect here - that's the price of a 30-line utility, and honestly it's the right design; the node can't know your scene's units.

Where it fits in a workflow

In the depth-estimation world, depth maps do two jobs: ControlNet conditioning (guiding a new generation's spatial structure) and geometry/parallax work. The reason this node exists is the first job. A common piece of advice in the community is that you can't feed an OpenEXR depth pass straight into a ControlNet preprocessor - the values are scene-linear and unbounded, so you must compress them into an 8-bit 0-1 grayscale first. That compression is precisely this node's whole existence.

The typical chain from the pack: Load EXRLoad EXR Layer by Name (grab the Z layer) → CoCo Z Normalize → your ControlNet or depth-based node. You can also slot CoCo Colorspace in before normalization if your depth pass needs a space change first, though depth is usually already linear and just needs the remap.

Installing it

Part of the CoCoTools pack - Manager ("CoCoTools") or:

cd ComfyUI/custom_nodes
git clone https://github.com/Conor-Collins/ComfyUI-CoCoTools_IO
pip install -r ComfyUI-CoCoTools_IO/requirements.txt

It doesn't need OpenImageIO or the EXR stack at all - it just takes an IMAGE tensor - so it's one of the two CoCoTools nodes you can use standalone in any workflow, even if you never touch the EXR loaders.

Troubleshooting

  • Output is all black or all white → your min/max window doesn't bracket the data. Widen it.
  • Depth looks inverted → this node doesn't flip polarity; if your renderer outputs far=white (or the opposite of what your ControlNet expects), handle the inversion separately. Z Normalize just rescales what you give it.
  • Single-channel input into a 3-channel-only socket → most ComfyUI depth loaders hand you a proper IMAGE, so this rarely bites, but if it does, route through MaskToImage first.

It's a dead-simple utility with zero fanfare and zero impressions - and that's fine. It's not supposed to be impressive; it's supposed to be the two minutes you'd otherwise spend writing a Python node so your depth ControlNet doesn't eat raw Z and barf.

CategoryCOCO Tools/Processing

Inputs (3)

NameTypeDefaultDescription
imageIMAGE
min_depthFLOAT0.00-10000–10000
max_depthFLOAT1.00-10000–10000

Outputs (1)

NameTypeDescription
normalized_depth_imageIMAGE