diff --git a/README.md b/README.md index 5b20608..dd1f3ac 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,8 @@ Nodes that have been migrated: [Migrated to Jovi_GLSL](https://github.com/Amorano/Jovi_GLSL) -**2025/09/04** @2.1.24: -* `AUTO LEVEL` node +**2025/09/04** @2.1.25: +* Auto-level for `LEVEL` node * `HISTOGRAM` node * new support for cozy_comfy (v3+ comfy node spec) diff --git a/core/adjust.py b/core/adjust.py index 0b53696..9460262 100644 --- a/core/adjust.py +++ b/core/adjust.py @@ -3,6 +3,7 @@ import sys from enum import Enum from typing import Any +from typing_extensions import override import comfy.model_management from comfy_api.latest import ComfyExtension, io @@ -28,7 +29,8 @@ from cozy_comfyui.image.adjust import \ image_contrast, image_brightness, image_equalize, image_gamma, \ image_exposure, image_pixelate, image_pixelscale, \ image_posterize, image_quantize, image_sharpen, image_morphology, \ - image_emboss, image_blur, image_edge, image_color + image_emboss, image_blur, image_edge, image_color, \ + image_autolevel, image_autolevel_histogram from cozy_comfyui.image.channel import \ channel_solid @@ -52,6 +54,11 @@ JOV_CATEGORY = "ADJUST" # === ENUMERATION === # ============================================================================== +class EnumAutoLevel(Enum): + MANUAL = 10 + AUTO = 20 + HISTOGRAM = 30 + class EnumAdjustLight(Enum): EXPOSURE = 10 GAMMA = 20 @@ -229,7 +236,8 @@ class AdjustLevelNode(CozyImageNode): NAME = "ADJUST: LEVELS (JOV)" CATEGORY = JOV_CATEGORY DESCRIPTION = """ - +Manual or automatic adjust image levels so that the darkest pixel becomes black +and the brightest pixel becomes white, enhancing overall contrast. """ @classmethod @@ -243,7 +251,14 @@ class AdjustLevelNode(CozyImageNode): "label": ["LOW", "MID", "HIGH"]}), Lexicon.RANGE: ("VEC2", { "default": (0, 1), "mij": 0, "maj": 1, "step": 0.01, - "label": ["IN", "OUT"]}) + "label": ["IN", "OUT"]}), + Lexicon.MODE: (EnumAutoLevel._member_names_, { + "default": EnumAutoLevel.MANUAL.name, + "tooltip": "Autolevel linearly or with Histogram bin values, per channel" + }), + "clip": ("FLOAT", { + "default": 0.5, "min": 0, "max": 1.0, "step": 0.01 + }) } }) return Lexicon._parse(d) @@ -252,18 +267,29 @@ class AdjustLevelNode(CozyImageNode): pA = parse_param(kw, Lexicon.IMAGE, EnumConvertType.IMAGE, None) LMH = parse_param(kw, Lexicon.LMH, EnumConvertType.VEC3, (0,0.5,1)) inout = parse_param(kw, Lexicon.RANGE, EnumConvertType.VEC2, (0,1)) - params = list(zip_longest_fill(pA, LMH, inout)) + mode = parse_param(kw, Lexicon.MODE, EnumAutoLevel, EnumAutoLevel.AUTO.name) + clip = parse_param(kw, "clip", EnumConvertType.FLOAT, 0.5, 0, 1) + params = list(zip_longest_fill(pA, LMH, inout, mode, clip)) images = [] pbar = ProgressBar(len(params)) - for idx, (pA, LMH, inout) in enumerate(params): + for idx, (pA, LMH, inout, mode, clip) in enumerate(params): pA = channel_solid() if pA is None else tensor_to_cv(pA) ''' h, s, v = hsv img_new = image_hsv(img_new, h, s, v) ''' - low, mid, high = LMH - start, end = inout - pA = image_levels(pA, low, mid, high, start, end) + match mode: + case EnumAutoLevel.MANUAL: + low, mid, high = LMH + start, end = inout + pA = image_levels(pA, low, mid, high, start, end) + + case EnumAutoLevel.AUTO: + pA = image_autolevel(pA) + + case EnumAutoLevel.HISTOGRAM: + pA = image_autolevel_histogram(pA, clip) + images.append(cv_to_tensor_full(pA)) pbar.update_absolute(idx) return image_stack(images) @@ -463,15 +489,13 @@ Sharpen the pixels of an image. return image_stack(images) class AdjustSharpenNodev3(CozyImageNodev3): - NAME = "ADJUST: SHARPEN (JOV)" - CATEGORY = JOV_CATEGORY - DESCRIPTION = """ -Sharpen the pixels of an image. -""" @classmethod def define_schema(cls, **kwarg) -> io.Schema: schema = super(**kwarg).define_schema() - # schema. + schema.display_name = "ADJUST: SHARPEN (JOV)" + schema.category = JOV_CATEGORY + schema.description = "Sharpen the pixels of an image." + schema.inputs.extend([ io.MultiType.Input( id=Lexicon.IMAGE[0], @@ -504,7 +528,8 @@ Sharpen the pixels of an image. ]) return schema - def run(self, **kw) -> RGBAMaskType: + @classmethod + def execute(self, *arg, **kw) -> io.NodeOutput: pA = parse_param(kw, Lexicon.IMAGE, EnumConvertType.IMAGE, None) amount = parse_param(kw, Lexicon.AMOUNT, EnumConvertType.FLOAT, 0) threshold = parse_param(kw, Lexicon.THRESHOLD, EnumConvertType.FLOAT, 0) @@ -516,4 +541,14 @@ Sharpen the pixels of an image. pA = image_sharpen(pA, amount / 2., threshold=threshold / 25.5) images.append(cv_to_tensor_full(pA)) pbar.update_absolute(idx) - return image_stack(images) + return io.NodeOutput(image_stack(images)) + +class AdjustExtension(ComfyExtension): + @override + async def get_node_list(self) -> list[type[io.ComfyNode]]: + return [ + AdjustSharpenNodev3 + ] + +async def comfy_entrypoint() -> AdjustExtension: + return AdjustExtension() \ No newline at end of file diff --git a/core/compose.py b/core/compose.py index 810a4a8..dc56472 100644 --- a/core/compose.py +++ b/core/compose.py @@ -1,7 +1,5 @@ """ Jovimetrix - Composition """ -from enum import Enum - import numpy as np from comfy.utils import ProgressBar @@ -23,8 +21,7 @@ from cozy_comfyui.image import \ from cozy_comfyui.image.adjust import \ EnumThreshold, EnumThresholdAdapt, \ - image_histogram2, image_invert, image_filter, image_threshold, \ - image_autolevel, image_autolevel_histogram + image_histogram2, image_invert, image_filter, image_threshold from cozy_comfyui.image.channel import \ EnumPixelSwizzle, \ @@ -47,66 +44,10 @@ from cozy_comfyui.image.misc import \ JOV_CATEGORY = "COMPOSE" -# ============================================================================== -# === ENUMERATION === -# ============================================================================== - -class EnumAutoLevel(Enum): - AUTO = 10 - HISTOGRAM = 20 - # ============================================================================== # === CLASS === # ============================================================================== -class AutoLevelNode(CozyImageNode): - NAME = "AUTO LEVEL (JOV)" - CATEGORY = JOV_CATEGORY - DESCRIPTION = """ -Automatically adjusts image levels so that the darkest pixel becomes black -and the brightest pixel becomes white, enhancing overall contrast. -""" - - @classmethod - def INPUT_TYPES(cls) -> InputType: - d = super().INPUT_TYPES() - d = deep_merge(d, { - "required": { - Lexicon.IMAGE: (COZY_TYPE_IMAGE, { - "tooltip": "Pixel Data (RGBA, RGB, or Grayscale)" - }), - Lexicon.MODE: (EnumAutoLevel._member_names_, { - "default": EnumAutoLevel.AUTO.name, - "tooltip": "Autolevel linearly or with Histogram bin values, per channel" - }), - "clip": ("FLOAT", { - "default": 0.5, "min": 0, "max": 1.0, "step": 0.01 - }) - } - }) - return Lexicon._parse(d) - - def run(self, **kw) -> RGBAMaskType: - pA = parse_param(kw, Lexicon.IMAGE, EnumConvertType.IMAGE, None) - mode = parse_param(kw, Lexicon.MODE, EnumAutoLevel, EnumAutoLevel.AUTO.name) - clip = parse_param(kw, "clip", EnumConvertType.FLOAT, 0.5, 0, 1) - - params = list(zip_longest_fill(pA, mode, clip)) - images = [] - pbar = ProgressBar(len(params)) - for idx, (pA, mode, clip) in enumerate(params): - img = tensor_to_cv(pA) - match mode: - case EnumAutoLevel.AUTO: - leveled = image_autolevel(img) - - case EnumAutoLevel.HISTOGRAM: - leveled = image_autolevel_histogram(img, clip) - - images.append(cv_to_tensor_full(leveled)) - pbar.update_absolute(idx) - return image_stack(images) - class BlendNode(CozyImageNode): NAME = "BLEND (JOV) ⚗️" CATEGORY = JOV_CATEGORY diff --git a/node_list.json b/node_list.json index a445364..edd2215 100644 --- a/node_list.json +++ b/node_list.json @@ -3,14 +3,13 @@ "ADJUST: COLOR (JOV)": "Enhance and modify images with various blur effects", "ADJUST: EDGE (JOV)": "Enhanced edge detection", "ADJUST: EMBOSS (JOV)": "Emboss boss mode", - "ADJUST: LEVELS (JOV)": "", + "ADJUST: LEVELS (JOV)": "Manual or automatic adjust image levels so that the darkest pixel becomes black\nand the brightest pixel becomes white, enhancing overall contrast", "ADJUST: LIGHT (JOV)": "Tonal adjustments", "ADJUST: MORPHOLOGY (JOV)": "Operations based on the image shape", "ADJUST: PIXEL (JOV)": "Pixel-level transformations", "ADJUST: SHARPEN (JOV)": "Sharpen the pixels of an image", "AKASHIC (JOV) \ud83d\udcd3": "Visualize data", "ARRAY (JOV) \ud83d\udcda": "Processes a batch of data based on the selected mode", - "AUTO LEVEL (JOV)": "Automatically adjusts image levels so that the darkest pixel becomes black\nand the brightest pixel becomes white, enhancing overall contrast", "BATCH TO LIST (JOV)": "Convert a batch of values into a pure python list of values", "BIT SPLIT (JOV) \u2b44": "Split an input into separate bits", "BLEND (JOV) \u2697\ufe0f": "Combine two input images using various blending modes, such as normal, screen, multiply, overlay, etc", diff --git a/pyproject.toml b/pyproject.toml index 45868c1..c86cac6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "jovimetrix" description = "Animation via tick. Parameter manipulation with wave generator. Unary and Binary math support. Value convert int/float/bool, VectorN and Image, Mask types. Shape mask generator. Stack images, do channel ops, split, merge and randomize arrays and batches. Load images & video from anywhere. Dynamic bus routing. Save output anywhere! Flatten, crop, transform; check colorblindness or linear interpolate values." -version = "2.1.24" +version = "2.1.25" license = { file = "LICENSE" } readme = "README.md" authors = [{ name = "Alexander G. Morano", email = "amorano@gmail.com" }]