#!/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 - Set parameter value")
print(" show - Show current parameters")
print(" test [steps] - Test current parameters")
print(" save - Save configuration to file")
print(" load - 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()