| | |
| | 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""" |
| | |
| | img_array = np.array(image) |
| | pixels = img_array.reshape(-1, 3) |
| | |
| | |
| | kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=10) |
| | kmeans.fit(pixels) |
| | |
| | |
| | colors = kmeans.cluster_centers_.astype(int) |
| | labels = kmeans.labels_ |
| | |
| | |
| | unique_labels, counts = np.unique(labels, return_counts=True) |
| | |
| | |
| | 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""" |
| | |
| | grid_width = carpet_width_cm * knots_per_cm |
| | grid_height = carpet_height_cm * knots_per_cm |
| | |
| | |
| | resized_image = image.resize((grid_width, grid_height), Image.Resampling.LANCZOS) |
| | img_array = np.array(resized_image) |
| | |
| | |
| | self.extract_color_palette(resized_image) |
| | |
| | |
| | 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] |
| | |
| | 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] |
| | |
| | |
| | for x in range(0, width, grid_spacing): |
| | draw.line([(x, 0), (x, height)], fill='black', width=1) |
| | |
| | |
| | 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 |
| | |
| | |
| | fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 15)) |
| | |
| | |
| | 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)') |
| | |
| | |
| | width_knots = self.carpet_width_cm * self.knots_per_cm |
| | height_knots = self.carpet_height_cm * self.knots_per_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_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) |
| | |
| | |
| | ax2.imshow(self.original_image) |
| | ax2.set_title('Original Image', fontsize=14, fontweight='bold') |
| | ax2.axis('off') |
| | |
| | |
| | self.display_color_palette_in_subplot(ax3) |
| | |
| | |
| | self.display_specifications_in_subplot(ax4) |
| | |
| | plt.tight_layout() |
| | |
| | |
| | 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) |
| | |
| | |
| | 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 |
| | |
| | |
| | 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]})" |
| | |
| | |
| | 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] |
| | |
| | for i, color in enumerate(self.color_palette): |
| | if np.array_equal(pixel, color): |
| | pattern_grid[y, x] = f"C{i+1:02d}" |
| | break |
| | |
| | |
| | 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() |
| | |
| | |
| | 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: |
| | |
| | mapper = CarpetWeavingMap() |
| | mapper.original_image = Image.fromarray(image).convert('RGB') |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | mapper.extract_color_palette(mapper.original_image, n_colors=n_colors) |
| | |
| | |
| | visualization_path = mapper.create_visualization(show_grid=show_grid) |
| | |
| | |
| | csv_path, txt_path = mapper.export_pattern_files() |
| | |
| | |
| | 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)}" |
| |
|
| | |
| | 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): |
| | |
| | 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_image = gr.Image( |
| | label="Carpet Weaving Map", |
| | type="filepath" |
| | ) |
| | |
| | specifications = gr.Textbox( |
| | label="Technical Specifications", |
| | lines=10, |
| | max_lines=15 |
| | ) |
| | |
| | with gr.Row(): |
| | |
| | csv_download = gr.File( |
| | label="Download Pattern Grid (CSV)", |
| | visible=True |
| | ) |
| | txt_download = gr.File( |
| | label="Download Color Mapping (TXT)", |
| | visible=True |
| | ) |
| | |
| | |
| | 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 |
| | """) |
| | |
| | |
| | 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] |
| | ) |
| | |
| | |
| | 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 |
| |
|
| | |
| | if __name__ == "__main__": |
| | interface = create_gradio_interface() |
| | interface.launch() |
| |
|
| | |
| | """ |
| | 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 |
| | """ |