carpet / app.py
citoreh's picture
Update app.py
13fde06 verified
# app.py - Hugging Face Spaces Carpet Weaving Map Generator
import gradio as gr
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageFilter, ImageEnhance, ImageDraw, ImageFont
import io
from sklearn.cluster import KMeans
import pandas as pd
import tempfile
import os
class CarpetWeavingMap:
def __init__(self):
self.original_image = None
self.knot_map = None
self.color_palette = None
self.grid_pattern = None
def extract_color_palette(self, image, n_colors=16):
"""Extract dominant colors for carpet knots using K-means clustering"""
# Convert image to RGB array
img_array = np.array(image)
pixels = img_array.reshape(-1, 3)
# Use K-means to find dominant colors
kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=10)
kmeans.fit(pixels)
# Get the colors and sort by frequency
colors = kmeans.cluster_centers_.astype(int)
labels = kmeans.labels_
# Count frequency of each color
unique_labels, counts = np.unique(labels, return_counts=True)
# Sort colors by frequency (most common first)
sorted_indices = np.argsort(counts)[::-1]
self.color_palette = colors[sorted_indices]
return self.color_palette
def create_knot_grid(self, image, knots_per_cm=10, carpet_width_cm=100, carpet_height_cm=150):
"""Create a grid pattern showing individual knot colors"""
# Calculate grid dimensions
grid_width = carpet_width_cm * knots_per_cm
grid_height = carpet_height_cm * knots_per_cm
# Resize image to match grid dimensions
resized_image = image.resize((grid_width, grid_height), Image.Resampling.LANCZOS)
img_array = np.array(resized_image)
# Extract color palette
self.extract_color_palette(resized_image)
# Create knot map by mapping each pixel to nearest palette color
self.knot_map = np.zeros((grid_height, grid_width, 3), dtype=np.uint8)
for y in range(grid_height):
for x in range(grid_width):
pixel = img_array[y, x]
# Find closest color in palette
distances = np.sqrt(np.sum((self.color_palette - pixel)**2, axis=1))
closest_color_idx = np.argmin(distances)
self.knot_map[y, x] = self.color_palette[closest_color_idx]
self.knots_per_cm = knots_per_cm
self.carpet_width_cm = carpet_width_cm
self.carpet_height_cm = carpet_height_cm
return self.knot_map
def draw_grid_overlay(self, knot_map, grid_spacing=10):
"""Draw grid lines over the knot map"""
grid_image = Image.fromarray(knot_map)
draw = ImageDraw.Draw(grid_image)
height, width = knot_map.shape[:2]
# Draw vertical lines (every grid_spacing knots)
for x in range(0, width, grid_spacing):
draw.line([(x, 0), (x, height)], fill='black', width=1)
# Draw horizontal lines (every grid_spacing knots)
for y in range(0, height, grid_spacing):
draw.line([(0, y), (width, y)], fill='black', width=1)
return np.array(grid_image)
def create_technical_specifications(self):
"""Create technical specifications dictionary"""
specs = {
'Carpet Dimensions': f"{self.carpet_width_cm} x {self.carpet_height_cm} cm",
'Knot Density': f"{self.knots_per_cm} knots per cm",
'Total Knots Width': f"{self.carpet_width_cm * self.knots_per_cm} knots",
'Total Knots Height': f"{self.carpet_height_cm * self.knots_per_cm} knots",
'Total Knots': f"{self.carpet_width_cm * self.carpet_height_cm * self.knots_per_cm**2:,} knots",
'Color Palette Size': f"{len(self.color_palette)} colors",
'Grid Sections (10x10)': f"{(self.carpet_width_cm * self.knots_per_cm)//10} x {(self.carpet_height_cm * self.knots_per_cm)//10}"
}
return specs
def create_visualization(self, show_grid=True, grid_spacing=10):
"""Create the complete carpet weaving map visualization"""
if self.knot_map is None:
return None, None, None
# Create figure with subplots
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 15))
# Main knot map
if show_grid:
display_map = self.draw_grid_overlay(self.knot_map, grid_spacing)
else:
display_map = self.knot_map
ax1.imshow(display_map)
ax1.set_title(f'Carpet Weaving Map ({self.knots_per_cm} knots/cm)', fontsize=16, fontweight='bold')
ax1.set_xlabel('Knots (Width)')
ax1.set_ylabel('Knots (Height)')
# Add scale markings
width_knots = self.carpet_width_cm * self.knots_per_cm
height_knots = self.carpet_height_cm * self.knots_per_cm
# X-axis ticks every 10 cm
x_ticks = np.arange(0, width_knots + 1, 10 * self.knots_per_cm)
x_labels = [f"{int(x/(self.knots_per_cm))}cm" for x in x_ticks]
ax1.set_xticks(x_ticks)
ax1.set_xticklabels(x_labels)
# Y-axis ticks every 10 cm
y_ticks = np.arange(0, height_knots + 1, 10 * self.knots_per_cm)
y_labels = [f"{int(y/(self.knots_per_cm))}cm" for y in y_ticks]
ax1.set_yticks(y_ticks)
ax1.set_yticklabels(y_labels)
# Original image
ax2.imshow(self.original_image)
ax2.set_title('Original Image', fontsize=14, fontweight='bold')
ax2.axis('off')
# Color palette
self.display_color_palette_in_subplot(ax3)
# Technical specifications
self.display_specifications_in_subplot(ax4)
plt.tight_layout()
# Save the figure
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png', dir=tempfile.gettempdir())
plt.savefig(temp_file.name, dpi=300, bbox_inches='tight')
plt.close()
return temp_file.name
def display_color_palette_in_subplot(self, ax):
"""Display color palette in a subplot"""
if self.color_palette is None:
return
n_colors = len(self.color_palette)
cols = 4
rows = (n_colors + cols - 1) // cols
for i, color in enumerate(self.color_palette):
row = i // cols
col = i % cols
rect = plt.Rectangle((col, rows - row - 1), 0.8, 0.8,
facecolor=color/255.0, edgecolor='black', linewidth=1)
ax.add_patch(rect)
# Add color number
ax.text(col + 0.4, rows - row - 0.5, f"C{i+1:02d}",
ha='center', va='center', fontsize=10, fontweight='bold')
ax.set_xlim(0, cols)
ax.set_ylim(0, rows)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title('Color Palette', fontsize=12, fontweight='bold')
def display_specifications_in_subplot(self, ax):
"""Display technical specifications in a subplot"""
specs = self.create_technical_specifications()
ax.axis('off')
ax.set_title('Technical Specifications', fontsize=12, fontweight='bold')
y_pos = 0.9
for key, value in specs.items():
ax.text(0.1, y_pos, f"{key}:", fontsize=10, fontweight='bold')
ax.text(0.6, y_pos, value, fontsize=10)
y_pos -= 0.12
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
def export_pattern_files(self):
"""Export pattern data as downloadable files"""
if self.knot_map is None:
return None, None
# Create color mapping
color_map = {}
for i, color in enumerate(self.color_palette):
color_map[f"C{i+1:02d}"] = f"RGB({color[0]},{color[1]},{color[2]})"
# Create pattern grid with color codes
height, width = self.knot_map.shape[:2]
pattern_grid = np.zeros((height, width), dtype='U4')
for y in range(height):
for x in range(width):
pixel = self.knot_map[y, x]
# Find matching color in palette
for i, color in enumerate(self.color_palette):
if np.array_equal(pixel, color):
pattern_grid[y, x] = f"C{i+1:02d}"
break
# Save pattern grid as CSV
csv_file = tempfile.NamedTemporaryFile(delete=False, suffix='.csv', mode='w', dir=tempfile.gettempdir())
np.savetxt(csv_file.name, pattern_grid, delimiter=',', fmt='%s')
csv_file.close()
# Save color mapping as text file
txt_file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt', mode='w', dir=tempfile.gettempdir())
txt_file.write("Carpet Pattern Color Mapping\n")
txt_file.write("=" * 30 + "\n\n")
for code, rgb in color_map.items():
txt_file.write(f"{code}: {rgb}\n")
txt_file.write(f"\nSpecifications:\n")
specs = self.create_technical_specifications()
for key, value in specs.items():
txt_file.write(f"{key}: {value}\n")
txt_file.close()
return csv_file.name, txt_file.name
def process_image(image, knots_per_cm, carpet_width_cm, carpet_height_cm, n_colors, show_grid):
"""Main processing function for Gradio interface"""
if image is None:
return None, None, None, "Please upload an image first."
try:
# Create mapper instance
mapper = CarpetWeavingMap()
mapper.original_image = Image.fromarray(image).convert('RGB')
# Create knot grid
mapper.create_knot_grid(
mapper.original_image,
knots_per_cm=knots_per_cm,
carpet_width_cm=carpet_width_cm,
carpet_height_cm=carpet_height_cm
)
# Extract color palette
mapper.extract_color_palette(mapper.original_image, n_colors=n_colors)
# Create visualization
visualization_path = mapper.create_visualization(show_grid=show_grid)
# Export pattern files
csv_path, txt_path = mapper.export_pattern_files()
# Create specifications text
specs = mapper.create_technical_specifications()
specs_text = "CARPET WEAVING SPECIFICATIONS:\n" + "="*40 + "\n"
for key, value in specs.items():
specs_text += f"{key}: {value}\n"
return visualization_path, csv_path, txt_path, specs_text
except Exception as e:
return None, None, None, f"Error processing image: {str(e)}"
# Gradio Interface
def create_gradio_interface():
"""Create the Gradio interface"""
title = "🧶 Carpet Weaving Map Generator"
description = """
Convert any image into a technical carpet weaving pattern with precise knot mapping and color specifications.
**Features:**
- Generate grid-based knot patterns
- Customizable knot density (4-20 knots/cm)
- Color palette extraction and mapping
- Exportable CSV pattern files
- Technical specifications for weavers
**Instructions:**
1. Upload your image
2. Adjust carpet dimensions and knot density
3. Set number of colors for the palette
4. Download the pattern files for weaving
"""
with gr.Blocks(title=title, theme=gr.themes.Soft()) as interface:
gr.Markdown(f"# {title}")
gr.Markdown(description)
with gr.Row():
with gr.Column(scale=1):
# Input controls
image_input = gr.Image(
label="Upload Image",
type="numpy"
)
with gr.Row():
knots_per_cm = gr.Slider(
minimum=4, maximum=20, value=10, step=1,
label="Knots per CM (density)"
)
n_colors = gr.Slider(
minimum=8, maximum=32, value=16, step=1,
label="Number of Colors"
)
with gr.Row():
carpet_width = gr.Number(
value=80, label="Carpet Width (cm)", precision=0
)
carpet_height = gr.Number(
value=120, label="Carpet Height (cm)", precision=0
)
show_grid = gr.Checkbox(
value=True, label="Show Grid Lines"
)
process_btn = gr.Button("Generate Carpet Pattern", variant="primary")
with gr.Column(scale=2):
# Output displays
output_image = gr.Image(
label="Carpet Weaving Map",
type="filepath"
)
specifications = gr.Textbox(
label="Technical Specifications",
lines=10,
max_lines=15
)
with gr.Row():
# Download files
csv_download = gr.File(
label="Download Pattern Grid (CSV)",
visible=True
)
txt_download = gr.File(
label="Download Color Mapping (TXT)",
visible=True
)
# Example configurations
gr.Markdown("""
### 💡 Recommended Settings:
**Fine Persian Style**: 15 knots/cm, 60×90 cm, 20 colors
**Standard Quality**: 10 knots/cm, 80×120 cm, 16 colors
**Rustic/Tribal**: 6 knots/cm, 100×150 cm, 12 colors
""")
# Processing function
process_btn.click(
fn=process_image,
inputs=[image_input, knots_per_cm, carpet_width, carpet_height, n_colors, show_grid],
outputs=[output_image, csv_download, txt_download, specifications]
)
# Footer information
gr.Markdown("""
### Knot Density Guide:
- **Fine Persian Carpets**: 15-20 knots/cm (very detailed)
- **Standard Quality**: 8-12 knots/cm (good balance)
- **Rustic/Tribal**: 4-6 knots/cm (traditional style)
### File Outputs:
- **CSV File**: Grid pattern with color codes for each knot position
- **TXT File**: Color mapping with RGB values and specifications
- **PNG Image**: Visual weaving map with grid overlay
""")
return interface
# Create and launch the interface
if __name__ == "__main__":
interface = create_gradio_interface()
interface.launch()
# Requirements for requirements.txt:
"""
gradio>=3.40.0
opencv-python>=4.8.0
numpy>=1.24.0
matplotlib>=3.7.0
Pillow>=10.0.0
scikit-learn>=1.3.0
pandas>=2.0.0
"""