From 75d58b822bdff2107776d658ce4455ef9a0e6df8 Mon Sep 17 00:00:00 2001 From: "Alexander G. Morano" Date: Tue, 18 Feb 2025 18:38:31 -0500 Subject: [PATCH] better typehints faster timeout for webcamera promptserver should not be wrapped in exception --- __init__.py | 153 ++++++++++++++++++++---------------------- core/calc.py | 40 +++++------ core/compose.py | 36 +++++----- core/create.py | 13 ++-- core/create_glsl.py | 14 ++-- core/device_midi.py | 12 ++-- core/device_stream.py | 8 +-- core/utility/batch.py | 14 ++-- core/utility/info.py | 6 +- core/utility/io.py | 14 ++-- sup/image/__init__.py | 21 +----- sup/image/adjust.py | 10 +-- sup/image/color.py | 13 +++- sup/stream.py | 10 +-- 14 files changed, 174 insertions(+), 190 deletions(-) diff --git a/__init__.py b/__init__.py index 94caba8..b709fa4 100644 --- a/__init__.py +++ b/__init__.py @@ -369,7 +369,7 @@ class Lexicon(metaclass=LexiconMeta): ZOOM = 'πŸ”Ž', "ZOOM" @classmethod - def _parse(cls, node: dict) -> dict: + def _parse(cls, node: dict) -> Dict[str, str]: for cat, entry in node.items(): if cat not in ['optional', 'required']: continue @@ -415,7 +415,7 @@ class JOVBaseNode: return True @classmethod - def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=False) -> dict: + def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=False) -> Dict[str, str]: data = { "optional": {}, "required": {}, @@ -685,7 +685,7 @@ def get_node_info(node_data: dict) -> Dict[str, Any]: data[".md"] = md return data -def deep_merge(d1: dict, d2: dict) -> dict: +def deep_merge(d1: dict, d2: dict) -> Dict[str, str]: """ Deep merge multiple dictionaries recursively. @@ -734,12 +734,12 @@ class ComfyAPIMessage: @classmethod def poll(cls, ident, period=0.01, timeout=3) -> Any: - _t = time.monotonic() + _t = time.perf_counter() if isinstance(ident, (set, list, tuple, )): ident = ident[0] sid = str(ident) logger.debug(f'sid {sid} -- {cls.MESSAGE}') - while not (sid in cls.MESSAGE) and time.monotonic() - _t < timeout: + while not (sid in cls.MESSAGE) and time.perf_counter() - _t < timeout: time.sleep(period) if not (sid in cls.MESSAGE): @@ -748,95 +748,90 @@ class ComfyAPIMessage: dat = cls.MESSAGE.pop(sid) return dat -def comfy_message(ident:str, route:str, data:dict) -> None: +def comfy_send_message(ident:str, route:str, data:dict) -> None: data['id'] = ident PromptServer.instance.send_sync(route, data) -try: +@PromptServer.instance.routes.get("/jovimetrix") +async def jovimetrix_home(request) -> Any: + data = template_load('home.html') + return web.Response(text=data.template, content_type='text/html') - @PromptServer.instance.routes.get("/jovimetrix") - async def jovimetrix_home(request) -> Any: - data = template_load('home.html') - return web.Response(text=data.template, content_type='text/html') +@PromptServer.instance.routes.get("/jovimetrix/message") +async def jovimetrix_message(request) -> Any: + return web.json_response(ComfyAPIMessage.MESSAGE) - @PromptServer.instance.routes.get("/jovimetrix/message") - async def jovimetrix_message(request) -> Any: - return web.json_response(ComfyAPIMessage.MESSAGE) +@PromptServer.instance.routes.post("/jovimetrix/message") +async def jovimetrix_message_post(request) -> Any: + json_data = await request.json() + logger.info(json_data) + if (did := json_data.get("id")) is not None: + ComfyAPIMessage.MESSAGE[str(did)] = json_data + return web.json_response(json_data) + return web.json_response({}) - @PromptServer.instance.routes.post("/jovimetrix/message") - async def jovimetrix_message_post(request) -> Any: - json_data = await request.json() - logger.info(json_data) - if (did := json_data.get("id")) is not None: - ComfyAPIMessage.MESSAGE[str(did)] = json_data - return web.json_response(json_data) - return web.json_response({}) +@PromptServer.instance.routes.get("/jovimetrix/config") +async def jovimetrix_config(request) -> Any: + global JOV_CONFIG, JOV_CONFIG_FILE + if len(JOV_CONFIG) == 0: + JOV_CONFIG = configLoad(JOV_CONFIG_FILE) + return web.json_response(JOV_CONFIG) - @PromptServer.instance.routes.get("/jovimetrix/config") - async def jovimetrix_config(request) -> Any: - global JOV_CONFIG, JOV_CONFIG_FILE - if len(JOV_CONFIG) == 0: - JOV_CONFIG = configLoad(JOV_CONFIG_FILE) - return web.json_response(JOV_CONFIG) +async def object_info(node_class: str, scheme:str, host: str) -> Any: + global COMFYUI_OBJ_DATA + if (info := COMFYUI_OBJ_DATA.get(node_class, None)) is None: + # look up via the route... + url = f"{scheme}://{host}/object_info/{node_class}" - async def object_info(node_class: str, scheme:str, host: str) -> Any: - global COMFYUI_OBJ_DATA - if (info := COMFYUI_OBJ_DATA.get(node_class, None)) is None: - # look up via the route... - url = f"{scheme}://{host}/object_info/{node_class}" - - # Make an asynchronous HTTP request using aiohttp.ClientSession - async with ClientSession() as session: - try: - async with session.get(url) as response: - if response.status == 200: - info = await response.json() - if (data := info.get(node_class, None)) is not None: - info = get_node_info(data) - else: - info = {'.html': f"No data for {node_class}"} - COMFYUI_OBJ_DATA[node_class] = info + # Make an asynchronous HTTP request using aiohttp.ClientSession + async with ClientSession() as session: + try: + async with session.get(url) as response: + if response.status == 200: + info = await response.json() + if (data := info.get(node_class, None)) is not None: + info = get_node_info(data) else: - info = {'.html': f"Failed to get docs {node_class}, status: {response.status}"} - logger.error(info) - except Exception as e: - logger.error(f"Failed to get docs {node_class}") - logger.exception(e) - info = {'.html': f"Failed to get docs {node_class}\n{e}"} + info = {'.html': f"No data for {node_class}"} + COMFYUI_OBJ_DATA[node_class] = info + else: + info = {'.html': f"Failed to get docs {node_class}, status: {response.status}"} + logger.error(info) + except Exception as e: + logger.error(f"Failed to get docs {node_class}") + logger.exception(e) + info = {'.html': f"Failed to get docs {node_class}\n{e}"} - return info + return info - @PromptServer.instance.routes.get("/jovimetrix/doc") - async def jovimetrix_doc(request) -> Any: +@PromptServer.instance.routes.get("/jovimetrix/doc") +async def jovimetrix_doc(request) -> Any: - for node_class in NODE_CLASS_MAPPINGS.keys(): - if COMFYUI_OBJ_DATA.get(node_class, None) is None: - COMFYUI_OBJ_DATA[node_class] = await object_info(node_class, request.scheme, request.host) - - node = NODE_DISPLAY_NAME_MAPPINGS[node_class] - fname = node.split(" (JOV)")[0] - path = Path(JOV_INTERNAL_DOC.replace("{name}", fname)) - path.mkdir(parents=True, exist_ok=True) - - if JOV_INTERNAL: - if (md := COMFYUI_OBJ_DATA[node_class].get('.md', None)) is not None: - with open(str(path / f"{fname}.md"), "w", encoding='utf-8') as f: - f.write(md) - - with open(str(path / f"{fname}.html"), "w", encoding='utf-8') as f: - f.write(COMFYUI_OBJ_DATA[node_class]['.html']) - - return web.json_response(COMFYUI_OBJ_DATA) - - @PromptServer.instance.routes.get("/jovimetrix/doc/{node}") - async def jovimetrix_doc_node_comfy(request) -> Any: - node_class = request.match_info.get('node') + for node_class in NODE_CLASS_MAPPINGS.keys(): if COMFYUI_OBJ_DATA.get(node_class, None) is None: COMFYUI_OBJ_DATA[node_class] = await object_info(node_class, request.scheme, request.host) - return web.Response(text=COMFYUI_OBJ_DATA[node_class]['.html'], content_type='text/html') -except Exception as e: - logger.error(e) + node = NODE_DISPLAY_NAME_MAPPINGS[node_class] + fname = node.split(" (JOV)")[0] + path = Path(JOV_INTERNAL_DOC.replace("{name}", fname)) + path.mkdir(parents=True, exist_ok=True) + + if JOV_INTERNAL: + if (md := COMFYUI_OBJ_DATA[node_class].get('.md', None)) is not None: + with open(str(path / f"{fname}.md"), "w", encoding='utf-8') as f: + f.write(md) + + with open(str(path / f"{fname}.html"), "w", encoding='utf-8') as f: + f.write(COMFYUI_OBJ_DATA[node_class]['.html']) + + return web.json_response(COMFYUI_OBJ_DATA) + +@PromptServer.instance.routes.get("/jovimetrix/doc/{node}") +async def jovimetrix_doc_node_comfy(request) -> Any: + node_class = request.match_info.get('node') + if COMFYUI_OBJ_DATA.get(node_class, None) is None: + COMFYUI_OBJ_DATA[node_class] = await object_info(node_class, request.scheme, request.host) + return web.Response(text=COMFYUI_OBJ_DATA[node_class]['.html'], content_type='text/html') # ============================================================================== # === SUPPORT === diff --git a/core/calc.py b/core/calc.py index 0cb3201..6fe9278 100644 --- a/core/calc.py +++ b/core/calc.py @@ -7,7 +7,7 @@ import sys import math import random from enum import Enum -from typing import Any, Tuple +from typing import Any, Dict, Tuple from collections import Counter import torch @@ -19,7 +19,7 @@ from comfy.utils import ProgressBar from .. import JOV_TYPE_ANY, JOV_TYPE_FULL, JOV_TYPE_NUMBER, JOV_TYPE_VECTOR, \ Lexicon, JOVBaseNode, \ - comfy_message, deep_merge, parse_reset + comfy_send_message, deep_merge, parse_reset from ..sup.util import EnumConvertType, EnumSwizzle, \ parse_dynamic, parse_param, parse_value, vector_swap, zip_longest_fill @@ -206,7 +206,7 @@ Split an input into separate bits. `BOOL`, `INT` and `FLOAT` use their numbers, image. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -234,7 +234,7 @@ Perform single function operations like absolute value, mean, median, mode, magn """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -336,7 +336,7 @@ Execute binary operations like addition, subtraction, multiplication, division, """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: names_convert = EnumConvertType._member_names_[:10] d = super().INPUT_TYPES() d = deep_merge(d, { @@ -478,7 +478,7 @@ Evaluates two inputs (A and B) with a specified comparison operators and optiona """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -600,7 +600,7 @@ Additionally, you can specify the easing function (EASE) and the desired output """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() names_convert = EnumConvertType._member_names_[:10] d = deep_merge(d, { @@ -685,7 +685,7 @@ Manipulate strings through filtering """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -750,7 +750,7 @@ Swap components between two vectors based on specified swizzle patterns and valu """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() names_convert = EnumConvertType._member_names_[3:10] d = deep_merge(d, { @@ -805,7 +805,7 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -880,7 +880,7 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I pbar.update_absolute(idx) if batch < 2: - comfy_message(ident, "jovi-tick", {"i": self.__frame}) + comfy_send_message(ident, "jovi-tick", {"i": self.__frame}) return (results.frame, results.lin, results.fixed, results.trigger, results.batch,) class ValueNode(JOVBaseNode): @@ -896,7 +896,7 @@ Supplies raw or default values for various data types, supporting vector input w UPDATE = False @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() typ = EnumConvertType._member_names_ @@ -1012,7 +1012,7 @@ Produce waveforms like sine, square, or sawtooth with adjustable frequency, ampl """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1066,7 +1066,7 @@ Outputs a VEC2 or VEC2INT. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1104,7 +1104,7 @@ Outputs a VEC2 or VEC2INT. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1142,7 +1142,7 @@ Outputs a VEC3 or VEC3INT. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1183,7 +1183,7 @@ Outputs a VEC3 or VEC3INT. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1224,7 +1224,7 @@ Outputs a VEC4 or VEC4INT. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1268,7 +1268,7 @@ Outputs a VEC4 or VEC4INT. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1309,7 +1309,7 @@ class ParameterNode(JOVBaseNode): """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/compose.py b/core/compose.py index 00d16d3..12c009e 100644 --- a/core/compose.py +++ b/core/compose.py @@ -4,7 +4,7 @@ Composition """ from enum import Enum -from typing import Any, List, Tuple +from typing import Any, Dict, List, Tuple import cv2 import torch @@ -78,7 +78,7 @@ Enhance and modify images with various effects such as blurring, sharpening, col """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -226,7 +226,7 @@ Combine two input images using various blending modes, such as normal, screen, m """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -315,7 +315,7 @@ Simulate color blindness effects on images. You can select various types of colo """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -352,7 +352,7 @@ Adjust the color scheme of one image to match another with the Color Match Node. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -440,7 +440,7 @@ The top-k colors ordered from most->least used as a strip, tonal palette and 3D """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -498,7 +498,7 @@ Generate a color harmony based on the selected scheme. Supported schemes include """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -537,7 +537,7 @@ Extract a portion of an input image or resize it. It supports various cropping m """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -601,7 +601,7 @@ Create masks based on specific color ranges within an image. Specify the color r """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -646,7 +646,7 @@ Combine multiple input images into a single image by summing their pixel values. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -689,7 +689,7 @@ Remaps an input image using a gradient lookup table (LUT). The gradient image wi """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -741,7 +741,7 @@ Combines individual color channels (red, green, blue) along with an optional mas """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -823,7 +823,7 @@ Takes an input image and splits it into its individual color channels (red, gree """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -851,7 +851,7 @@ Swap pixel values between two input images based on specified channel swizzle op """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -916,7 +916,7 @@ Merge multiple input images into a single composite image by stacking them along """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -960,7 +960,7 @@ Define a range and apply it to an image for segmentation and feature extraction. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1003,7 +1003,7 @@ Apply various geometric transformations to images, including translation, rotati """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -1101,7 +1101,7 @@ The Histogram Node generates a histogram representation of the input image, show """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/create.py b/core/create.py index 905ef5f..d8d3e4a 100644 --- a/core/create.py +++ b/core/create.py @@ -3,13 +3,12 @@ Jovimetrix - http://www.github.com/amorano/jovimetrix Creation """ -from typing import Tuple +from typing import Dict, Tuple import torch import numpy as np from PIL import ImageFont from skimage.filters import gaussian -from loguru import logger from comfy.utils import ProgressBar @@ -50,7 +49,7 @@ Generate a constant image or mask of a specified size and color. It can be used """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -97,7 +96,7 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -168,7 +167,7 @@ Generates false perception 3D images from 2D input. Set tile divisions, noise, g """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -214,7 +213,7 @@ class StereoscopicNode(JOVBaseNode): Simulates depth perception in images by generating stereoscopic views. It accepts an optional input image for color matte. Adjust baseline and focal length for customized depth effects. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -252,7 +251,7 @@ Generates images containing text based on parameters such as font, size, alignme """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/create_glsl.py b/core/create_glsl.py index d64d0ec..d07a075 100644 --- a/core/create_glsl.py +++ b/core/create_glsl.py @@ -5,7 +5,7 @@ Creation import sys from pathlib import Path -from typing import Any, Tuple +from typing import Any, Dict, Tuple import torch from loguru import logger @@ -19,7 +19,7 @@ from comfy.utils import ProgressBar from .. import JOV_TYPE_IMAGE, \ Lexicon, JOVImageNode, \ - comfy_message, deep_merge + comfy_send_message, deep_merge from ..sup.util import EnumConvertType, \ parse_param, parse_value @@ -86,7 +86,7 @@ class GLSLNodeBase(JOVImageNode): CATEGORY = f"JOVIMETRIX πŸ”ΊπŸŸ©πŸ”΅/GLSL" @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -122,7 +122,7 @@ class GLSLNodeBase(JOVImageNode): self.__glsl.vertex = getattr(self, 'VERTEX', kw.pop(Lexicon.PROG_VERT, None)) self.__glsl.fragment = getattr(self, 'FRAGMENT', kw.pop(Lexicon.PROG_FRAG, None)) except CompileException as e: - comfy_message(ident, "jovi-glsl-error", {"id": ident, "e": str(e)}) + comfy_send_message(ident, "jovi-glsl-error", {"id": ident, "e": str(e)}) logger.error(self.NAME) logger.error(e) return @@ -169,7 +169,7 @@ class GLSLNodeBase(JOVImageNode): img = image_scalefit(img, w, h, mode, sample) images.append(cv2tensor_full(img, matte)) self.__delta += step - comfy_message(ident, "jovi-glsl-time", {"id": ident, "t": self.__delta}) + comfy_send_message(ident, "jovi-glsl-time", {"id": ident, "t": self.__delta}) pbar.update_absolute(idx) return [torch.stack(i) for i in zip(*images)] @@ -181,7 +181,7 @@ Execute custom GLSL (OpenGL Shading Language) fragment shaders to generate image """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() opts = d.get('optional', {}) opts.update({ @@ -203,7 +203,7 @@ class GLSLNodeDynamic(GLSLNodeBase): PARAM = None @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: original_params = super().INPUT_TYPES() opts = original_params.get('optional', {}) opts.update({ diff --git a/core/device_midi.py b/core/device_midi.py index cb25a6d..5880e8c 100644 --- a/core/device_midi.py +++ b/core/device_midi.py @@ -7,12 +7,10 @@ Device -- MIDI type 2 (asynchronous): each track is independent of the others """ -from typing import Tuple +from typing import Dict, Tuple from math import isclose from queue import Queue -from loguru import logger - from comfy.utils import ProgressBar from .. import JOVBaseNode, Lexicon, deep_merge @@ -50,7 +48,7 @@ Processes MIDI messages received from an external MIDI controller or device. It """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -83,7 +81,7 @@ Captures MIDI messages from an external MIDI device or controller. It monitors M CHANGED = False @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -154,7 +152,7 @@ Provides advanced filtering capabilities for MIDI messages based on various crit """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -259,7 +257,7 @@ Filter MIDI messages based on various criteria, including MIDI mode (such as not """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/device_stream.py b/core/device_stream.py index 59a3c1b..591613f 100644 --- a/core/device_stream.py +++ b/core/device_stream.py @@ -6,7 +6,7 @@ Device -- WEBCAM, REMOTE URLS, SPOUT import sys import time import uuid -from typing import Tuple +from typing import Dict, Tuple from enum import Enum import cv2 @@ -76,7 +76,7 @@ Capture frames from various sources such as URLs, cameras, monitors, windows, or """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() if cls.CAMERAS is None: @@ -297,7 +297,7 @@ Sends frames to a specified route, typically for live streaming or recording pur """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -368,7 +368,7 @@ Sends frames to a specified Spout receiver application for real-time video shari """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/utility/batch.py b/core/utility/batch.py index 1466d9f..6686b49 100644 --- a/core/utility/batch.py +++ b/core/utility/batch.py @@ -11,7 +11,7 @@ import random from enum import Enum from pathlib import Path from itertools import zip_longest -from typing import Any, List, Literal, Tuple +from typing import Any, Dict, List, Literal, Tuple import torch import numpy as np @@ -22,7 +22,7 @@ from comfy.utils import ProgressBar from nodes import interrupt_processing from ... import JOV_TYPE_ANY, ROOT, Lexicon, JOVBaseNode, deep_merge, \ - comfy_message, parse_reset + comfy_send_message, parse_reset from ...sup.util import EnumConvertType, parse_dynamic, parse_param @@ -70,7 +70,7 @@ Processes a batch of data based on the selected mode, such as merging, picking, """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -239,7 +239,7 @@ class QueueBaseNode(JOVBaseNode): return float('nan') @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -373,7 +373,7 @@ class QueueBaseNode(JOVBaseNode): # make sure we have more to process if are a single fire queue stop = parse_param(kw, Lexicon.STOP, EnumConvertType.BOOLEAN, False)[0] if stop and self.__index >= self.__len: - comfy_message(ident, "jovi-queue-done", self.status) + comfy_send_message(ident, "jovi-queue-done", self.status) interrupt_processing() return self.__previous, self.__q, self.__current, self.__index_last+1, self.__len @@ -430,7 +430,7 @@ class QueueBaseNode(JOVBaseNode): self.__index += 1 self.__previous = data - comfy_message(ident, "jovi-queue-ping", self.status) + comfy_send_message(ident, "jovi-queue-ping", self.status) if stop and batched: interrupt_processing() return data, self.__q, self.__current, self.__index, self.__len, self.__index == self.__index_last or batched @@ -479,7 +479,7 @@ Manage a queue of specific items: media files. Supports various image and video """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/utility/info.py b/core/utility/info.py index 0751388..25952c8 100644 --- a/core/utility/info.py +++ b/core/utility/info.py @@ -5,7 +5,7 @@ Utility import io import json -from typing import Any, Tuple +from typing import Any, Dict, Tuple import torch import numpy as np @@ -137,7 +137,7 @@ Visualize a series of data points over time. It accepts a dynamic number of valu """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -218,7 +218,7 @@ Exports and Displays immediate information about images. """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { diff --git a/core/utility/io.py b/core/utility/io.py index c6ab7c2..d490b82 100644 --- a/core/utility/io.py +++ b/core/utility/io.py @@ -7,7 +7,7 @@ import os import json from uuid import uuid4 from pathlib import Path -from typing import Any, Tuple +from typing import Any, Dict, Tuple import torch import numpy as np @@ -22,7 +22,7 @@ from nodes import interrupt_processing from ... import JOV_TYPE_ANY, JOV_TYPE_IMAGE, \ Lexicon, JOVBaseNode, ComfyAPIMessage, TimedOutException, \ - comfy_message, deep_merge + comfy_send_message, deep_merge from ...sup.util import EnumConvertType, path_next, parse_param, \ zip_longest_fill @@ -71,7 +71,7 @@ Introduce pauses in the workflow that accept an optional input to pass through a """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -94,7 +94,7 @@ Introduce pauses in the workflow that accept an optional input to pass through a if delay < 0: delay = JOV_DELAY_MAX if delay > JOV_DELAY_MIN: - comfy_message(ident, "jovi-delay-user", {"id": ident, "timeout": delay}) + comfy_send_message(ident, "jovi-delay-user", {"id": ident, "timeout": delay}) # enable = parse_param(kw, Lexicon.ENABLE, EnumConvertType.BOOLEAN, True) step = 1 @@ -125,7 +125,7 @@ Responsible for saving images or animations to disk. It supports various output """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() d = deep_merge(d, { "optional": { @@ -235,7 +235,7 @@ Routes the input data from the optional input ports to the output port, preservi """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES() e = { "optional": { @@ -263,7 +263,7 @@ Save the output image along with its metadata to the specified path. Supports sa """ @classmethod - def INPUT_TYPES(cls) -> dict: + def INPUT_TYPES(cls) -> Dict[str, str]: d = super().INPUT_TYPES(True, True) d = deep_merge(d, { "optional": { diff --git a/sup/image/__init__.py b/sup/image/__init__.py index 08b04b5..e9ea9d5 100644 --- a/sup/image/__init__.py +++ b/sup/image/__init__.py @@ -6,12 +6,11 @@ Copyright 2023 Alexander Morano (Joviex) """ -import io -from io import BytesIO import math import base64 import requests from enum import Enum +from io import BytesIO from typing import List, Tuple, Union import cv2 @@ -19,8 +18,6 @@ import torch import numpy as np from PIL import Image, ImageOps -from loguru import logger - # ============================================================================== # === GLOBAL === # ============================================================================== @@ -54,11 +51,6 @@ TYPE_VECTOR = Union[TYPE_IMAGE, TYPE_PIXEL] # === ENUMERATION === # ============================================================================== -class EnumGrayscaleCrunch(Enum): - LOW = 0 - HIGH = 1 - MEAN = 2 - class EnumImageType(Enum): GRAYSCALE = 0 RGB = 10 @@ -66,10 +58,6 @@ class EnumImageType(Enum): BGR = 30 BGRA = 40 -class EnumIntFloat(Enum): - FLOAT = 0 - INT = 1 - # ============================================================================== # === CONVERSION === # ============================================================================== @@ -92,13 +80,13 @@ def b64_2_tensor(base64str: str) -> torch.Tensor: def b64_2_pil(base64_string): prefix, base64_data = base64_string.split(",", 1) image_data = base64.b64decode(base64_data) - image_stream = io.BytesIO(image_data) + image_stream = BytesIO(image_data) return Image.open(image_stream) def b64_2_cv(base64_string) -> TYPE_IMAGE: _, data = base64_string.split(",", 1) data = base64.b64decode(data) - data = io.BytesIO(data) + data = BytesIO(data) data = Image.open(data) data = np.array(data) return cv2.cvtColor(data, cv2.COLOR_RGB2BGR) @@ -386,7 +374,6 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]: raise ValueError(f"{url} could not be loaded.") img = image_normalize(img) - # logger.debug(f"load image {url}: {img.ndim} {img.shape}") if img.ndim == 3: if img.shape[2] == 4: img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) @@ -396,7 +383,6 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]: img = np.expand_dims(img, -1) except Exception: - logger.debug(f"load image fallback to PIL {url}") try: img = Image.open(url) img = ImageOps.exif_transpose(img) @@ -404,7 +390,6 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]: if img.dtype != np.uint8: img = np.clip(np.array(img * 255), 0, 255).astype(dtype=np.uint8) except Exception as e: - # logger.error(str(e)) raise Exception(f"Error loading image: {e}") if img is None: diff --git a/sup/image/adjust.py b/sup/image/adjust.py index c35e11a..9b6f63d 100644 --- a/sup/image/adjust.py +++ b/sup/image/adjust.py @@ -10,16 +10,16 @@ import cv2 import torch import numpy as np -from loguru import logger - -from . import TYPE_IMAGE, TYPE_PIXEL, TYPE_fCOORD2D, \ - EnumImageType, \ +from . import \ + TYPE_IMAGE, TYPE_PIXEL, \ + TYPE_fCOORD2D, EnumImageType, \ image_convert, image_mask_add, image_matte, image_minmax, bgr2image, \ cv2tensor, image2bgr, tensor2cv from .compose import image_blend, image_crop_center -from .channel import EnumPixelSwizzle, \ +from .channel import \ + EnumPixelSwizzle, \ channel_solid # ============================================================================== diff --git a/sup/image/color.py b/sup/image/color.py index f5b7885..f149b44 100644 --- a/sup/image/color.py +++ b/sup/image/color.py @@ -15,10 +15,8 @@ from sklearn.cluster import KMeans from daltonlens import simulate from blendmodes.blend import BlendType -from loguru import logger - from . import TYPE_IMAGE, TYPE_PIXEL, \ - EnumGrayscaleCrunch, EnumImageType, EnumIntFloat, \ + EnumImageType, \ bgr2hsv, hsv2bgr, image_convert, image_mask, image_mask_add from .compose import image_blend @@ -33,6 +31,15 @@ TYPE_LUT = Tuple[int, int, int, int] # === ENUMERATION === # ============================================================================== +class EnumIntFloat(Enum): + FLOAT = 0 + INT = 1 + +class EnumGrayscaleCrunch(Enum): + LOW = 0 + HIGH = 1 + MEAN = 2 + class EnumColorMap(Enum): AUTUMN = cv2.COLORMAP_AUTUMN BONE = cv2.COLORMAP_BONE diff --git a/sup/stream.py b/sup/stream.py index 1861a59..b3409d3 100644 --- a/sup/stream.py +++ b/sup/stream.py @@ -10,7 +10,7 @@ import json import time import array import threading -from typing import Any, List, Tuple +from typing import Any, Dict, List, Tuple from itertools import repeat from configparser import ConfigParser from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer @@ -97,7 +97,7 @@ def monitor_capture(monitor:int=0, tlwh:Tuple[int, int, int, int]=None, width:in img = cv2.resize(img, (width, height)) return img -def monitor_list() -> dict: +def monitor_list() -> Dict[str, str]: if JOV_DOCKERENV: return {} ret = {} @@ -105,7 +105,7 @@ def monitor_list() -> dict: ret = {i:v for i, v in enumerate(sct.monitors)} return ret -def window_list() -> dict: +def window_list() -> Dict[str, str]: return {} if sys.platform.startswith('win'): @@ -114,7 +114,7 @@ if sys.platform.startswith('win'): import win32ui from ctypes import windll - def window_list() -> dict: + def window_list() -> Dict[str, str]: _windows = {} def window_enum_handler(hwnd, ctx) -> None: if win32gui.IsWindowVisible(hwnd): @@ -191,7 +191,7 @@ elif sys.platform.startswith('darwin'): return None - def window_list() -> dict: + def window_list() -> Dict[str, str]: _windows = {} window_list = Quartz.CGWindowListCopyWindowInfo( Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements,