diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 3a24117..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.jpg filter=lfs diff=lfs merge=lfs -text -*.png filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index 62bfdc5..f4c603d 100644 --- a/README.md +++ b/README.md @@ -2,46 +2,31 @@ This is an Extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI), which is the joint research between me and TimothyAlexisVass. For more information, check out the original [Extension](https://github.com/Haoming02/sd-webui-diffusion-cg) for **Automatic1111**. -## Nodes -Some example workflows are included~ +## How to Use +> Example workflows are included~ +- Attach the **Recenter** or **RecenterXL** node between `Empty Latent` and `KSampler` nodes + - Adjust the **strength** and **color** sliders as needed +- Attach the **Normalization** or **NormalizationXL** node between `KSampler` and `VAE Decode` nodes -- **Hook Recenter:** For **SD 1.5**. Hooks the callback to achieve the centering effect. - - Comes with `Effect Strength` slider and `CMYK` color settings -- **Hook Recenter XL:** For **SDXL**. Hooks the callback to achieve the centering effect. - - Comes with `Effect Strength` slider and `Lab` color settings -- **Unhook Recenter** (Optional)**:** Unhook the callback to disable the effects completely. - - If used, put near the end of the workflow -- **Normalization:** For **SD 1.5**. Use before the `VAE Decode` node to achieve the normalization effect. -- **NormalizationXL:** For **SDXL**. Use before the `VAE Decode` node to achieve the normalization effect. - -#### Important: -- Toggle `custom_sampler` if you're using the **SamplerCustom** node. -- In a single workflow, you only need to hook the callback once. The simplest way is to add it between the `Positive Prompt` and the `Sampler`. -- Due to how `ComfyUI` works, if you also add **Unhook Recenter**, the effect may not work sometimes unless you also change the prompt. - -> ComfyUI doesn't go through a node unless it needs to be updated, so if you unhook the callback and the parameters didn't change *(**eg.** you're only iterating throguh seeds)*, then the callback will not be hooked again. Easiest way to solve this is just adding a space to the positive prompt, or just don't unhook the callback. +### Important: +- The **Recenter** is "global." If you want to disable it during later part of the workflow *(**eg.** during `Hires. Fix`)*, +you have to add another **Recenter** node and set its `strength` to `0.0`. ## Samples

SD 1.5
- - -
Off | On
+ + +
Off | On

SDXL
-
Off | On
+
Off | On

## Known Issue -- Doesn't really work with `LCM` Sampler - -
- -##### Checkpoints Used: -- [UHD-23](https://civitai.com/models/22371/uhd-23) -- [Juggernaut XL](https://civitai.com/models/133005/juggernaut-xl) +- Doesn't work with certain Samplers diff --git a/__init__.py b/__init__.py index b67d635..e6f86be 100644 --- a/__init__.py +++ b/__init__.py @@ -1,21 +1,16 @@ from .normalization import Normalization, NormalizationXL -from .recenter import HookCallback, HookCallbackXL, UnhookCallback -# from .tensor_debug import Debug +from .recenter import Recenter, RecenterXL NODE_CLASS_MAPPINGS = { "Normalization": Normalization, "NormalizationXL": NormalizationXL, - "Hook Recenter": HookCallback, - "Hook Recenter XL": HookCallbackXL, - "Unhook Recenter": UnhookCallback, - # "Tensor Debug": Debug, + "Recenter": Recenter, + "Recenter XL": RecenterXL } NODE_DISPLAY_NAME_MAPPINGS = { "Normalization": "Normalization", "NormalizationXL": "NormalizationXL", - "Hook Recenter": "Hook Recenter", - "Hook Recenter XL": "Hook Recenter XL", - "Unhook Recenter": "Unhook Recenter", - # "Tensor Debug": "Tensor Debug", + "Recenter": "Recenter", + "Recenter XL": "RecenterXL" } diff --git a/normalization.py b/normalization.py index 5d8ddb3..c65a0f7 100644 --- a/normalization.py +++ b/normalization.py @@ -1,6 +1,7 @@ DYNAMIC_RANGE = [18, 14, 14, 14] DYNAMIC_RANGE_XL = [20, 16, 16] + def normalize_tensor(x, r): ratio = r / max(abs(float(x.min())), abs(float(x.max()))) x *= max(ratio, 0.99) @@ -12,6 +13,7 @@ def clone_latent(latent): return cloned_latent + class Normalization: @classmethod def INPUT_TYPES(s): diff --git a/recenter.py b/recenter.py index b8fa864..08b9bc3 100644 --- a/recenter.py +++ b/recenter.py @@ -1,20 +1,27 @@ import comfy +rc_strength = 0.0 +LUTs = [] + ORIGINAL_SAMPLE = comfy.sample.sample ORIGINAL_SAMPLE_CUSTOM = comfy.sample.sample_custom - -def hijack(SAMPLE, LUTs:list, strength:float): +def hijack(SAMPLE): def sample_center(*args, **kwargs): original_callback = kwargs['callback'] def hijack_callback(step, x0, x, total_steps): + global rc_strength + global LUTs + + if rc_strength == 0 or len(LUTs) == 0: + return original_callback(step, x0, x, total_steps) batchSize = x.size(0) for b in range(batchSize): for c in range(len(LUTs)): - x[b][c] += (LUTs[c] - x[b][c].mean()) * strength + x[b][c] += (LUTs[c] - x[b][c].mean()) * rc_strength return original_callback(step, x0, x, total_steps) @@ -23,30 +30,16 @@ def hijack(SAMPLE, LUTs:list, strength:float): return sample_center - -class UnhookCallback: - @classmethod - def INPUT_TYPES(s): - return { "required": { "latent": ("LATENT", ) } } - - RETURN_TYPES = ("LATENT", ) - FUNCTION = "unhook" - CATEGORY = "Diffusion CG" - - def unhook(self, latent): - comfy.sample.sample_custom = ORIGINAL_SAMPLE_CUSTOM - comfy.sample.sample = ORIGINAL_SAMPLE - - return (latent,) +comfy.sample.sample = hijack(ORIGINAL_SAMPLE) +comfy.sample.sample_custom = hijack(ORIGINAL_SAMPLE_CUSTOM) -class HookCallback: +class Recenter: @classmethod def INPUT_TYPES(s): return { "required": { - "prompt": ("CONDITIONING",), - "custom_sampler": ("BOOLEAN", {"default": False}), + "latent": ("LATENT",), "strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.1, "round": 0.1, "display": "slider"}), "C": ("FLOAT", {"default": 0.01, "min": -1.00, "max": 1.00, "step": 0.01}), @@ -56,26 +49,25 @@ class HookCallback: } } - RETURN_TYPES = ("CONDITIONING",) + RETURN_TYPES = ("LATENT",) FUNCTION = "hook" - CATEGORY = "Diffusion CG" + CATEGORY = "latent" - def hook(self, prompt, custom_sampler, strength, C, M, Y, K): - if custom_sampler: - comfy.sample.sample_custom = hijack(ORIGINAL_SAMPLE_CUSTOM, [-K, -M, C, Y], strength) - else: - comfy.sample.sample = hijack(ORIGINAL_SAMPLE, [-K, -M, C, Y], strength) + def hook(self, latent, strength, C, M, Y, K): + global rc_strength + rc_strength = strength + global LUTs + LUTs = [-K, -M, C, Y] - return (prompt,) + return (latent,) -class HookCallbackXL: +class RecenterXL: @classmethod def INPUT_TYPES(s): return { "required": { - "prompt": ("CONDITIONING",), - "custom_sampler": ("BOOLEAN", {"default": False}), + "latent": ("LATENT",), "strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.1, "round": 0.1, "display": "slider"}), "L": ("FLOAT", {"default": 0.0, "min": -1.0, "max": 1.0, "step": 0.05}), @@ -84,14 +76,14 @@ class HookCallbackXL: } } - RETURN_TYPES = ("CONDITIONING",) + RETURN_TYPES = ("LATENT",) FUNCTION = "hook" - CATEGORY = "Diffusion CG" + CATEGORY = "latent" - def hook(self, prompt, custom_sampler, strength, L, a, b): - if custom_sampler: - comfy.sample.sample_custom = hijack(ORIGINAL_SAMPLE_CUSTOM, [L, -a, b], strength) - else: - comfy.sample.sample = hijack(ORIGINAL_SAMPLE, [L, -a, b], strength) + def hook(self, latent, strength, L, a, b): + global rc_strength + rc_strength = strength + global LUTs + LUTs = [L, -a, b] - return (prompt,) + return (latent,) diff --git a/samples/1.5_off.jpg b/samples/1.5_off.jpg deleted file mode 100644 index ee379d2..0000000 --- a/samples/1.5_off.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dbbda98d15ded20936bbfee25bb6ec9ea10de7e8762112b06d0478961cb3fa6a -size 292905 diff --git a/samples/15_off.jpg b/samples/15_off.jpg new file mode 100644 index 0000000..daa7e72 Binary files /dev/null and b/samples/15_off.jpg differ diff --git a/samples/xl_off.jpg b/samples/xl_off.jpg index 7cd915c..2097424 100644 Binary files a/samples/xl_off.jpg and b/samples/xl_off.jpg differ diff --git a/tensor_debug.py b/tensor_debug.py deleted file mode 100644 index 2c2a7f8..0000000 --- a/tensor_debug.py +++ /dev/null @@ -1,17 +0,0 @@ -class Debug: - @classmethod - def INPUT_TYPES(s): - return { "required": { "latent": ("LATENT",) } } - - RETURN_TYPES = ("LATENT",) - FUNCTION = "debug" - CATEGORY = "latent" - - def debug(self, latent): - - print('\n') - for c in range(4): - print(f'(min: {latent["samples"][0][c].min()}, max: {latent["samples"][0][c].max()}, mean: {latent["samples"][0][c].mean()})') - print('\n') - - return (latent,) diff --git a/workflows/1.5_on.png b/workflows/1.5_on.png deleted file mode 100644 index 30ffd20..0000000 --- a/workflows/1.5_on.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2cefec2b4672d27c2f505e0615d769378d20048f1f58b2c1af33c3487e116979 -size 1551287 diff --git a/workflows/15_on.png b/workflows/15_on.png new file mode 100644 index 0000000..286b0c6 Binary files /dev/null and b/workflows/15_on.png differ diff --git a/workflows/xl_on.png b/workflows/xl_on.png index f6cebf6..6988e18 100644 Binary files a/workflows/xl_on.png and b/workflows/xl_on.png differ