From 23e908f987f86de255cf4c2d4b49d1a80bc01bfa Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Feb 2026 13:55:04 +0700 Subject: [PATCH] Update nodes with new version --- __init__.py | 3 ++ numstrcleanup.py | 63 ++++++++++++++++++++++++ ocr.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 numstrcleanup.py create mode 100644 ocr.py diff --git a/__init__.py b/__init__.py index ecfdd41..2c1685d 100644 --- a/__init__.py +++ b/__init__.py @@ -10,6 +10,7 @@ from .blackextract import * from .autocrop import * from .numfunction import * from .linecounter import * +from .numstrcleanup import * NODE_CLASS_MAPPINGS = { "RasterToUniformSVG": RasterToSVGConverter, @@ -27,6 +28,7 @@ NODE_CLASS_MAPPINGS = { "RoundUpNode": RoundUpNode, "RoundDownNode": RoundDownNode, "CSVValueExtractor": CSVValueExtractor, + "NumberStringCleanup": NumberStringCleanup, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -45,4 +47,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "RoundUpNode": "Round Up", "RoundDownNode": "Round Down", "CSVValueExtractor": "CSV Value Extractor", + "NumberStringCleanup": "Number String Cleanup", } \ No newline at end of file diff --git a/numstrcleanup.py b/numstrcleanup.py new file mode 100644 index 0000000..d145320 --- /dev/null +++ b/numstrcleanup.py @@ -0,0 +1,63 @@ +""" +SVG Path Length Calculator Node for ComfyUI + +Calculates the total length of all paths in an SVG file, +ignoring line thickness (stroke-width). +""" + +import re + + +class NumberStringCleanup: + """ + Cleans up a raw string containing a number and converts it + into a two-decimal-place float-compatible string. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "raw_string": ("STRING", {"default": "", "multiline": False}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("cleaned_number",) + FUNCTION = "clean_number" + CATEGORY = "utils/string" + + def clean_number(self, raw_string: str): + s = raw_string.strip() + + # Remove everything except digits, commas, dots, and minus sign + s = re.sub(r"[^\d.,-]", "", s) + + # Replace comma with dot (e.g. "52,25" -> "52.25") + s = s.replace(",", ".") + + # If multiple dots exist, keep only the last one as decimal separator + if s.count(".") > 1: + parts = s.split(".") + s = "".join(parts[:-1]) + "." + parts[-1] + + # Handle edge cases + if s in ("", ".", "-", "-."): + return ("0.00",) + + # Handle leading minus + negative = s.startswith("-") + if negative: + s = s[1:] + + # Convert to float and format to 2 decimal places + try: + value = float(s) + except ValueError: + value = 0.0 + + if negative: + value = -value + + result = f"{value:.2f}" + return (result,) \ No newline at end of file diff --git a/ocr.py b/ocr.py new file mode 100644 index 0000000..73c8c58 --- /dev/null +++ b/ocr.py @@ -0,0 +1,121 @@ +import re +import numpy as np +from PIL import Image + + +class MaxValuePaddleOCR: + """ + A ComfyUI node that uses PaddleOCR to extract numbers from an image + and returns the largest numeric value found. + """ + + def __init__(self): + self.ocr = None + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + "optional": { + "use_gpu": ("BOOLEAN", {"default": False}), + "lang": (["en", "ch", "japan", "korean", "french", "german"], {"default": "en"}), + } + } + + RETURN_TYPES = ("FLOAT",) + RETURN_NAMES = ("max_value",) + FUNCTION = "extract_max_number" + CATEGORY = "image/text" + + def _init_ocr(self, use_gpu=False, lang="en"): + """Initialize PaddleOCR if not already done.""" + if self.ocr is None: + from paddleocr import PaddleOCR + self.ocr = PaddleOCR( + use_angle_cls=True, + lang=lang, + use_gpu=use_gpu, + show_log=False + ) + return self.ocr + + def _extract_numbers(self, text): + """ + Extract all numeric values (integers and floats) from text. + Handles various number formats including negative numbers and decimals. + """ + # Pattern matches integers and floats, including negative numbers + # Examples: 123, -456, 3.14, -2.5, .5, -.5 + pattern = r'-?\d+\.?\d*|-?\.\d+' + + numbers = [] + matches = re.findall(pattern, text) + + for match in matches: + try: + # Try to convert to float (works for both int and float strings) + num = float(match) + numbers.append(num) + except ValueError: + continue + + return numbers + + def extract_max_number(self, image, use_gpu=False, lang="en"): + """ + Main function to extract the maximum number from an image. + + Args: + image: ComfyUI IMAGE tensor (B, H, W, C) in range [0, 1] + use_gpu: Whether to use GPU for OCR + lang: Language for OCR recognition + + Returns: + Tuple containing the maximum numeric value found, or 0.0 if none found + """ + # Initialize OCR + ocr = self._init_ocr(use_gpu=use_gpu, lang=lang) + + # Convert ComfyUI image tensor to numpy array + # ComfyUI images are (B, H, W, C) with values in [0, 1] + if isinstance(image, np.ndarray): + img_array = image + else: + # Convert from torch tensor if needed + img_array = image.cpu().numpy() + + # Handle batch dimension - process first image + if len(img_array.shape) == 4: + img_array = img_array[0] + + # Convert from [0, 1] to [0, 255] and ensure uint8 + img_array = (img_array * 255).astype(np.uint8) + + # Convert to PIL Image then back to numpy for PaddleOCR + # PaddleOCR expects BGR or RGB numpy array + pil_image = Image.fromarray(img_array) + img_for_ocr = np.array(pil_image) + + # Run OCR + result = ocr.ocr(img_for_ocr, cls=True) + + # Extract all numbers from OCR results + all_numbers = [] + + if result and result[0]: + for line in result[0]: + if line and len(line) >= 2: + # line[1] contains (text, confidence) + text = line[1][0] if isinstance(line[1], (list, tuple)) else str(line[1]) + numbers = self._extract_numbers(text) + all_numbers.extend(numbers) + + # Return the maximum value, or 0.0 if no numbers found + if all_numbers: + max_value = max(all_numbers) + else: + max_value = 0.0 + + return (float(max_value),) \ No newline at end of file