Palette Optimal Transport Transfer
Optimal transport, no posterization
- image
- target_colors
- IMAGE
Of all the palette nodes in ComfyUI-Color_Transfer, this is the one that sounds most like a math exam and behaves the most like magic. Palette Optimal Transport Transfer doesn't just snap every pixel to the nearest swatch like the plain Palette Transfer does. It moves your image's whole distribution of colors toward the palette's distribution, the way you'd shift a pile of sand so it matches another pile's shape. Same sand, same amount, rearranged to fit a new mold. That's exactly what "optimal transport" means, and it's why the result keeps your image's tonal balance instead of flattening it into bands.
How it works
The pipeline is, step by step:
- Summarize the source. It runs MiniBatchKMeans with 1000 clusters on your image's pixels - a compact stand-in for the full color distribution.
- Compute a transport plan. Using the POT (Python Optimal Transport) library, it solves a regularized optimal-transport problem (Sinkhorn) between your image's cluster distribution and your palette, with a cost matrix measuring how "expensive" it is to turn each source color into each palette color. The plan moves colors proportionally: if your image is 30% blue, roughly 30% of it gets pushed toward blue-ish palette colors, not all of it to whatever single swatch is closest.
- Recolor. Each pixel takes its cluster's transported color, and you can blend that against the original or against a grayscale version.
That proportional behavior is the selling point. Hard nearest-swatch mapping can dump everything into one dominant palette color and leave your other swatches unused; transport actively spends your palette, so you end up with a recolor where all the swatches show up in sensible amounts.
The inputs that matter
- image, target_colors - same as the rest of the family: any
IMAGEplus aCOLOR_LISTfrom ColorPalette. - palette_extension_method (
None/Dense/Edge, defaultNone) - how much the palette gets pre-expanded before transport.Denseinterpolates points between every pair of colors;Edgeuses a Delaunay triangulation to find sensible "edges" between colors and interpolates along those. With a small 4-color palette andNone, the palette distribution is spiky - extension gives transport smoother intermediate colors to work with. - palette_extension_points (2–20, default 5) - how many interpolated points per pair/edge. More points = smoother, heavier.
- blend_mode (
Original/Grayscale) - blend the transport result against your original image, or against a desaturated version.Grayscale+ a low blend ratio is the classic "tint it toward a mood without losing the photo's luminance" move. - blend_ratio (0–1, default 0.5) - how much transport wins. 0 keeps the source untouched; 1 is full recolor.
Practical notes and gotchas
This is the most expensive node in the pack - a 1000-cluster KMeans plus a Sinkhorn solve on every frame. The source has explicit memory-safety fixes (small batches, no reassignment, forced GC on batches), but on a 4K image it'll still chug noticeably. Fine for stills, worth a thought for video batches.
Also, it's the only node that genuinely needs the POT package. The pack's other nodes are fine if POT is missing, but this one imports ot at the top of the module - if ComfyUI's loader complains, install it:
pip install POT
Install
ComfyUI Manager search "Color_Transfer", or:
cd ComfyUI/custom_nodes
git clone https://github.com/45uee/ComfyUI-Color_Transfer
Then restart. No models, no keys, CPU math. If it's your first node from this pack, also note the README's general fix for import errors: pip install scikit-learn. It's a small solo-dev pack, and this is its heaviest, cleverest node - expect the good results to come with a bit of wait.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| target_colors | COLOR_LIST | — | |
| palette_extension_method | COMBO | None | 3 options: Dense, Edge, None |
| palette_extension_points | INT | 52–20 | — |
| blend_mode | COMBO | Original | 2 options: Original, Grayscale |
| blend_ratio | FLOAT | 0.50–1 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |