Merge pull request #14 from Limbicnation/chore/lint-and-docs

Chore/lint and docs
This commit is contained in:
Gero Doll
2026-04-22 05:53:27 +02:00
committed by GitHub
14 changed files with 101 additions and 28 deletions
+6
View File
@@ -172,3 +172,9 @@ cython_debug/
# PyPI configuration file
.pypirc
# IDE / Editor artifacts
.claude/
# Build artifacts
*.zip
+74 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
-1
View File
@@ -1,6 +1,5 @@
import numpy as np
import cv2
import colorsys
from typing import Tuple, Optional, List
# Lazy import sklearn with graceful fallback
+9 -8
View File
@@ -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:
+1 -1
View File
@@ -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
+2 -3
View File
@@ -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
+4 -6
View File
@@ -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)})")
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
-1
View File
@@ -2,7 +2,6 @@
from __future__ import annotations
import numpy as np
import pytest
import torch
-2
View File
@@ -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