two modes

* histogram, has a clip value 0->1. Default is 0.5.
* auto == linear auto-level (which is equivalent to a histrogram with clip value 1.0)
This commit is contained in:
Alexander G. Morano
2025-09-04 22:26:46 -04:00
parent cb00ef610d
commit 3895344681
6 changed files with 170 additions and 37 deletions
+5
View File
@@ -138,6 +138,11 @@ Nodes that have been migrated:
[Migrated to Jovi_GLSL](https://github.com/Amorano/Jovi_GLSL)
**2025/09/04** @2.1.24:
* `AUTO LEVEL` node
* `HISTOGRAM` node
* new support for cozy_comfy (v3+ comfy node spec)
**2025/08/15** @2.1.23:
* fixed regression in `FLATTEN` node
+63 -1
View File
@@ -4,6 +4,8 @@ import sys
from enum import Enum
from typing import Any
import comfy.model_management
from comfy_api.latest import ComfyExtension, io
from comfy.utils import ProgressBar
from cozy_comfyui import \
@@ -13,6 +15,10 @@ from cozy_comfyui import \
from cozy_comfyui.lexicon import \
Lexicon
from cozy_comfy.node import \
COZY_TYPE_IMAGE as COZY_TYPE_IMAGEv3, \
CozyImageNode as CozyImageNodev3
from cozy_comfyui.node import \
COZY_TYPE_IMAGE, \
CozyImageNode
@@ -20,7 +26,7 @@ from cozy_comfyui.node import \
from cozy_comfyui.image.adjust import \
EnumAdjustBlur, EnumAdjustColor, EnumAdjustEdge, EnumAdjustMorpho, \
image_contrast, image_brightness, image_equalize, image_gamma, \
image_exposure, image_hsv, image_invert, image_pixelate, image_pixelscale, \
image_exposure, image_pixelate, image_pixelscale, \
image_posterize, image_quantize, image_sharpen, image_morphology, \
image_emboss, image_blur, image_edge, image_color
@@ -455,3 +461,59 @@ Sharpen the pixels of an image.
images.append(cv_to_tensor_full(pA))
pbar.update_absolute(idx)
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.inputs.extend([
io.MultiType.Input(
id=Lexicon.IMAGE,
types=COZY_TYPE_IMAGEv3,
display_name=Lexicon.IMAGE,
optional=True,
tooltip=Lexicon.IMAGE[1]
),
io.Float.Input(
id=Lexicon.AMOUNT,
display_name=Lexicon.AMOUNT,
optional=True,
default= 0,
min=0,
max=1,
step=0.01,
tooltip=Lexicon.AMOUNT[1]
),
io.Float.Input(
id=Lexicon.THRESHOLD,
display_name=Lexicon.THRESHOLD,
optional=True,
default= 0,
min=0,
max=1,
step=0.01,
tooltip=Lexicon.THRESHOLD[1]
)
])
return schema
def run(self, **kw) -> RGBAMaskType:
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)
params = list(zip_longest_fill(pA, amount, threshold))
images = []
pbar = ProgressBar(len(params))
for idx, (pA, amount, threshold) in enumerate(params):
pA = channel_solid() if pA is None else tensor_to_cv(pA)
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)
+97 -35
View File
@@ -1,5 +1,7 @@
""" Jovimetrix - Composition """
from enum import Enum
import numpy as np
from comfy.utils import ProgressBar
@@ -21,7 +23,8 @@ from cozy_comfyui.image import \
from cozy_comfyui.image.adjust import \
EnumThreshold, EnumThresholdAdapt, \
image_invert, image_filter, image_threshold
image_histogram2, image_invert, image_filter, image_threshold, \
image_autolevel, image_autolevel_histogram
from cozy_comfyui.image.channel import \
EnumPixelSwizzle, \
@@ -29,6 +32,7 @@ from cozy_comfyui.image.channel import \
from cozy_comfyui.image.compose import \
EnumBlendType, EnumScaleMode, EnumScaleInputMode, EnumInterpolation, \
image_resize, \
image_scalefit, image_split, image_blend, image_matte
from cozy_comfyui.image.convert import \
@@ -43,10 +47,66 @@ 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
@@ -216,6 +276,42 @@ Create masks based on specific color ranges within an image. Specify the color r
pbar.update_absolute(idx)
return image_stack(images)
class HistogramNode(CozyImageNode):
NAME = "HISTOGRAM (JOV)"
CATEGORY = JOV_CATEGORY
DESCRIPTION = """
The Histogram Node generates a histogram representation of the input image, showing the distribution of pixel intensity values across different bins. This visualization is useful for understanding the overall brightness and contrast characteristics of an image. Additionally, the node performs histogram normalization, which adjusts the pixel values to enhance the contrast of the image. Histogram normalization can be helpful for improving the visual quality of images or preparing them for further image processing tasks.
"""
@classmethod
def INPUT_TYPES(cls) -> InputType:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
Lexicon.IMAGE: (COZY_TYPE_IMAGE, {
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"}),
Lexicon.WH: ("VEC2", {
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
"label": ["W", "H"]}),
}
})
return Lexicon._parse(d)
def run(self, **kw) -> RGBAMaskType:
pA = parse_param(kw, Lexicon.IMAGE, EnumConvertType.IMAGE, None)
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, (512, 512), IMAGE_SIZE_MIN)
params = list(zip_longest_fill(pA, wihi))
images = []
pbar = ProgressBar(len(params))
for idx, (pA, wihi) in enumerate(params):
pA = tensor_to_cv(pA) if pA is not None else channel_solid()
hist_img = image_histogram2(pA, bins=256)
width, height = wihi
hist_img = image_resize(hist_img, width, height, EnumInterpolation.NEAREST)
images.append(cv_to_tensor_full(hist_img))
pbar.update_absolute(idx)
return image_stack(images)
class PixelMergeNode(CozyImageNode):
NAME = "PIXEL MERGE (JOV) 🫂"
CATEGORY = JOV_CATEGORY
@@ -433,37 +529,3 @@ Define a range and apply it to an image for segmentation and feature extraction.
images.append(cv_to_tensor_full(pA))
pbar.update_absolute(idx)
return image_stack(images)
'''
class HistogramNode(JOVImageSimple):
NAME = "HISTOGRAM (JOV) 👁‍🗨"
CATEGORY = JOV_CATEGORY
RETURN_TYPES = ("IMAGE", )
RETURN_NAMES = ("IMAGE",)
DESCRIPTION = """
The Histogram Node generates a histogram representation of the input image, showing the distribution of pixel intensity values across different bins. This visualization is useful for understanding the overall brightness and contrast characteristics of an image. Additionally, the node performs histogram normalization, which adjusts the pixel values to enhance the contrast of the image. Histogram normalization can be helpful for improving the visual quality of images or preparing them for further image processing tasks.
"""
@classmethod
def INPUT_TYPES(cls) -> InputType:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
Lexicon.IMAGE": (COZY_TYPE_IMAGE, {
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"}),
}
})
return Lexicon._parse(d)
def run(self, **kw) -> RGBAMaskType:
pA = parse_param(kw, Lexicon.IMAGE", None), EnumConvertType.IMAGE, None)
params = list(zip_longest_fill(pA,))
images = []
pbar = ProgressBar(len(params))
for idx, (pA, ) in enumerate(params):
pA = image_histogram(pA)
pA = image_histogram_normalize(pA)
images.append(cv_to_tensor(pA))
pbar.update_absolute(idx)
return image_stack(images)
'''
+2
View File
@@ -10,6 +10,7 @@
"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",
@@ -26,6 +27,7 @@
"FLATTEN (JOV) \u2b07\ufe0f": "Combine multiple input images into a single image by summing their pixel values",
"GRADIENT MAP (JOV) \ud83c\uddf2\ud83c\uddfa": "Remaps an input image using a gradient lookup table (LUT)",
"GRAPH (JOV) \ud83d\udcc8": "Visualize a series of data points over time",
"HISTOGRAM (JOV)": "The Histogram Node generates a histogram representation of the input image, showing the distribution of pixel intensity values across different bins",
"IMAGE INFO (JOV) \ud83d\udcda": "Exports and Displays immediate information about images",
"LERP (JOV) \ud83d\udd30": "Calculate linear interpolation between two values or vectors based on a blending factor (alpha)",
"OP BINARY (JOV) \ud83c\udf1f": "Execute binary operations like addition, subtraction, multiplication, division, and bitwise operations on input values, supporting various data types and vector sizes",
+2 -1
View File
@@ -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.23"
version = "2.1.24"
license = { file = "LICENSE" }
readme = "README.md"
authors = [{ name = "Alexander G. Morano", email = "amorano@gmail.com" }]
@@ -19,6 +19,7 @@ requires-python = ">=3.10"
dependencies = [
"aenum",
"git+https://github.com/cozy-comfyui/cozy_comfyui@main#egg=cozy_comfyui",
"git+https://github.com/cozy-comfyui/cozy_comfy@main#egg=cozy_comfy",
"matplotlib",
"numpy>=1.25.0",
"opencv-contrib-python",
+1
View File
@@ -1,5 +1,6 @@
aenum
git+https://github.com/cozy-comfyui/cozy_comfyui@main#egg=cozy_comfyui
git+https://github.com/cozy-comfyui/cozy_comfy@main#egg=cozy_comfy
matplotlib
numpy>=1.25.0
opencv-contrib-python