|
|
import gradio as gr |
|
|
import tempfile |
|
|
import os |
|
|
from simple_mosaic import SimpleMosaicImage |
|
|
from tile_library import build_cifar10_tile_library, build_cifar100_tile_library |
|
|
from performance import calculate_all_metrics, format_metrics_report |
|
|
|
|
|
MAX_IMAGE_SIZE = 4500 |
|
|
|
|
|
tile_cache = {} |
|
|
|
|
|
def get_tile_library(tile_type, max_per_class): |
|
|
"""Get cached tile library or create new one""" |
|
|
cache_key = f"{tile_type}_{max_per_class}" |
|
|
|
|
|
if cache_key not in tile_cache: |
|
|
if tile_type == "CIFAR-10": |
|
|
tiles, means, labels = build_cifar10_tile_library(max_per_class=max_per_class) |
|
|
elif tile_type == "CIFAR-100": |
|
|
tiles, means, labels = build_cifar100_tile_library(max_per_class=max_per_class) |
|
|
else: |
|
|
return None, None, None |
|
|
|
|
|
tile_cache[cache_key] = (tiles, means, labels) |
|
|
|
|
|
return tile_cache[cache_key] |
|
|
|
|
|
def process_mosaic(image, start_size, min_size, threshold, tile_type, max_per_class, quantize_colors): |
|
|
"""Process image to create mosaic""" |
|
|
if image is None: |
|
|
return None, "Please upload an image first." |
|
|
|
|
|
try: |
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file: |
|
|
image.save(tmp_file.name) |
|
|
|
|
|
|
|
|
loader = SimpleMosaicImage(tmp_file.name) |
|
|
|
|
|
|
|
|
if max(loader.width, loader.height) > MAX_IMAGE_SIZE: |
|
|
loader.resize(MAX_IMAGE_SIZE); |
|
|
|
|
|
|
|
|
if quantize_colors > 0: |
|
|
loader.quantize_colors(quantize_colors) |
|
|
|
|
|
|
|
|
loader.crop_to_grid(2) |
|
|
|
|
|
|
|
|
cells = loader.build_adaptive_cells( |
|
|
start_size=int(start_size), |
|
|
min_size=int(min_size), |
|
|
threshold=float(threshold) |
|
|
) |
|
|
|
|
|
|
|
|
if tile_type == "None (Average Colors)": |
|
|
result_img = loader.mosaic_average_color_adaptive(cells) |
|
|
basic_info = f"Generated mosaic with {len(cells)} adaptive cells using average colors." |
|
|
else: |
|
|
tiles, tile_means, _ = get_tile_library(tile_type, int(max_per_class)) |
|
|
if tiles is None: |
|
|
return None, f"Failed to load {tile_type} tile library." |
|
|
|
|
|
result_img = loader.mosaic_with_tiles_adaptive(cells, tiles, tile_means) |
|
|
basic_info = f"Generated mosaic with {len(cells)} cells using {len(tiles)} {tile_type} tiles." |
|
|
|
|
|
|
|
|
try: |
|
|
metrics = calculate_all_metrics(image, result_img) |
|
|
metrics_report = format_metrics_report(metrics) |
|
|
info = f"{basic_info}\n\n{metrics_report}" |
|
|
except Exception as e: |
|
|
info = f"{basic_info}\n\nMetrics calculation failed: {str(e)}" |
|
|
|
|
|
|
|
|
os.unlink(tmp_file.name) |
|
|
|
|
|
return result_img, info |
|
|
|
|
|
except Exception as e: |
|
|
return None, f"Error processing image: {str(e)}" |
|
|
|
|
|
def update_max_per_class_visibility(tile_type): |
|
|
"""Show/hide max_per_class slider based on tile type""" |
|
|
if tile_type in ["CIFAR-10", "CIFAR-100"]: |
|
|
return gr.update(visible=True) |
|
|
else: |
|
|
return gr.update(visible=False) |
|
|
|
|
|
def get_max_per_class_range(tile_type): |
|
|
"""Get appropriate range for max_per_class slider""" |
|
|
if tile_type == "CIFAR-10": |
|
|
return gr.update(maximum=1000, value=500) |
|
|
elif tile_type == "CIFAR-100": |
|
|
return gr.update(maximum=400, value=200) |
|
|
else: |
|
|
return gr.update(maximum=1000, value=500) |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap'); |
|
|
|
|
|
/* White background everywhere */ |
|
|
* { |
|
|
background: #ffffff !important; |
|
|
background-color: #ffffff !important; |
|
|
font-family: 'Inter', sans-serif !important; |
|
|
} |
|
|
|
|
|
/* Black text everywhere */ |
|
|
* { |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
/* Button exception - keep dark */ |
|
|
.gr-button-primary { |
|
|
background: #000000 !important; |
|
|
background-color: #000000 !important; |
|
|
color: #ffffff !important; |
|
|
border: 1px solid #000000 !important; |
|
|
border-radius: 6px !important; |
|
|
padding: 8px 16px !important; |
|
|
} |
|
|
|
|
|
.gr-button-primary:hover { |
|
|
background: #333333 !important; |
|
|
background-color: #333333 !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
with gr.Blocks(css=custom_css, title="Interactive Image Mosaic Generator") as demo: |
|
|
gr.Markdown("# Interactive Image Mosaic Generator") |
|
|
gr.Markdown("Transform your images into mosaics with adaptive grid technology") |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=1, min_width=300, elem_classes=["control-panel"]): |
|
|
gr.Markdown("### Controls") |
|
|
|
|
|
|
|
|
input_image = gr.Image( |
|
|
label="Upload Image", |
|
|
type="pil", |
|
|
height=200, |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("**Grid Parameters**") |
|
|
start_size = gr.Slider( |
|
|
minimum=16, maximum=128, value=64, step=8, |
|
|
label="Initial Block Size", |
|
|
info="Starting grid size (larger = fewer details)" |
|
|
) |
|
|
|
|
|
min_size = gr.Slider( |
|
|
minimum=2, maximum=32, value=4, step=2, |
|
|
label="Minimum Block Size", |
|
|
info="Smallest allowed grid size" |
|
|
) |
|
|
|
|
|
threshold = gr.Slider( |
|
|
minimum=0.1, maximum=20.0, value=5.0, step=0.1, |
|
|
label="Subdivision Threshold", |
|
|
info="Lower values = more subdivision" |
|
|
) |
|
|
|
|
|
|
|
|
quantize_colors = gr.Slider( |
|
|
minimum=0, maximum=128, value=16, step=1, |
|
|
label="Color Quantization", |
|
|
info="Number of colors (0 = no quantization)" |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("**Tile Library**") |
|
|
tile_type = gr.Radio( |
|
|
choices=["None (Average Colors)", "CIFAR-10", "CIFAR-100"], |
|
|
value="CIFAR-10", |
|
|
label="Tile Type", |
|
|
info="Choose tile source or use average colors" |
|
|
) |
|
|
|
|
|
max_per_class = gr.Slider( |
|
|
minimum=10, maximum=1000, value=500, step=10, |
|
|
label="Tiles per Class", |
|
|
info="Number of tiles per category", |
|
|
visible=True |
|
|
) |
|
|
|
|
|
|
|
|
process_btn = gr.Button( |
|
|
"Generate Mosaic", |
|
|
variant="primary", |
|
|
size="lg" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(scale=2, elem_classes=["image-display"]): |
|
|
gr.Markdown("### Results") |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(): |
|
|
gr.Markdown("**Original**") |
|
|
original_display = gr.Image( |
|
|
label="Original Image", |
|
|
height=400, |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(): |
|
|
gr.Markdown("**Mosaic Result**") |
|
|
result_image = gr.Image( |
|
|
label="Mosaic Image", |
|
|
height=400, |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
|
|
|
info_output = gr.Textbox( |
|
|
label="Processing Info & Performance Metrics", |
|
|
interactive=False, |
|
|
max_lines=10, |
|
|
lines=8 |
|
|
) |
|
|
|
|
|
|
|
|
tile_type.change( |
|
|
fn=update_max_per_class_visibility, |
|
|
inputs=[tile_type], |
|
|
outputs=[max_per_class] |
|
|
) |
|
|
|
|
|
tile_type.change( |
|
|
fn=get_max_per_class_range, |
|
|
inputs=[tile_type], |
|
|
outputs=[max_per_class] |
|
|
) |
|
|
|
|
|
input_image.change( |
|
|
fn=lambda img: img, |
|
|
inputs=[input_image], |
|
|
outputs=[original_display] |
|
|
) |
|
|
|
|
|
process_btn.click( |
|
|
fn=process_mosaic, |
|
|
inputs=[ |
|
|
input_image, start_size, min_size, threshold, |
|
|
tile_type, max_per_class, quantize_colors |
|
|
], |
|
|
outputs=[result_image, info_output] |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |