mirror of
				https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
				synced 2025-10-31 01:55:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			291 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| """
 | |
| Interactive Parameter Tuning Tool for Newspaper Image Cleaning
 | |
| 
 | |
| This tool helps you find optimal parameters for your specific images
 | |
| by providing an interactive tuning interface.
 | |
| """
 | |
| 
 | |
| import cv2
 | |
| import json
 | |
| import numpy as np
 | |
| from pathlib import Path
 | |
| from image_cleaner import NewspaperImageCleaner
 | |
| 
 | |
| 
 | |
| class ParameterTuner:
 | |
|     """Interactive parameter tuning for image cleaning pipeline."""
 | |
| 
 | |
|     def __init__(self, sample_image_path):
 | |
|         """Initialize with a sample image for tuning."""
 | |
|         self.original = cv2.imread(str(sample_image_path))
 | |
|         if self.original is None:
 | |
|             raise ValueError(f"Could not load image: {sample_image_path}")
 | |
| 
 | |
|         # Resize large images for faster processing during tuning
 | |
|         height, width = self.original.shape[:2]
 | |
|         if height > 1500 or width > 1500:
 | |
|             scale = min(1500/height, 1500/width)
 | |
|             new_width = int(width * scale)
 | |
|             new_height = int(height * scale)
 | |
|             self.original = cv2.resize(self.original, (new_width, new_height))
 | |
|             print(f"Resized image to {new_width}x{new_height} for faster tuning")
 | |
| 
 | |
|         self.current_params = self._get_default_params()
 | |
|         self.cleaner = NewspaperImageCleaner(self.current_params)
 | |
| 
 | |
|     def _get_default_params(self):
 | |
|         """Get default parameters as starting point."""
 | |
|         return {
 | |
|             'bilateral_d': 9,
 | |
|             'bilateral_sigma_color': 75,
 | |
|             'bilateral_sigma_space': 75,
 | |
|             'clahe_clip_limit': 2.0,
 | |
|             'clahe_grid_size': (8, 8),
 | |
|             'gamma': 1.2,
 | |
|             'denoise_h': 10,
 | |
|             'morph_kernel_size': 2,
 | |
|             'unsharp_amount': 1.5,
 | |
|             'unsharp_radius': 1.0,
 | |
|             'unsharp_threshold': 0,
 | |
|         }
 | |
| 
 | |
|     def update_parameter(self, param_name, value):
 | |
|         """Update a single parameter and refresh the cleaner."""
 | |
|         if param_name in self.current_params:
 | |
|             # Handle special cases
 | |
|             if param_name == 'clahe_grid_size':
 | |
|                 self.current_params[param_name] = (int(value), int(value))
 | |
|             else:
 | |
|                 self.current_params[param_name] = value
 | |
| 
 | |
|             # Update cleaner with new parameters
 | |
|             self.cleaner = NewspaperImageCleaner(self.current_params)
 | |
|             print(f"Updated {param_name} = {value}")
 | |
| 
 | |
|     def process_with_current_params(self, steps=None):
 | |
|         """Process the sample image with current parameters."""
 | |
|         if steps is None:
 | |
|             steps = ['denoise', 'contrast', 'background', 'sharpen']
 | |
| 
 | |
|         image = self.original.copy()
 | |
| 
 | |
|         # Apply processing steps
 | |
|         if 'denoise' in steps:
 | |
|             image = self.cleaner.reduce_noise(image)
 | |
| 
 | |
|         if 'contrast' in steps:
 | |
|             image = self.cleaner.enhance_contrast(image)
 | |
| 
 | |
|         if 'background' in steps:
 | |
|             image = self.cleaner.clean_background(image)
 | |
| 
 | |
|         if 'sharpen' in steps:
 | |
|             image = self.cleaner.sharpen_image(image)
 | |
| 
 | |
|         return image
 | |
| 
 | |
|     def create_comparison(self, steps=None):
 | |
|         """Create side-by-side comparison with current parameters."""
 | |
|         processed = self.process_with_current_params(steps)
 | |
| 
 | |
|         # Create side-by-side comparison
 | |
|         height = max(self.original.shape[0], processed.shape[0])
 | |
|         comparison = np.hstack([
 | |
|             cv2.resize(self.original, (self.original.shape[1], height)),
 | |
|             cv2.resize(processed, (processed.shape[1], height))
 | |
|         ])
 | |
| 
 | |
|         return comparison
 | |
| 
 | |
|     def save_comparison(self, output_path, steps=None):
 | |
|         """Save comparison image to file."""
 | |
|         comparison = self.create_comparison(steps)
 | |
|         cv2.imwrite(str(output_path), comparison)
 | |
|         print(f"Comparison saved to: {output_path}")
 | |
| 
 | |
|     def save_config(self, config_path):
 | |
|         """Save current parameters to JSON config file."""
 | |
|         # Convert tuple to list for JSON serialization
 | |
|         config_to_save = self.current_params.copy()
 | |
|         if 'clahe_grid_size' in config_to_save:
 | |
|             config_to_save['clahe_grid_size'] = list(config_to_save['clahe_grid_size'])
 | |
| 
 | |
|         with open(config_path, 'w') as f:
 | |
|             json.dump(config_to_save, f, indent=4)
 | |
|         print(f"Configuration saved to: {config_path}")
 | |
| 
 | |
|     def load_config(self, config_path):
 | |
|         """Load parameters from JSON config file."""
 | |
|         with open(config_path, 'r') as f:
 | |
|             loaded_params = json.load(f)
 | |
| 
 | |
|         # Convert list back to tuple if needed
 | |
|         if 'clahe_grid_size' in loaded_params:
 | |
|             loaded_params['clahe_grid_size'] = tuple(loaded_params['clahe_grid_size'])
 | |
| 
 | |
|         self.current_params.update(loaded_params)
 | |
|         self.cleaner = NewspaperImageCleaner(self.current_params)
 | |
|         print(f"Configuration loaded from: {config_path}")
 | |
| 
 | |
|     def interactive_tune(self):
 | |
|         """Start interactive tuning session."""
 | |
|         print("\n" + "="*60)
 | |
|         print("INTERACTIVE PARAMETER TUNING")
 | |
|         print("="*60)
 | |
|         print("Commands:")
 | |
|         print("  set <param> <value>  - Set parameter value")
 | |
|         print("  show                 - Show current parameters")
 | |
|         print("  test [steps]         - Test current parameters")
 | |
|         print("  save <file>          - Save configuration to file")
 | |
|         print("  load <file>          - Load configuration from file")
 | |
|         print("  compare [file]       - Save comparison image")
 | |
|         print("  presets              - Show parameter presets")
 | |
|         print("  help                 - Show this help")
 | |
|         print("  quit                 - Exit tuning")
 | |
|         print("\nParameters you can adjust:")
 | |
|         for param in self.current_params:
 | |
|             print(f"  {param}")
 | |
| 
 | |
|         while True:
 | |
|             try:
 | |
|                 command = input("\ntuner> ").strip().split()
 | |
|                 if not command:
 | |
|                     continue
 | |
| 
 | |
|                 cmd = command[0].lower()
 | |
| 
 | |
|                 if cmd == 'quit' or cmd == 'exit':
 | |
|                     break
 | |
| 
 | |
|                 elif cmd == 'show':
 | |
|                     self._show_parameters()
 | |
| 
 | |
|                 elif cmd == 'set' and len(command) >= 3:
 | |
|                     param = command[1]
 | |
|                     try:
 | |
|                         value = float(command[2]) if '.' in command[2] else int(command[2])
 | |
|                     except ValueError:
 | |
|                         value = command[2]
 | |
|                     self.update_parameter(param, value)
 | |
| 
 | |
|                 elif cmd == 'test':
 | |
|                     steps = command[1:] if len(command) > 1 else None
 | |
|                     print("Processing with current parameters...")
 | |
|                     processed = self.process_with_current_params(steps)
 | |
|                     print(f"Processed image shape: {processed.shape}")
 | |
| 
 | |
|                 elif cmd == 'save' and len(command) > 1:
 | |
|                     self.save_config(command[1])
 | |
| 
 | |
|                 elif cmd == 'load' and len(command) > 1:
 | |
|                     self.load_config(command[1])
 | |
| 
 | |
|                 elif cmd == 'compare':
 | |
|                     output = command[1] if len(command) > 1 else "tuning_comparison.jpg"
 | |
|                     self.save_comparison(output)
 | |
| 
 | |
|                 elif cmd == 'presets':
 | |
|                     self._show_presets()
 | |
| 
 | |
|                 elif cmd == 'help':
 | |
|                     self._show_help()
 | |
| 
 | |
|                 else:
 | |
|                     print("Unknown command. Type 'help' for available commands.")
 | |
| 
 | |
|             except KeyboardInterrupt:
 | |
|                 print("\nExiting tuner...")
 | |
|                 break
 | |
|             except Exception as e:
 | |
|                 print(f"Error: {str(e)}")
 | |
| 
 | |
|     def _show_parameters(self):
 | |
|         """Display current parameter values."""
 | |
|         print("\nCurrent Parameters:")
 | |
|         print("-" * 30)
 | |
|         for param, value in self.current_params.items():
 | |
|             print(f"  {param:<20} = {value}")
 | |
| 
 | |
|     def _show_presets(self):
 | |
|         """Show preset configurations for different image types."""
 | |
|         presets = {
 | |
|             "light_cleaning": {
 | |
|                 "bilateral_d": 5,
 | |
|                 "denoise_h": 5,
 | |
|                 "clahe_clip_limit": 1.5,
 | |
|                 "gamma": 1.1,
 | |
|                 "unsharp_amount": 1.2
 | |
|             },
 | |
|             "heavy_cleaning": {
 | |
|                 "bilateral_d": 15,
 | |
|                 "denoise_h": 15,
 | |
|                 "clahe_clip_limit": 3.0,
 | |
|                 "gamma": 1.3,
 | |
|                 "unsharp_amount": 2.0
 | |
|             },
 | |
|             "high_contrast": {
 | |
|                 "clahe_clip_limit": 4.0,
 | |
|                 "gamma": 1.4,
 | |
|                 "unsharp_amount": 2.5
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         print("\nAvailable Presets:")
 | |
|         print("-" * 30)
 | |
|         for name, params in presets.items():
 | |
|             print(f"{name}:")
 | |
|             for param, value in params.items():
 | |
|                 print(f"  {param} = {value}")
 | |
|             print()
 | |
| 
 | |
|     def _show_help(self):
 | |
|         """Show detailed help information."""
 | |
|         help_text = """
 | |
| Parameter Descriptions:
 | |
| -----------------------
 | |
| bilateral_d          : Neighborhood diameter for bilateral filtering (5-15)
 | |
| bilateral_sigma_color: Filter sigma in color space (50-150)
 | |
| bilateral_sigma_space: Filter sigma in coordinate space (50-150)
 | |
| clahe_clip_limit     : Contrast limit for CLAHE (1.0-4.0)
 | |
| clahe_grid_size      : CLAHE tile grid size (4-16)
 | |
| gamma                : Gamma correction value (0.8-2.0)
 | |
| denoise_h            : Denoising filter strength (5-20)
 | |
| morph_kernel_size    : Morphological operation kernel size (1-5)
 | |
| unsharp_amount       : Unsharp masking amount (0.5-3.0)
 | |
| unsharp_radius       : Unsharp masking radius (0.5-2.0)
 | |
| unsharp_threshold    : Unsharp masking threshold (0-10)
 | |
| 
 | |
| Tips:
 | |
| - Start with small adjustments (±20% of current value)
 | |
| - Test frequently with 'compare' command
 | |
| - Save working configurations before major changes
 | |
| - Use 'test denoise' to test individual steps
 | |
|         """
 | |
|         print(help_text)
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     """Main function for command line usage."""
 | |
|     import argparse
 | |
| 
 | |
|     parser = argparse.ArgumentParser(description="Interactive parameter tuning for newspaper image cleaning")
 | |
|     parser.add_argument("image", help="Sample image path for tuning")
 | |
|     parser.add_argument("-c", "--config", help="Load initial config from file")
 | |
| 
 | |
|     args = parser.parse_args()
 | |
| 
 | |
|     try:
 | |
|         tuner = ParameterTuner(args.image)
 | |
| 
 | |
|         if args.config:
 | |
|             tuner.load_config(args.config)
 | |
| 
 | |
|         tuner.interactive_tune()
 | |
| 
 | |
|     except Exception as e:
 | |
|         print(f"Error: {str(e)}")
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     main() | 
