From 1d1b99ac4989ba2a5df143f6c81913ad20f3db0b Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 14 Oct 2025 11:40:16 -0300 Subject: [PATCH] [Added] Arbitrary normalization and a node to input the parameters --- README.md | 2 ++ docs/nodes_img.md | 35 ++++++++++++++++++++++-- src/nodes/nodes_img.py | 60 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2452340..986dacb 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ Currently we just have a few nodes used by other nodes I maintain. - [Normalize Image to ImageNet](docs/nodes_img.md#4-normalize-image-to-imagenet) - [Normalize Image to [-0.5, 0.5]](docs/nodes_img.md#5-normalize-image-to-05-05) - [Normalize Image to [-1, 1]](docs/nodes_img.md#6-normalize-image-to-1-1) + - [Arbitrary Normalize](docs/nodes_img.md#14-arbitrary-normalize) + - [Normalize Parameters](docs/nodes_img.md#15-normalize-parameters) - 📝 [Usage Notes](#-usage-notes) - 📜 [Project History](#-project-history) - ⚖️ [License](#️-license) diff --git a/docs/nodes_img.md b/docs/nodes_img.md index d93fa41..eac7850 100644 --- a/docs/nodes_img.md +++ b/docs/nodes_img.md @@ -37,7 +37,7 @@ - The animated frame is automatically resized to fit the bounding box dimensions before pasting. The pasting logic safely handles cases where the bbox is partially outside the image boundaries. # 3. Face Composite (frame by frame) - - **Display Name:** `"Face Composite (frame by frame)` + - **Display Name:** `Face Composite (frame by frame)` - **Internal Name:** `SET_CompositeFaceFrameByFrame` - **Category:** `image/manipulation` - **Description:** This node is a simplified variant for **1-to-1** compositing. It is perfect for video processing workflows where you need to paste a sequence of processed frames back into the original video sequence at a static location. @@ -55,7 +55,7 @@ # 4. Normalize Image to ImageNet - - **Display Name:** `"Normalize Image to ImageNet` + - **Display Name:** `Normalize Image to ImageNet` - **Internal Name:** `SET_NormalizeToImageNetDataset` - **Category:** `image/normalization` - **Description:** Normalizes an image tensor using the mean and standard deviation of the ImageNet dataset. @@ -255,3 +255,34 @@ - `mask` (`MASK`): The resized mask tensor. - `width` (`INT`): The final width of the output mask. - `height` (`INT`): The final height of the output mask. + + +# 14. Arbitrary Normalize + - **Display Name:** `Arbitrary Normalize` + - **Internal Name:** `SET_NormalizeArbitrary` + - **Category:** `image/normalization` + - **Description:** Normalizes an image tensor using the mean and standard deviation provided in the `parameters` input. + - **Purpose:** Essential for pre-processing images before feeding them into models + - **Inputs:** + - `image` (`IMAGE`): A standard ComfyUI image tensor in the `[0, 1]` range. + - `parameters` (`NORM_PARAMS`): The parameters for the normalization. This is a dict with two keys, `mean` and `std`, which are lists with the R, G, B parameters. + - **Output:** + - `image` (`IMAGE`): The normalized image tensor. The value range will be altered significantly. + - **How it Works:** For each channel, it performs the operation `output = (input - mean) / std`, using the values from NORM_PARAMS + + +# 15. Normalize Parameters + - **Display Name:** `Normalize Parameters` + - **Internal Name:** `SET_NormalizeParameters` + - **Category:** `image/normalization` + - **Description:** Provides the `NORM_PARAMS` for the [Arbitrary Normalize](#14-arbitrary-normalize) node + - **Purpose:** This is a user interface to the [Arbitrary Normalize](#14-arbitrary-normalize) node + - **Inputs:** + - `mean_red` (`FLOAT`): Mean value for the red channel + - `mean_green` (`FLOAT`): Mean value for the green channel + - `mean_blue` (`FLOAT`): Mean value for the blue channel + - `std_red` (`FLOAT`): Standard deviation for the red channel + - `std_green` (`FLOAT`): Standard deviation for the green channel + - `std_blue` (`FLOAT`): Standard deviation for the blue channel + - **Output:** + - `parameters` (`NORM_PARAMS`): The parameters for the normalization. This is a dict with two keys, `mean` and `std`, which are lists with the R, G, B parameters. diff --git a/src/nodes/nodes_img.py b/src/nodes/nodes_img.py index 651fd34..76be881 100644 --- a/src/nodes/nodes_img.py +++ b/src/nodes/nodes_img.py @@ -89,6 +89,12 @@ PAD_TRANS = ("FLOAT", { "display": "number", "tooltip": ("The transparency for the padded area for all modes except `edge_pixel`." "1.0 is fully transparent, 0.0 is fully opaque.")}) +NORM_PARAM = ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.1, + "display": "number"}) def tensor_to_pil(tensor: torch.Tensor) -> Image.Image: @@ -418,7 +424,6 @@ class NormalizeToImageNetDataset(): DESCRIPTION = ("Normalize the image to the ImageNet dataset") UNIQUE_NAME = "SET_NormalizeToImageNetDataset" DISPLAY_NAME = "Normalize Image to ImageNet" - imagenet_normalize = None def normalize(self, image: torch.Tensor): return (TF.normalize(image.permute(0, 3, 1, 2), # BHWC -> BCHW @@ -440,7 +445,6 @@ class NormalizeToRangeMinus05to05(): DESCRIPTION = ("Normalize the image to [-0.5, 0.5]") UNIQUE_NAME = "SET_NormalizeToRangeMinus05to05" DISPLAY_NAME = "Normalize Image to [-0.5, 0.5]" - imagenet_normalize = None def normalize(self, image: torch.Tensor): return (TF.normalize(image.permute(0, 3, 1, 2), # BHWC -> BCHW @@ -462,7 +466,6 @@ class NormalizeToRangeMinus1to1(): DESCRIPTION = ("Normalize the image to [-1, 1]") UNIQUE_NAME = "SET_NormalizeToRangeMinus1to1" DISPLAY_NAME = "Normalize Image to [-1, 1] (i.e. GAN)" - imagenet_normalize = None def normalize(self, image: torch.Tensor): return (TF.normalize(image.permute(0, 3, 1, 2), # BHWC -> BCHW @@ -470,6 +473,57 @@ class NormalizeToRangeMinus1to1(): std=[0.5, 0.5, 0.5]).permute(0, 2, 3, 1),) # BCHW -> BHWC +class NormalizeArbitrary(): + """ + A ComfyUI node to normalize the values to arbitrary mean/std + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "parameters": ("NORM_PARAMS",), + }, + } + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "normalize" + CATEGORY = BASE_CATEGORY + "/" + NORMALIZATION + DESCRIPTION = ("Normalize the image to the provided parameters") + UNIQUE_NAME = "SET_NormalizeArbitrary" + DISPLAY_NAME = "Arbitrary Normalize" + + def normalize(self, image: torch.Tensor, parameters): + return (TF.normalize(image.movedim(-1, 1), # BHWC -> BCHW + mean=parameters["mean"], + std=parameters["std"]).movedim(1, -1),) # BCHW -> BHWC + + +class NormalizeParameters(): + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "mean_red": NORM_PARAM, + "mean_green": NORM_PARAM, + "mean_blue": NORM_PARAM, + "std_red": NORM_PARAM, + "std_green": NORM_PARAM, + "std_blue": NORM_PARAM, + }, + } + RETURN_TYPES = ("NORM_PARAMS",) + RETURN_NAMES = ("parameters",) + FUNCTION = "normalize" + CATEGORY = BASE_CATEGORY + "/" + NORMALIZATION + DESCRIPTION = ("Parameters for the arbitrary normalization") + UNIQUE_NAME = "SET_NormalizeParameters" + DISPLAY_NAME = "Normalize Parameters" + + def normalize(self, mean_red, mean_green, mean_blue, std_red, std_green, std_blue): + return ({"mean": [mean_red, mean_green, mean_blue], "std": [std_red, std_green, std_blue]},) + + class ApplyMaskAFFCE: @classmethod def INPUT_TYPES(cls):