Files
2026-03-22 16:05:10 +08:00

345 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ComfyUI-LCS
Training-free color control via the **Latent Color Subspace**, plus **sharpness control** via a discovered sharpness subspace.
> **Note:** This is an unofficial community implementation. For the official code, see [ExplainableML/LCS](https://github.com/ExplainableML/LCS).
Based on ["The Latent Color Subspace"](https://arxiv.org/abs/2603.12261v1) (ICML 2026): color in diffusion model latent patch spaces lives in a **3D subspace** (PCA captures 100% color variance), while the remaining 61 dimensions encode structure and detail orthogonally.
This plugin steers colors directly in the 3D LCS during diffusion sampling — no training, no LoRA, no post-processing.
> [中文版 README](README_zh.md)
## LCS vs Traditional Post-Processing
LCS operates **during** diffusion sampling, not after — this is the key difference from traditional color grading (Photoshop, filters, etc.).
| | Traditional Post-Processing | LCS |
|---|---|---|
| **When** | After VAE decode, in pixel space | During sampling, in latent space |
| **Mechanism** | Color filter on the final image | Modifies 3D color subspace mid-generation |
| **Model awareness** | None — structure already locked | Model adapts to color shifts in subsequent steps |
| **Result** | Colors can look "painted on" | Colors look naturally intended by the model |
For example: to get a warm orange sunset, post-processing tints everything orange (muddying shadows and skin tones), while LCS nudges the color subspace early in sampling so clouds, lighting, and reflections are *coherently* warm.
The core insight: color and structure are **orthogonal** in the latent patch space — you can steer one without disturbing the other.
## Tested Models
| Model | Status |
|-------|--------|
| FLUX | Tested |
| FLUX2.klein | Tested |
| z-image | Tested |
| z-image-turbo | Tested |
| Wan (qwen-image) | Tested |
| LTX2.3 | Tested |
LCS calibrates per-VAE, so it should work with any model using a compatible VAE. Feel free to report results with other models.
## Features
- **Color Steering** — Push colors toward any target color
- **Batch Multi-Color** — Different colors per batch item
- **Tone Adjustment** — Contrast, brightness, saturation, temperature with one-click presets
- **Color Anchor** — Zero-config color drift correction: self-anchor, reference-based, or spatial smoothing with auto mode
- **Sharpness Control** — Sharpen or blur during generation via a discovered sharpness subspace (PC1 explains ~97% variance)
- **Localized Control** — Optional mask for region-specific changes
- **Latent Color Preview** — Visualize color structure without VAE decoding
- **Step Observer** — Per-step color previews for debugging
## Installation
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/facok/ComfyUI-LCS.git
```
Dependencies (usually already present in ComfyUI):
```bash
pip install einops safetensors
```
## Quick Start
### Basic Color Control
```
LCS Load Data → LCS Color Intervene → KSampler
↑
(pick a color)
```
1. **LCS Load Data** — connect your VAE (auto-calibrates on first run)
2. **LCS Color Intervene** — connect MODEL and LCS_DATA, pick a target color
3. Connect the output MODEL to KSampler
### Tone Adjustment
```
LCS Load Data → LCS Tone Adjust → KSampler
```
1. **LCS Load Data** → **LCS Tone Adjust**
2. Select a preset (e.g., "Cinematic") or adjust sliders manually
![3d3c82eb0e89ed1608e40ac7a8cc3408](https://github.com/user-attachments/assets/62868e2d-0275-4801-a9bd-606bfea3ce2f)
![42541357](https://github.com/user-attachments/assets/fe22f09e-98ac-4281-ae40-f58232c7700f)
### Sharpness Control
```
LCS Load Data ──→ LCS Sharpness Calibrate → LCS Sharpness Intervene → KSampler
↑ lcs_data
```
1. **LCS Sharpness Calibrate** — connect VAE (auto-calibrates and caches). Optionally connect `lcs_data` from LCS Load Data to ensure sharpness edits don't affect color.
2. **LCS Sharpness Intervene** — connect MODEL and SHARPNESS_DATA, set strength
- Positive strength → sharper
- Negative strength → blurrier
- 0 → no change
![89814728](https://github.com/user-attachments/assets/62f036e9-0bea-4cc0-9220-af4c2fb8fa76)
### Multi-Color Batch
```
LCS Load Data → LCS Color Batch → KSampler
↓
batch_size → EmptyLatentImage
```
Enter comma-separated hex colors (e.g., `#FF0000,#00FF00,#0000FF`). Each color applies to one batch item.
### Color Anchor (Zero-Config Drift Correction)
```
LCS Load Data → LCS Color Anchor → KSampler
```
1. **LCS Load Data** → **LCS Color Anchor** — connect MODEL and LCS_DATA
2. Set mode to **auto** (default) and leave intensity at default
3. Connect the output MODEL to KSampler
That's it. In `auto` mode, the node automatically selects the correction strategy based on which optional inputs are connected:
| Connected Inputs | Resolved Mode | Behavior |
|---|---|---|
| Nothing | self_anchor | Learns the image's color patterns early on, then prevents sudden color shifts |
| reference_image + vae | reference | Keeps generated colors close to your reference image |
| mask (no reference) | smooth | Smooths out color seams (great for inpainting) |
Intensity is also derived automatically from measured drift — no manual tuning needed.
> **When to use manual mode:** If you want full control, set mode to `smooth`, `reference`, or `self_anchor` explicitly and adjust the `intensity` slider (0–1). Auto mode is designed for zero-config "just works" usage.
## Nodes
### Calibration
| Node | Description |
|------|-------------|
| **LCS Load Data** | Auto-calibrate and cache LCS color data per-VAE. Fingerprints VAE weights for automatic cache management. |
| **LCS Sharpness Calibrate** | Discover sharpness subspace via PCA on blur stimuli. Optionally connect `lcs_data` for color-orthogonal sharpness. |
Calibration runs once per VAE and caches automatically. Subsequent runs load instantly.
### Intervention
| Node | Description |
|------|-------------|
| **LCS Color Intervene** | Steer colors toward a target. Supports Type I (LCS shift), Type II (HSL shift), or interpolated mode. |
| **LCS Color Batch** | Different target colors per batch item. Outputs `batch_size` for EmptyLatentImage. |
| **LCS Tone Adjust** | Contrast, brightness, saturation, temperature. Preset dropdown with real-time slider sync. |
| **LCS Color Anchor** | Correct color drift during sampling. Auto mode infers strategy and intensity from connected inputs. |
| **LCS Sharpness Intervene** | Control sharpness during generation. Positive = sharper, negative = blurrier. |
### Observation
| Node | Description |
|------|-------------|
| **LCS Preview Colors** | Decode latent colors to RGB preview without VAE decoding. |
| **LCS Step Observer** | Save per-step color preview PNGs to ComfyUI temp directory. |
## Intervention Modes
| Mode | Description | Best For |
|------|-------------|----------|
| **interpolated** (default) | Blends Type I and Type II using sigma | General use |
| **type_i** | Direct translation in 3D LCS space | Strong global color shifts |
| **type_ii** | Per-patch HSL interpolation via bicone geometry | Precise local color control |
## Key Parameters
### Color Intervention
- **strength** (0.0–2.0): Intervention intensity. 1.0 = full, 0.0 = none.
- **start_step / end_step**: Step range for intervention. Paper optimal: steps 8–10 of 50.
- **mask**: Optional. Downsampled to patch grid for localized control.
### Sharpness Intervention
- **strength** (-5.0–5.0): Positive = sharper, negative = blurrier, 0 = no change.
- **start_step / end_step**: Step range (default 5–15).
- **mask**: Optional. Localized sharpness control.
> **Tip for distilled models**: Step-distilled models (e.g., z-image-turbo) use far fewer steps, so intervention should start earlier — even from step 0.
### Color Anchor
Sometimes diffusion models produce unexpected color shifts during sampling — a blue sky suddenly turns purple, or inpainting leaves visible color seams. The Color Anchor node fixes these problems by monitoring and correcting colors as the image is being generated.
**Modes:**
| Mode | What it does | When to use |
|------|-------------|----------|
| **auto** (default) | Looks at what you connected and picks the best strategy for you | Just want it to work, no config needed |
| **self_anchor** | Watches how colors evolve in early steps, then prevents sudden color jumps in later steps | General color stability, no reference needed |
| **reference** | Keeps the generated image's colors close to a reference image you provide | "Make it look like this photo's color palette" |
| **smooth** | Smooths out abrupt color boundaries between regions | Fixing visible seams after inpainting |
**How auto mode picks for you:**
1. **Which strategy?** Based on what you plugged in:
- Connected a reference image + VAE → uses `reference`
- Connected a mask (but no reference) → uses `smooth`
- Connected nothing extra → uses `self_anchor`
2. **How strong?** The node measures how much color drift is actually happening, then sets the correction strength accordingly. Big drift → stronger fix. Small drift → gentle touch. The range is 0.15–0.6, so it never over-corrects or does nothing.
**What happens during sampling:**
The node runs at every sampling step but doesn't always intervene. It automatically figures out which steps are safe to correct:
1. **Early steps** (image is mostly noise) — Too early to fix colors without creating artifacts. Skipped. In self_anchor mode, the node uses these steps to *learn* the image's color patterns.
2. **Middle steps** (image is taking shape) — The sweet spot. The node applies corrections here, ramping smoothly in and out to avoid sudden changes.
3. **Late steps** (fine details) — Corrections would disturb fine detail. Skipped.
Only colors are modified — structure, texture, and detail are never touched.
**Parameters:**
- **mode**: `auto`, `smooth`, `reference`, or `self_anchor`
- **intensity** (0.0–1.0): How strong the correction is. In `auto` mode this is determined automatically. Set to 0 to disable the node entirely.
- **vae** (optional): Needed for `reference` mode to encode the reference image
- **reference_image** (optional): The image whose colors you want to match
- **mask** (optional): Only correct colors inside the masked area
## Tone Presets
Select a preset — sliders update in real-time. Tweak after selecting for fine-tuning. Select **Custom** to set values manually.
| Preset | Contrast | Brightness | Saturation | Temperature |
|--------|----------|------------|------------|-------------|
| Base | 1.0 | 0.0 | 1.0 | 0.0 |
| Cinematic | 1.20 | -0.05 | 0.90 | 0.05 |
| HDR | 1.40 | 0.0 | 1.20 | 0.0 |
| Vivid | 1.10 | 0.0 | 1.50 | 0.0 |
| Dramatic | 1.50 | -0.10 | 0.85 | 0.0 |
| Low Key | 1.30 | -0.20 | 0.80 | 0.0 |
| High Key | 0.80 | 0.20 | 0.90 | 0.0 |
| Warm | 1.05 | 0.03 | 1.10 | 0.30 |
| Cool | 1.05 | 0.0 | 1.05 | -0.30 |
| Desaturated | 1.0 | 0.0 | 0.40 | 0.0 |
## How It Works
### Color (LCS)
1. **Project** — Convert denoised prediction to 64D patch space, project onto 3D LCS basis
2. **Decompose** — Separate 3D color coordinates from the 61D structural residual
3. **Normalize** — Transform to reference timestep (t=50) using learned alpha/beta statistics
4. **Manipulate** — Shift colors, adjust tone, or apply other transformations in 3D LCS
5. **Reconstruct** — Denormalize, add back the preserved 61D residual, convert to latent space
The 61D residual (structure, texture, detail) is never modified — only the 3D color subspace is touched.
### Sharpness
Sharpness lives in a separate subspace orthogonal to color:
1. **Calibrate** — Generate grayscale noise images at multiple blur levels, VAE-encode, PCA on color-removed patch vectors. PC1 captures ~97% of sharpness variance.
2. **Intervene** — Add `strength * pc1_direction` to each patch. Since pc1_direction is orthogonal to color (calibrated with LCS removal) and DC-free (per-vector zero-mean before PCA), this modifies only spatial frequency content without affecting color or brightness.
### Color Anchor
The Color Anchor stabilizes colors without pushing them toward a specific target — it prevents drift from what the model is already generating:
1. **Decide when to act** — The node checks each sampling step: is the image still mostly noise (too early), taking shape (good time to correct), or nearly finished (too late)? It only corrects during the safe middle window.
2. **Learn the color pattern** (self_anchor) — During early noisy steps, the node watches how colors relate to their neighbors and builds a running average of these relationships. This is more reliable than tracking absolute colors, which shift naturally as the image forms.
3. **Measure drift** — On the first correction step, the node measures how much the colors have actually drifted (varies by mode: step-to-step jumps, distance from reference, or spatial roughness). This sets the correction strength in auto mode.
4. **Apply gentle corrections** — Corrections ramp smoothly in and out (no sudden jumps). Each mode corrects differently: self_anchor fixes patches that deviate from learned patterns, reference pulls toward the reference image's colors, smooth blurs out sharp color boundaries.
5. **Preserve everything else** — As with all LCS operations, only the 3D color coordinates change. Structure, texture, and detail are untouched.
## File Structure
```
ComfyUI-LCS/
├── __init__.py # Entry point (V3 + V2 compat)
├── requirements.txt
├── core/
│ ├── adaptive.py # Adaptive scheduling (phases, envelopes, drift estimation)
│ ├── bilateral.py # Bilateral filter for LCS color smoothing
│ ├── calibration.py # PCA calibration pipeline (color)
│ ├── color_space.py # Bicone LCS ↔ HSL mapping
│ ├── defaults.py # Alpha/beta tables from paper
│ ├── lcs_data.py # LCSData dataclass
│ ├── patchify.py # Patch ↔ latent conversion
│ ├── relationships.py # Local color relationship analysis & anomaly detection
│ ├── sampling.py # Shared constants & step utilities
│ ├── sharpness.py # Sharpness subspace calibration
│ └── timestep.py # Sigma/timestep utilities
├── nodes/
│ ├── anchor.py # LCSColorAnchor (adaptive color drift correction)
│ ├── calibrate.py # LCSLoadData (auto-calibrate + cache)
│ ├── intervene.py # LCSColorIntervene, LCSColorBatch, LCSToneAdjust
│ ├── observe.py # LCSPreviewColors, LCSStepObserver
│ └── sharpen.py # LCSSharpnessCalibrate, LCSSharpnessIntervene
├── data/ # Cached calibration files
└── web/js/
└── tone_preset.js # Frontend preset sync
```
## Changelog
### 2026-03-21
- **Color Anchor: auto mode** — New `auto` mode that infers correction strategy (self_anchor / reference / smooth) from connected inputs and derives intensity from measured drift. Zero-config usage.
- **Color Anchor: adaptive scheduling** — Phase assignment (observe/correct/skip) and strength envelope are derived from the sigma schedule at runtime.
### 2026-03-20
- **Sharpness Control** — New sharpness subspace discovered via PCA on blur stimuli. `LCS Sharpness Calibrate` + `LCS Sharpness Intervene` nodes. PC1 explains ~97% variance, orthogonal to color.
- **Color-orthogonal sharpness** — Optional `lcs_data` input removes color component during sharpness calibration, preventing color shift.
### 2026-03-19
- **Video VAE support (Wan)** — Handle 5D video latents in patchify/unpatchify. Per-image VAE encoding fallback for video VAEs.
- **LTXV compatibility** — Pad odd spatial dims in patchify, handle 3D tensors, skip gracefully for incompatible formats.
- **FLUX2 support** — Auto-detect 128-channel latents in unpatchify.
- **Universal latent format** — Use model's `latent_format` for space conversion instead of hardcoded FLUX constants.
### 2026-03-18
- **Tone Adjust** — `LCS Tone Adjust` node with contrast, brightness, saturation, temperature sliders. 10 presets with frontend real-time sync.
- **Color temperature** — Warm/cool shift along LCS blue-yellow axis.
- **Bicone HSL geometry** — Correct Type II intervention via bicone LCS-to-HSL mapping.
### 2026-03-17
- **Initial release** — Color steering (Type I + Type II + interpolated), batch multi-color, localized mask control, latent color preview, step observer. Per-VAE auto-calibration with caching.
## Citation
Official repository: [ExplainableML/LCS](https://github.com/ExplainableML/LCS)
```bibtex
@article{pach2026latentcolorsubspace,
title={The Latent Color Subspace: Emergent Order in High-Dimensional Chaos},
author={Mateusz Pach and Jessica Bader and Quentin Bouniot and Serge Belongie and Zeynep Akata},
journal={arxiv},
year={2026}
}
```
## Acknowledgments
Thanks to Mateusz Pach, Jessica Bader, Quentin Bouniot, Serge Belongie, and Zeynep Akata for their research making training-free color control possible.
## License
MIT