From faab680db508e30039bd9ab62395164c9b285921 Mon Sep 17 00:00:00 2001 From: limbicnation Date: Wed, 22 Apr 2026 04:57:18 +0200 Subject: [PATCH 1/2] chore: lint cleanup and artifact removal - Remove unused imports (ruff F401) - Fix f-strings without placeholders (ruff F541) - Resolve E402 import ordering in grabcut_nodes.py - Fix B108 hardcoded /tmp fallback in nodes.py (use tempfile.gettempdir()) - Delete untracked artifacts: .claude/, node.zip, __pycache__/ - Add .claude/ and *.zip to .gitignore --- .gitignore | 6 ++++++ RemoveBackgroundAndResizeNode.py | 2 +- __init__.py | 2 +- background_remover.py | 1 - grabcut_nodes.py | 17 +++++++++-------- grabcut_remover.py | 2 +- nodes.py | 5 ++--- test_grabcut.py | 10 ++++------ test_node_import.py | 2 +- test_power_of_8_scaling.py | 2 +- test_power_of_8_standalone.py | 2 +- tests/test_grabcut_nodes.py | 1 - tests/test_grabcut_processor.py | 2 -- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 0a19790..419c287 100644 --- a/.gitignore +++ b/.gitignore @@ -172,3 +172,9 @@ cython_debug/ # PyPI configuration file .pypirc + +# IDE / Editor artifacts +.claude/ + +# Build artifacts +*.zip diff --git a/RemoveBackgroundAndResizeNode.py b/RemoveBackgroundAndResizeNode.py index 807441b..8ecd981 100644 --- a/RemoveBackgroundAndResizeNode.py +++ b/RemoveBackgroundAndResizeNode.py @@ -15,7 +15,7 @@ License: Apache-2.0 from __future__ import annotations import logging -from typing import Optional, Tuple +from typing import Tuple import numpy as np import torch diff --git a/__init__.py b/__init__.py index 398a238..c092f90 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ # Check for optional dependencies and provide helpful warnings try: - import sklearn + import sklearn # noqa: F401 SKLEARN_AVAILABLE = True except ImportError: SKLEARN_AVAILABLE = False diff --git a/background_remover.py b/background_remover.py index 62e2e9e..8f94944 100644 --- a/background_remover.py +++ b/background_remover.py @@ -1,6 +1,5 @@ import numpy as np import cv2 -import colorsys from typing import Tuple, Optional, List # Lazy import sklearn with graceful fallback diff --git a/grabcut_nodes.py b/grabcut_nodes.py index be79890..61c7a97 100644 --- a/grabcut_nodes.py +++ b/grabcut_nodes.py @@ -1,6 +1,5 @@ from __future__ import annotations -import time from typing import Optional, Tuple import numpy as np @@ -10,10 +9,17 @@ from PIL import Image log = structlog.get_logger(__name__) +import os.path as _path # noqa: E402 +import sys as _sys # noqa: E402 + +_parent = _path.dirname(__file__) +if _parent not in _sys.path: + _sys.path.insert(0, _parent) + # Try to import ComfyUI modules try: - import folder_paths - import comfy.utils + import folder_paths # noqa: F401 + import comfy.utils # noqa: F401 COMFY_AVAILABLE = True except ImportError: COMFY_AVAILABLE = False @@ -26,11 +32,6 @@ except ImportError: from grabcut_remover import GrabCutProcessor, create_fallback_processor, _log_gpu_memory # Pydantic validation — security-hardened parameter sanitisation before GPU execution -import os.path as _path -import sys as _sys -_parent = _path.dirname(__file__) -if _parent not in _sys.path: - _sys.path.insert(0, _parent) try: from src.validation import validate_node_params, GrabCutParams, MaskParams except ImportError: diff --git a/grabcut_remover.py b/grabcut_remover.py index ef853e4..d0d8f5c 100644 --- a/grabcut_remover.py +++ b/grabcut_remover.py @@ -3,7 +3,7 @@ from __future__ import annotations import os import threading import time -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, Optional import cv2 import numpy as np diff --git a/nodes.py b/nodes.py index fe6b0f6..0139a73 100644 --- a/nodes.py +++ b/nodes.py @@ -1,9 +1,8 @@ -import os import numpy as np import torch from PIL import Image import cv2 -import colorsys +import tempfile # Handle ComfyUI imports gracefully for testing try: @@ -17,7 +16,7 @@ except ImportError: # Create mock folder_paths folder_paths = ModuleType('folder_paths') - folder_paths.get_input_directory = lambda: '/tmp' + folder_paths.get_input_directory = lambda: tempfile.gettempdir() sys.modules['folder_paths'] = folder_paths # Create mock comfy.utils diff --git a/test_grabcut.py b/test_grabcut.py index b8f354f..a6fc595 100644 --- a/test_grabcut.py +++ b/test_grabcut.py @@ -6,9 +6,7 @@ Tests both standalone processing and ComfyUI node integration. import numpy as np import cv2 -from pathlib import Path import sys -import time # Test imports print("Testing imports...") @@ -78,7 +76,7 @@ def test_grabcut_processor(): result = processor.process_with_grabcut(test_img, target_class="auto") if result['success']: - print(f"✓ Processing successful!") + print("✓ Processing successful!") print(f" - Processing time: {result['processing_time_ms']}ms") print(f" - Detection confidence: {result['confidence']:.2f}") if result['bbox']: @@ -334,7 +332,7 @@ def test_bbox_validation_fixes(): print(f"✗ Error with safety margin {safety_margin}: {e}") # Test minimum bbox size functionality with small object simulation - print(f"\n--- Testing min_bbox_size functionality ---") + print("\n--- Testing min_bbox_size functionality ---") # Create a small test image with a tiny object to force min_bbox_size logic small_test_image = torch.zeros((1, 200, 200, 3)) # Small 200x200 image @@ -373,7 +371,7 @@ def test_bbox_validation_fixes(): else: print(f" ✗ Bbox size not enforced: {actual_width}x{actual_height} < {min_size}") else: - print(f" ⚠ No valid bbox detected, using fallback") + print(" ⚠ No valid bbox detected, using fallback") except Exception as e: print(f" ✗ Error with min_bbox_size {min_size}: {e}") @@ -431,7 +429,7 @@ def test_edge_blur_functionality(): if len(mask_values) > 2: print(f" ✓ Soft edges detected (unique values: {len(mask_values)})") else: - print(f" ⚠ Expected soft edges but found binary mask") + print(" ⚠ Expected soft edges but found binary mask") else: print(f" ✓ Binary edges preserved (unique values: {len(mask_values)})") diff --git a/test_node_import.py b/test_node_import.py index a1d3370..fc4520f 100644 --- a/test_node_import.py +++ b/test_node_import.py @@ -15,7 +15,7 @@ from grabcut_nodes import NODE_CLASS_MAPPINGS as GRABCUT_MAPPINGS, NODE_DISPLAY_ NODE_CLASS_MAPPINGS = {**NODES_MAPPINGS, **GRABCUT_MAPPINGS} NODE_DISPLAY_NAME_MAPPINGS = {**NODES_DISPLAY_MAPPINGS, **GRABCUT_DISPLAY_MAPPINGS} -print(f"✓ Successfully imported and combined node mappings") +print("✓ Successfully imported and combined node mappings") print(f"Found {len(NODE_CLASS_MAPPINGS)} total nodes:") for name, cls in NODE_CLASS_MAPPINGS.items(): display = NODE_DISPLAY_NAME_MAPPINGS.get(name, "Unknown") diff --git a/test_power_of_8_scaling.py b/test_power_of_8_scaling.py index d9566f3..b19efcc 100644 --- a/test_power_of_8_scaling.py +++ b/test_power_of_8_scaling.py @@ -97,7 +97,7 @@ def test_input_types(): if sizes == expected_sizes: print("✅ output_size parameter configured correctly") else: - print(f"❌ output_size sizes mismatch") + print("❌ output_size sizes mismatch") print(f" Expected: {expected_sizes}") print(f" Got: {sizes}") return False diff --git a/test_power_of_8_standalone.py b/test_power_of_8_standalone.py index e46d65f..ab82f94 100644 --- a/test_power_of_8_standalone.py +++ b/test_power_of_8_standalone.py @@ -115,7 +115,7 @@ def test_input_types(): if sizes == expected_sizes: print("✅ output_size parameter configured correctly") else: - print(f"❌ output_size sizes mismatch") + print("❌ output_size sizes mismatch") print(f" Expected: {expected_sizes}") print(f" Got: {sizes}") return False diff --git a/tests/test_grabcut_nodes.py b/tests/test_grabcut_nodes.py index bcbd5a7..a5461f6 100644 --- a/tests/test_grabcut_nodes.py +++ b/tests/test_grabcut_nodes.py @@ -2,7 +2,6 @@ from __future__ import annotations import numpy as np -import pytest import torch diff --git a/tests/test_grabcut_processor.py b/tests/test_grabcut_processor.py index 71e1865..277e817 100644 --- a/tests/test_grabcut_processor.py +++ b/tests/test_grabcut_processor.py @@ -109,7 +109,6 @@ class TestRefineEdges: def test_refine_edges_returns_uint8_mask(self, grabcut_processor, sample_mask_hw): """refine_edges returns uint8 array with 0/255 values.""" - import cv2 rgb = np.zeros((256, 256, 3), dtype=np.uint8) result = grabcut_processor.refine_edges(sample_mask_hw, rgb) assert result.dtype == np.uint8 @@ -121,7 +120,6 @@ class TestRefineEdges: """With zero refinement strength, mask is returned unchanged (type may differ).""" grabcut_processor.edge_refinement_strength = 0.0 grabcut_processor.edge_blur_amount = 0.0 - import cv2 rgb = np.zeros((256, 256, 3), dtype=np.uint8) result = grabcut_processor.refine_edges(sample_mask_hw, rgb) assert result.shape == sample_mask_hw.shape From d53c734df10af1a813041e81e34c9b7dae1de610 Mon Sep 17 00:00:00 2001 From: limbicnation Date: Wed, 22 Apr 2026 05:01:13 +0200 Subject: [PATCH 2/2] docs: update README with v1.2.0 node and dev instructions - Add missing dependencies (ultralytics, pydantic, structlog, opencv-contrib) - Document RemoveBackgroundAndResizeNode - Add Development section with testing, linting, and project structure --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59243b6..e5adbe0 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,11 @@ - `torch` - PyTorch for tensor operations - `numpy` - Numerical computing - `Pillow` - Image processing -- `opencv-python` - Computer vision operations +- `opencv-python` / `opencv-contrib-python` - Computer vision operations - `scikit-learn` - Machine learning algorithms for clustering +- `ultralytics` - YOLO object detection for Auto GrabCut +- `pydantic` - Parameter validation and sanitisation +- `structlog` - Structured logging ### ⚠️ Important: Dependency Installation @@ -372,6 +375,76 @@ The system automatically adjusts `edge_blur_amount` based on image analysis: --- +## 🖼️ Remove Background and Resize Node + +### Overview +The **RemoveBackgroundAndResizeNode** provides standalone PIL-only background removal with integrated resize functionality. Unlike the main transparency node, this implementation uses pure Pillow operations without OpenCV, making it lightweight and dependency-minimal for simple workflows. + +### Key Features +- **PIL-Only Processing**: No OpenCV required — uses Pillow for all image operations +- **Integrated Resize**: Background removal and resizing in a single node +- **Lightweight**: Minimal memory footprint for batch workflows + +### Node Parameters + +| Parameter | Type | Range/Options | Default | Description | +|-----------|------|---------------|---------|-------------| +| **width** | INT | 64-4096 | 512 | Output width | +| **height** | INT | 64-4096 | 512 | Output height | +| **upscale_method** | DROPDOWN | NEAREST, BILINEAR, BICUBIC, LANCZOS | LANCZOS | Interpolation method | + +--- + +## 🔧 Development + +### Testing + +```bash +# Install test dependencies +pip install pytest + +# Run test suite +python -m pytest tests/ -v + +# Run standalone test scripts +python test_scaling.py +python test_power_of_8_scaling.py +python test_standalone.py +python test_power_of_8_standalone.py +``` + +### Linting & Security + +```bash +# Run ruff (fast Python linter) +ruff check . + +# Run flake8 (critical error checks) +flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + +# Run bandit (security scan) +bandit -r . +``` + +### Project Structure + +``` +. +├── __init__.py # ComfyUI node registration +├── nodes.py # Core TransparencyBackgroundRemover nodes +├── background_remover.py # EnhancedPixelArtProcessor engine +├── grabcut_nodes.py # Auto GrabCut + Refinement nodes +├── grabcut_remover.py # GrabCutProcessor engine +├── RemoveBackgroundAndResizeNode.py # PIL-only resize node +├── src/validation.py # Pydantic parameter validation +├── tests/ # Pytest test suite +├── requirements.txt # Python dependencies +├── install.py # Dependency installer +└── examples/ # Example images and workflows +``` + +--- + ## 🐛 Troubleshooting ### Common Issues