diff --git a/test_power_of_8_scaling.py b/test_power_of_8_scaling.py new file mode 100644 index 0000000..485b2ee --- /dev/null +++ b/test_power_of_8_scaling.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +""" +Test script for Power-of-8 scaling functionality +""" +import sys +import os + +# Add current directory to path to import nodes +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +def test_parsing(): + """Test output size parsing""" + print("Testing output size parsing...") + + try: + from nodes import TransparencyBackgroundRemover + node = TransparencyBackgroundRemover() + + # Test valid sizes + test_cases = [ + ("ORIGINAL", None), + ("64x64", (64, 64)), + ("512x512", (512, 512)), + ("1024x1024", (1024, 1024)), + ("2048x2048", (2048, 2048)) + ] + + for input_str, expected in test_cases: + result = node.parse_output_size(input_str) + if result == expected: + print(f"✅ {input_str} -> {result}") + else: + print(f"❌ {input_str} -> {result}, expected {expected}") + return False + + # Test invalid format + try: + node.parse_output_size("invalid") + print("❌ Should have raised ValueError for invalid format") + return False + except ValueError: + print("✅ Correctly rejected invalid format") + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_scaling_calculation(): + """Test scaling factor calculation""" + print("Testing scaling factor calculation...") + + try: + from nodes import TransparencyBackgroundRemover + node = TransparencyBackgroundRemover() + + test_cases = [ + # (current_size, target_size, expected_scale_approx) + ((64, 64), (128, 128), 2.0), + ((128, 128), (256, 256), 2.0), + ((100, 100), (512, 512), 5.12), + ((256, 256), (128, 128), 0.5), + ((512, 512), (512, 512), 1.0) + ] + + for current, target, expected_scale in test_cases: + result = node.calculate_scaling_factor(current, target) + if abs(result - expected_scale) < 0.01: + print(f"✅ {current} -> {target}: scale={result:.2f}") + else: + print(f"❌ {current} -> {target}: scale={result:.2f}, expected≈{expected_scale}") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_input_types(): + """Test that INPUT_TYPES includes new parameters""" + print("Testing INPUT_TYPES configuration...") + + try: + from nodes import TransparencyBackgroundRemover + node = TransparencyBackgroundRemover() + inputs = node.INPUT_TYPES() + + # Check output_size parameter + if 'output_size' in inputs['required']: + sizes = inputs['required']['output_size'][0] + expected_sizes = ["ORIGINAL", "64x64", "96x96", "128x128", "256x256", + "512x512", "768x768", "1024x1024", "1280x1280", + "1536x1536", "1792x1792", "2048x2048"] + + if sizes == expected_sizes: + print("✅ output_size parameter configured correctly") + else: + print(f"❌ output_size sizes mismatch") + print(f" Expected: {expected_sizes}") + print(f" Got: {sizes}") + return False + else: + print("❌ output_size parameter missing") + return False + + # Check scaling_method parameter + if 'scaling_method' in inputs['required']: + methods = inputs['required']['scaling_method'][0] + if methods == ["NEAREST"]: + print("✅ scaling_method parameter configured correctly") + else: + print(f"❌ scaling_method wrong: {methods}") + return False + else: + print("❌ scaling_method parameter missing") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_power_of_8_validation(): + """Verify all sizes are powers/multiples of 8""" + print("Testing power-of-8 validation...") + + sizes = [64, 96, 128, 256, 512, 768, 1024, 1280, 1536, 1792, 2048] + + for size in sizes: + if size % 8 == 0: + print(f"✅ {size} is multiple of 8") + else: + print(f"❌ {size} is NOT multiple of 8") + return False + + return True + +def main(): + """Run all tests""" + print("🧪 Testing Power-of-8 Scaling Implementation") + print("=" * 50) + + tests = [ + test_power_of_8_validation, + test_input_types, + test_parsing, + test_scaling_calculation + ] + + passed = 0 + total = len(tests) + + for test in tests: + if test(): + passed += 1 + print() + + print("=" * 50) + print(f"Results: {passed}/{total} tests passed") + + if passed == total: + print("🎉 All tests PASSED!") + return True + else: + print("❌ Some tests FAILED!") + return False + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test_power_of_8_standalone.py b/test_power_of_8_standalone.py new file mode 100644 index 0000000..e46d65f --- /dev/null +++ b/test_power_of_8_standalone.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +""" +Standalone test script for Power-of-8 scaling functionality +without ComfyUI dependencies. +""" +import sys +import os +import numpy as np +from PIL import Image + +# Add current directory to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# Mock ComfyUI modules for testing +class MockFolderPaths: + pass + +class MockComfyUtils: + pass + +class MockComfy: + utils = MockComfyUtils() + +# Mock the imports +sys.modules['folder_paths'] = MockFolderPaths() +sys.modules['comfy'] = MockComfy() +sys.modules['comfy.utils'] = MockComfyUtils() + +def test_parsing(): + """Test output size parsing""" + print("Testing output size parsing...") + + try: + from nodes import TransparencyBackgroundRemover + node = TransparencyBackgroundRemover() + + # Test valid sizes + test_cases = [ + ("ORIGINAL", None), + ("64x64", (64, 64)), + ("512x512", (512, 512)), + ("1024x1024", (1024, 1024)), + ("2048x2048", (2048, 2048)) + ] + + for input_str, expected in test_cases: + result = node.parse_output_size(input_str) + if result == expected: + print(f"✅ {input_str} -> {result}") + else: + print(f"❌ {input_str} -> {result}, expected {expected}") + return False + + # Test invalid format + try: + node.parse_output_size("invalid") + print("❌ Should have raised ValueError for invalid format") + return False + except ValueError: + print("✅ Correctly rejected invalid format") + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_scaling_calculation(): + """Test scaling factor calculation""" + print("Testing scaling factor calculation...") + + try: + from nodes import TransparencyBackgroundRemover + node = TransparencyBackgroundRemover() + + test_cases = [ + # (current_size, target_size, expected_scale_approx) + ((64, 64), (128, 128), 2.0), + ((128, 128), (256, 256), 2.0), + ((100, 100), (512, 512), 5.12), + ((256, 256), (128, 128), 0.5), + ((512, 512), (512, 512), 1.0) + ] + + for current, target, expected_scale in test_cases: + result = node.calculate_scaling_factor(current, target) + if abs(result - expected_scale) < 0.01: + print(f"✅ {current} -> {target}: scale={result:.2f}") + else: + print(f"❌ {current} -> {target}: scale={result:.2f}, expected≈{expected_scale}") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_input_types(): + """Test that INPUT_TYPES includes new parameters""" + print("Testing INPUT_TYPES configuration...") + + try: + from nodes import TransparencyBackgroundRemover + node = TransparencyBackgroundRemover() + inputs = node.INPUT_TYPES() + + # Check output_size parameter + if 'output_size' in inputs['required']: + sizes = inputs['required']['output_size'][0] + expected_sizes = ["ORIGINAL", "64x64", "96x96", "128x128", "256x256", + "512x512", "768x768", "1024x1024", "1280x1280", + "1536x1536", "1792x1792", "2048x2048"] + + if sizes == expected_sizes: + print("✅ output_size parameter configured correctly") + else: + print(f"❌ output_size sizes mismatch") + print(f" Expected: {expected_sizes}") + print(f" Got: {sizes}") + return False + else: + print("❌ output_size parameter missing") + return False + + # Check scaling_method parameter + if 'scaling_method' in inputs['required']: + methods = inputs['required']['scaling_method'][0] + expected_methods = ["NEAREST", "BILINEAR", "BICUBIC", "LANCZOS"] + if methods == expected_methods: + print("✅ scaling_method parameter configured correctly") + else: + print(f"❌ scaling_method wrong: {methods}") + return False + else: + print("❌ scaling_method parameter missing") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_interpolation_methods(): + """Test interpolation methods""" + print("Testing interpolation methods...") + + try: + from nodes import TransparencyBackgroundRemover + + # Create test image + test_array = np.zeros((64, 64, 3), dtype=np.uint8) + test_array[16:48, 16:48] = [255, 0, 0] # Red square + test_pil = Image.fromarray(test_array) + + node = TransparencyBackgroundRemover() + + # Test all interpolation methods + methods = ["NEAREST", "BILINEAR", "BICUBIC", "LANCZOS"] + target_size = (128, 128) + + for method in methods: + result = node.intelligent_scale(test_pil, target_size, method) + if result.size == target_size: + print(f"✅ {method} scaling works correctly") + else: + print(f"❌ {method} scaling failed: expected {target_size}, got {result.size}") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_power_of_8_validation(): + """Verify all sizes are powers/multiples of 8""" + print("Testing power-of-8 validation...") + + sizes = [64, 96, 128, 256, 512, 768, 1024, 1280, 1536, 1792, 2048] + + for size in sizes: + if size % 8 == 0: + print(f"✅ {size} is multiple of 8") + else: + print(f"❌ {size} is NOT multiple of 8") + return False + + return True + +def main(): + """Run all tests""" + print("🧪 Testing Power-of-8 Scaling Implementation (Standalone)") + print("=" * 60) + + tests = [ + test_power_of_8_validation, + test_input_types, + test_parsing, + test_scaling_calculation, + test_interpolation_methods + ] + + passed = 0 + total = len(tests) + + for test in tests: + if test(): + passed += 1 + print() + + print("=" * 60) + print(f"Results: {passed}/{total} tests passed") + + if passed == total: + print("🎉 All tests PASSED!") + return True + else: + print("❌ Some tests FAILED!") + return False + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test_scaling.py b/test_scaling.py new file mode 100644 index 0000000..396dbe5 --- /dev/null +++ b/test_scaling.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Test script for NEAREST NEIGHBOR scaling functionality +""" +import numpy as np +import torch +from PIL import Image +import sys +import os + +# Add current directory to path to import nodes +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from nodes import TransparencyBackgroundRemover + +def create_test_image(width=64, height=64): + """Create a simple test image with distinct patterns""" + # Create a simple pattern with clear foreground/background + image = np.zeros((height, width, 3), dtype=np.uint8) + + # Add background (blue) + image[:, :] = [50, 100, 200] + + # Add foreground object (red square in center) + center_size = min(width, height) // 3 + start_x = (width - center_size) // 2 + start_y = (height - center_size) // 2 + image[start_y:start_y+center_size, start_x:start_x+center_size] = [200, 50, 50] + + return image + +def test_input_validation(): + """Test minimum size validation""" + print("Testing input validation...") + + node = TransparencyBackgroundRemover() + + # Test with image smaller than 64x64 + small_image = create_test_image(32, 32) + small_tensor = torch.from_numpy(small_image).unsqueeze(0).float() / 255.0 + + try: + node.remove_background(small_tensor) + print("❌ FAILED: Should have raised ValueError for small image") + return False + except ValueError as e: + if "64x64" in str(e): + print("✅ PASSED: Correctly rejected small image") + else: + print(f"❌ FAILED: Wrong error message: {e}") + return False + except Exception as e: + print(f"❌ FAILED: Unexpected error: {e}") + return False + + return True + +def test_scaling_functionality(): + """Test NEAREST scaling functionality""" + print("Testing scaling functionality...") + + node = TransparencyBackgroundRemover() + + # Test with valid 64x64 image + test_image = create_test_image(64, 64) + test_tensor = torch.from_numpy(test_image).unsqueeze(0).float() / 255.0 + + try: + # Test 1x scaling (no scaling) + result_1x, mask_1x = node.remove_background( + test_tensor, + scale_factor=1, + scaling_method="NEAREST" + ) + print(f"✅ 1x scaling: {result_1x.shape}") + + # Test 2x scaling + result_2x, mask_2x = node.remove_background( + test_tensor, + scale_factor=2, + scaling_method="NEAREST" + ) + print(f"✅ 2x scaling: {result_2x.shape}") + + # Verify dimensions are doubled + expected_height = test_tensor.shape[1] * 2 + expected_width = test_tensor.shape[2] * 2 + + if result_2x.shape[1] == expected_height and result_2x.shape[2] == expected_width: + print("✅ PASSED: 2x scaling dimensions correct") + else: + print(f"❌ FAILED: Expected {expected_height}x{expected_width}, got {result_2x.shape[1]}x{result_2x.shape[2]}") + return False + + # Test 4x scaling + result_4x, mask_4x = node.remove_background( + test_tensor, + scale_factor=4, + scaling_method="NEAREST" + ) + print(f"✅ 4x scaling: {result_4x.shape}") + + return True + + except Exception as e: + print(f"❌ FAILED: Scaling test error: {e}") + return False + +def test_nearest_scale_function(): + """Test the nearest_scale function directly""" + print("Testing nearest_scale function...") + + node = TransparencyBackgroundRemover() + + # Create a small PIL image + test_array = create_test_image(8, 8) + test_pil = Image.fromarray(test_array) + + try: + # Test 1x (should return same image) + result_1x = node.nearest_scale(test_pil, 1) + if result_1x.size == test_pil.size: + print("✅ 1x scaling preserves size") + else: + print("❌ FAILED: 1x scaling changed size") + return False + + # Test 2x scaling + result_2x = node.nearest_scale(test_pil, 2) + expected_size = (test_pil.width * 2, test_pil.height * 2) + if result_2x.size == expected_size: + print("✅ 2x scaling correct size") + else: + print(f"❌ FAILED: Expected {expected_size}, got {result_2x.size}") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: nearest_scale test error: {e}") + return False + +def main(): + """Run all tests""" + print("🧪 Testing NEAREST NEIGHBOR Scaling Implementation") + print("=" * 50) + + tests = [ + test_input_validation, + test_nearest_scale_function, + test_scaling_functionality + ] + + passed = 0 + total = len(tests) + + for test in tests: + if test(): + passed += 1 + print() + + print("=" * 50) + print(f"Results: {passed}/{total} tests passed") + + if passed == total: + print("🎉 All tests PASSED!") + return True + else: + print("❌ Some tests FAILED!") + return False + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test_standalone.py b/test_standalone.py new file mode 100644 index 0000000..800ff44 --- /dev/null +++ b/test_standalone.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +""" +Standalone test script for the background remover functionality +without ComfyUI dependencies. +""" +import sys +import os +import numpy as np +from PIL import Image + +# Add current directory to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +def create_test_image(width=64, height=64): + """Create a simple test image with distinct patterns""" + # Create a simple pattern with clear foreground/background + image = np.zeros((height, width, 3), dtype=np.uint8) + + # Add background (blue) + image[:, :] = [50, 100, 200] + + # Add foreground object (red square in center) + center_size = min(width, height) // 3 + start_x = (width - center_size) // 2 + start_y = (height - center_size) // 2 + image[start_y:start_y+center_size, start_x:start_x+center_size] = [200, 50, 50] + + return image + +def test_background_remover(): + """Test the EnhancedPixelArtProcessor directly""" + print("Testing EnhancedPixelArtProcessor...") + + try: + from background_remover import EnhancedPixelArtProcessor + + # Create test image + test_image = create_test_image(128, 128) + print(f"✅ Created test image: {test_image.shape}") + + # Initialize processor + processor = EnhancedPixelArtProcessor( + tolerance=30, + edge_sensitivity=0.8, + color_clusters=8, + foreground_bias=0.7, + edge_refinement=True, + dither_handling=True, + binary_threshold=128 + ) + print("✅ Initialized processor") + + # Process image + result = processor.remove_background_advanced(test_image) + print(f"✅ Processed image: {result.shape}") + + # Verify result has alpha channel + if result.shape[2] == 4: + print("✅ Result has alpha channel") + else: + print(f"❌ Expected 4 channels, got {result.shape[2]}") + return False + + # Check that some pixels are transparent + alpha_channel = result[:, :, 3] + transparent_pixels = np.sum(alpha_channel == 0) + opaque_pixels = np.sum(alpha_channel == 255) + + print(f"✅ Transparent pixels: {transparent_pixels}") + print(f"✅ Opaque pixels: {opaque_pixels}") + + if transparent_pixels > 0 and opaque_pixels > 0: + print("✅ Background removal successful - has both transparent and opaque pixels") + else: + print("❌ Background removal may not be working correctly") + return False + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_auto_adjustment(): + """Test auto-adjustment feature""" + print("Testing auto-adjustment...") + + try: + from background_remover import EnhancedPixelArtProcessor + + # Create test image + test_image = create_test_image(64, 64) + + # Initialize processor + processor = EnhancedPixelArtProcessor() + + # Test auto-adjustment + adjustments = processor.auto_adjust_parameters(test_image) + + if adjustments: + print(f"✅ Auto-adjustments made: {adjustments}") + else: + print("✅ No auto-adjustments needed") + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_scaling_methods(): + """Test different scaling methods""" + print("Testing scaling methods...") + + try: + # Test PIL resampling methods + test_image = create_test_image(64, 64) + pil_image = Image.fromarray(test_image) + + methods = { + "NEAREST": Image.Resampling.NEAREST, + "BILINEAR": Image.Resampling.BILINEAR, + "BICUBIC": Image.Resampling.BICUBIC, + "LANCZOS": Image.Resampling.LANCZOS + } + + for method_name, method in methods.items(): + scaled = pil_image.resize((128, 128), method) + print(f"✅ {method_name} scaling: {scaled.size}") + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def test_performance_large_image(): + """Test performance optimizations with large image""" + print("Testing performance with large image...") + + try: + from background_remover import EnhancedPixelArtProcessor + import time + + # Create large test image + large_image = create_test_image(1200, 1200) + print(f"✅ Created large test image: {large_image.shape}") + + # Initialize processor + processor = EnhancedPixelArtProcessor() + + # Process with timing + start_time = time.time() + result = processor.remove_background_advanced(large_image) + processing_time = time.time() - start_time + + print(f"✅ Processed large image in {processing_time:.3f}s") + print(f"✅ Result shape: {result.shape}") + + # Performance should be reasonable (< 5 seconds for 1200x1200) + if processing_time < 5.0: + print("✅ Performance is acceptable") + else: + print(f"⚠️ Performance slower than expected: {processing_time:.3f}s") + + return True + + except Exception as e: + print(f"❌ FAILED: {e}") + return False + +def main(): + """Run all standalone tests""" + print("🧪 Running Standalone Background Remover Tests") + print("=" * 50) + + tests = [ + test_background_remover, + test_auto_adjustment, + test_scaling_methods, + test_performance_large_image + ] + + passed = 0 + total = len(tests) + + for test in tests: + print() + if test(): + passed += 1 + print("-" * 30) + + print() + print("=" * 50) + print(f"Results: {passed}/{total} tests passed") + + if passed == total: + print("🎉 All tests PASSED!") + return True + else: + print("❌ Some tests FAILED!") + return False + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/yolov8n.pt b/yolov8n.pt new file mode 100644 index 0000000..0db4ca4 Binary files /dev/null and b/yolov8n.pt differ