Files
Amorano-Jovimetrix/sup/image/misc.py
T
Alexander G. Morano ed67ec57c5 further cleaning the image lib split
support for non-alpha comfy load image image (flat alpha)
2024-09-18 14:47:51 -07:00

188 lines
6.6 KiB
Python

"""
Jovimetrix - http://www.github.com/amorano/jovimetrix
Extras Support
"""
import math
from enum import Enum
from typing import Any, Tuple
import cv2
import numpy as np
from numba import jit
from PIL import Image, ImageDraw
from loguru import logger
from Jovimetrix.sup.image import TYPE_IMAGE, TYPE_PIXEL, \
TYPE_iRGB, bgr2image, image2bgr, image_convert, pil2cv
# =============================================================================
# === ENUMERATION ===
# =============================================================================
class EnumProjection(Enum):
NORMAL = 0
POLAR = 5
SPHERICAL = 10
FISHEYE = 15
PERSPECTIVE = 20
class EnumShapes(Enum):
CIRCLE = 0
SQUARE = 1
ELLIPSE = 2
RECTANGLE = 3
POLYGON = 4
class EnumThreshold(Enum):
BINARY = cv2.THRESH_BINARY
TRUNC = cv2.THRESH_TRUNC
TOZERO = cv2.THRESH_TOZERO
class EnumThresholdAdapt(Enum):
ADAPT_NONE = -1
ADAPT_MEAN = cv2.ADAPTIVE_THRESH_MEAN_C
ADAPT_GAUSS = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
# =============================================================================
# === EXPLICIT SHAPE FUNCTIONS ===
# =============================================================================
def shape_ellipse(width: int, height: int, sizeX:float=1., sizeY:float=1.,
fill:TYPE_PIXEL=255, back:TYPE_PIXEL=0) -> Image:
sizeX = max(0.5, sizeX / 2 + 0.5)
sizeY = max(0.5, sizeY / 2 + 0.5)
xy = [(width * (1. - sizeX), height * (1. - sizeY)),(width * sizeX, height * sizeY)]
image = Image.new("RGB", (width, height), back)
ImageDraw.Draw(image).ellipse(xy, fill=fill)
return image
def shape_quad(width: int, height: int, sizeX:float=1., sizeY:float=1.,
fill:TYPE_PIXEL=255, back:TYPE_PIXEL=0) -> Image:
sizeX = max(0.5, sizeX / 2 + 0.5)
sizeY = max(0.5, sizeY / 2 + 0.5)
xy = [(width * (1. - sizeX), height * (1. - sizeY)),(width * sizeX, height * sizeY)]
image = Image.new("RGB", (width, height), back)
ImageDraw.Draw(image).rectangle(xy, fill=fill)
return image
def shape_polygon(width: int, height: int, size: float=1., sides: int=3,
fill:TYPE_PIXEL=255, back:TYPE_PIXEL=0) -> Image:
size = max(0.00001, size)
r = min(width, height) * size * 0.5
xy = (width * 0.5, height * 0.5, r)
image = Image.new("RGB", (width, height), back)
d = ImageDraw.Draw(image)
d.regular_polygon(xy, sides, fill=fill)
return image
def image_gradient(width:int, height:int, color_map:dict=None) -> TYPE_IMAGE:
if color_map is None:
color_map = {0: (0,0,0,255)}
else:
color_map = {np.clip(float(k), 0, 1): [np.clip(int(c), 0, 255) for c in v] for k, v in color_map.items()}
color_map = dict(sorted(color_map.items()))
image = Image.new('RGBA', (width, height))
draw = image.load()
widthf = float(width)
@jit
def gaussian(x, a, b, c, d=0) -> Any:
return a * math.exp(-(x - b)**2 / (2 * c**2)) + d
def pixel(x, spread:int=1) -> TYPE_iRGB:
ws = widthf / (spread * len(color_map))
r = sum([gaussian(x, p[0], k * widthf, ws) for k, p in color_map.items()])
g = sum([gaussian(x, p[1], k * widthf, ws) for k, p in color_map.items()])
b = sum([gaussian(x, p[2], k * widthf, ws) for k, p in color_map.items()])
return min(255, int(r)), min(255, int(g)), min(255, int(b))
for x in range(width):
r, g, b = pixel(x)
for y in range(height):
draw[x, y] = r, g, b
return pil2cv(image)
def image_split(image: TYPE_IMAGE) -> Tuple[TYPE_IMAGE, ...]:
h, w = image.shape[:2]
# Grayscale image
if image.ndim == 2 or image.shape[2] == 1:
r = g = b = image
a = np.full((h, w), 255, dtype=image.dtype)
# BGR image
elif image.shape[2] == 3:
r, g, b = cv2.split(image)
a = np.full((h, w), 255, dtype=image.dtype)
else:
r, g, b, a = cv2.split(image)
return r, g, b, a
def image_stereogram(image: TYPE_IMAGE, depth: TYPE_IMAGE, divisions:int=8,
mix:float=0.33, gamma:float=0.33, shift:float=1.) -> TYPE_IMAGE:
height, width = depth.shape[:2]
out = np.zeros((height, width, 3), dtype=np.uint8)
image = cv2.resize(image, (width, height))
image = image_convert(image, 3)
depth = image_convert(depth, 3)
noise = np.random.randint(0, max(1, int(gamma * 255)), (height, width, 3), dtype=np.uint8)
# noise = cv2.cvtColor(noise, cv2.COLOR_GRAY2BGR)
image = cv2.addWeighted(image, 1. - mix, noise, mix, 0)
pattern_width = width // divisions
# shift -= 1
for y in range(height):
for x in range(width):
if x < pattern_width:
out[y, x] = image[y, x]
else:
# out[y, x] = out[y, x - pattern_width + int(shift * invert)]
offset = depth[y, x][0] // divisions
pos = x - pattern_width + int(shift * offset)
# pos = max(-pattern_width, min(pattern_width, pos))
out[y, x] = out[y, pos]
return out
def image_threshold(image:TYPE_IMAGE, threshold:float=0.5,
mode:EnumThreshold=EnumThreshold.BINARY,
adapt:EnumThresholdAdapt=EnumThresholdAdapt.ADAPT_NONE,
block:int=3, const:float=0.) -> TYPE_IMAGE:
const = max(-100, min(100, const))
block = max(3, block if block % 2 == 1 else block + 1)
image, alpha, cc = image2bgr(image)
if adapt != EnumThresholdAdapt.ADAPT_NONE:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.adaptiveThreshold(gray, 255, adapt.value, cv2.THRESH_BINARY, block, const)
gray = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
# gray = np.stack([gray, gray, gray], axis=-1)
image = cv2.bitwise_and(image, gray)
else:
threshold = int(threshold * 255)
_, image = cv2.threshold(image, threshold, 255, mode.value)
return bgr2image(image, alpha, cc == 1)
# MORPHOLOGY
def morph_edge_detect(image: TYPE_IMAGE,
ksize: int=3,
low: float=0.27,
high:float=0.6) -> TYPE_IMAGE:
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ksize = max(3, ksize)
image = cv2.GaussianBlur(src=image, ksize=(ksize, ksize+2), sigmaX=0.5)
# Perform Canny edge detection
return cv2.Canny(image, int(low * 255), int(high * 255))
def morph_emboss(image: TYPE_IMAGE, amount: float=1., kernel: int=2) -> TYPE_IMAGE:
kernel = max(2, kernel)
kernel = np.array([
[-kernel, -kernel+1, 0],
[-kernel+1, kernel-1, 1],
[kernel-2, kernel-1, 2]
]) * amount
return cv2.filter2D(src=image, ddepth=-1, kernel=kernel)