citoreh commited on
Commit
48b754f
·
verified ·
1 Parent(s): 0d175ba

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +410 -0
app.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Hugging Face Spaces Carpet Weaving Map Generator
2
+ import gradio as gr
3
+ import cv2
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ from PIL import Image, ImageFilter, ImageEnhance, ImageDraw, ImageFont
7
+ import io
8
+ from sklearn.cluster import KMeans
9
+ import pandas as pd
10
+ import tempfile
11
+ import os
12
+
13
+ class CarpetWeavingMap:
14
+ def __init__(self):
15
+ self.original_image = None
16
+ self.knot_map = None
17
+ self.color_palette = None
18
+ self.grid_pattern = None
19
+
20
+ def extract_color_palette(self, image, n_colors=16):
21
+ """Extract dominant colors for carpet knots using K-means clustering"""
22
+ # Convert image to RGB array
23
+ img_array = np.array(image)
24
+ pixels = img_array.reshape(-1, 3)
25
+
26
+ # Use K-means to find dominant colors
27
+ kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=10)
28
+ kmeans.fit(pixels)
29
+
30
+ # Get the colors and sort by frequency
31
+ colors = kmeans.cluster_centers_.astype(int)
32
+ labels = kmeans.labels_
33
+
34
+ # Count frequency of each color
35
+ unique_labels, counts = np.unique(labels, return_counts=True)
36
+
37
+ # Sort colors by frequency (most common first)
38
+ sorted_indices = np.argsort(counts)[::-1]
39
+ self.color_palette = colors[sorted_indices]
40
+
41
+ return self.color_palette
42
+
43
+ def create_knot_grid(self, image, knots_per_cm=10, carpet_width_cm=100, carpet_height_cm=150):
44
+ """Create a grid pattern showing individual knot colors"""
45
+ # Calculate grid dimensions
46
+ grid_width = carpet_width_cm * knots_per_cm
47
+ grid_height = carpet_height_cm * knots_per_cm
48
+
49
+ # Resize image to match grid dimensions
50
+ resized_image = image.resize((grid_width, grid_height), Image.Resampling.LANCZOS)
51
+ img_array = np.array(resized_image)
52
+
53
+ # Extract color palette
54
+ self.extract_color_palette(resized_image)
55
+
56
+ # Create knot map by mapping each pixel to nearest palette color
57
+ self.knot_map = np.zeros((grid_height, grid_width, 3), dtype=np.uint8)
58
+
59
+ for y in range(grid_height):
60
+ for x in range(grid_width):
61
+ pixel = img_array[y, x]
62
+ # Find closest color in palette
63
+ distances = np.sqrt(np.sum((self.color_palette - pixel)**2, axis=1))
64
+ closest_color_idx = np.argmin(distances)
65
+ self.knot_map[y, x] = self.color_palette[closest_color_idx]
66
+
67
+ self.knots_per_cm = knots_per_cm
68
+ self.carpet_width_cm = carpet_width_cm
69
+ self.carpet_height_cm = carpet_height_cm
70
+
71
+ return self.knot_map
72
+
73
+ def draw_grid_overlay(self, knot_map, grid_spacing=10):
74
+ """Draw grid lines over the knot map"""
75
+ grid_image = Image.fromarray(knot_map)
76
+ draw = ImageDraw.Draw(grid_image)
77
+
78
+ height, width = knot_map.shape[:2]
79
+
80
+ # Draw vertical lines (every grid_spacing knots)
81
+ for x in range(0, width, grid_spacing):
82
+ draw.line([(x, 0), (x, height)], fill='black', width=1)
83
+
84
+ # Draw horizontal lines (every grid_spacing knots)
85
+ for y in range(0, height, grid_spacing):
86
+ draw.line([(0, y), (width, y)], fill='black', width=1)
87
+
88
+ return np.array(grid_image)
89
+
90
+ def create_technical_specifications(self):
91
+ """Create technical specifications dictionary"""
92
+ specs = {
93
+ 'Carpet Dimensions': f"{self.carpet_width_cm} x {self.carpet_height_cm} cm",
94
+ 'Knot Density': f"{self.knots_per_cm} knots per cm",
95
+ 'Total Knots Width': f"{self.carpet_width_cm * self.knots_per_cm} knots",
96
+ 'Total Knots Height': f"{self.carpet_height_cm * self.knots_per_cm} knots",
97
+ 'Total Knots': f"{self.carpet_width_cm * self.carpet_height_cm * self.knots_per_cm**2:,} knots",
98
+ 'Color Palette Size': f"{len(self.color_palette)} colors",
99
+ 'Grid Sections (10x10)': f"{(self.carpet_width_cm * self.knots_per_cm)//10} x {(self.carpet_height_cm * self.knots_per_cm)//10}"
100
+ }
101
+ return specs
102
+
103
+ def create_visualization(self, show_grid=True, grid_spacing=10):
104
+ """Create the complete carpet weaving map visualization"""
105
+ if self.knot_map is None:
106
+ return None, None, None
107
+
108
+ # Create figure with subplots
109
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 15))
110
+
111
+ # Main knot map
112
+ if show_grid:
113
+ display_map = self.draw_grid_overlay(self.knot_map, grid_spacing)
114
+ else:
115
+ display_map = self.knot_map
116
+
117
+ ax1.imshow(display_map)
118
+ ax1.set_title(f'Carpet Weaving Map ({self.knots_per_cm} knots/cm)', fontsize=16, fontweight='bold')
119
+ ax1.set_xlabel('Knots (Width)')
120
+ ax1.set_ylabel('Knots (Height)')
121
+
122
+ # Add scale markings
123
+ width_knots = self.carpet_width_cm * self.knots_per_cm
124
+ height_knots = self.carpet_height_cm * self.knots_per_cm
125
+
126
+ # X-axis ticks every 10 cm
127
+ x_ticks = np.arange(0, width_knots + 1, 10 * self.knots_per_cm)
128
+ x_labels = [f"{int(x/(self.knots_per_cm))}cm" for x in x_ticks]
129
+ ax1.set_xticks(x_ticks)
130
+ ax1.set_xticklabels(x_labels)
131
+
132
+ # Y-axis ticks every 10 cm
133
+ y_ticks = np.arange(0, height_knots + 1, 10 * self.knots_per_cm)
134
+ y_labels = [f"{int(y/(self.knots_per_cm))}cm" for y in y_ticks]
135
+ ax1.set_yticks(y_ticks)
136
+ ax1.set_yticklabels(y_labels)
137
+
138
+ # Original image
139
+ ax2.imshow(self.original_image)
140
+ ax2.set_title('Original Image', fontsize=14, fontweight='bold')
141
+ ax2.axis('off')
142
+
143
+ # Color palette
144
+ self.display_color_palette_in_subplot(ax3)
145
+
146
+ # Technical specifications
147
+ self.display_specifications_in_subplot(ax4)
148
+
149
+ plt.tight_layout()
150
+
151
+ # Save the figure
152
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png', dir=tempfile.gettempdir())
153
+ plt.savefig(temp_file.name, dpi=300, bbox_inches='tight')
154
+ plt.close()
155
+
156
+ return temp_file.name
157
+
158
+ def display_color_palette_in_subplot(self, ax):
159
+ """Display color palette in a subplot"""
160
+ if self.color_palette is None:
161
+ return
162
+
163
+ n_colors = len(self.color_palette)
164
+ cols = 4
165
+ rows = (n_colors + cols - 1) // cols
166
+
167
+ for i, color in enumerate(self.color_palette):
168
+ row = i // cols
169
+ col = i % cols
170
+
171
+ rect = plt.Rectangle((col, rows - row - 1), 0.8, 0.8,
172
+ facecolor=color/255.0, edgecolor='black', linewidth=1)
173
+ ax.add_patch(rect)
174
+
175
+ # Add color number
176
+ ax.text(col + 0.4, rows - row - 0.5, f"C{i+1:02d}",
177
+ ha='center', va='center', fontsize=10, fontweight='bold')
178
+
179
+ ax.set_xlim(0, cols)
180
+ ax.set_ylim(0, rows)
181
+ ax.set_aspect('equal')
182
+ ax.axis('off')
183
+ ax.set_title('Color Palette', fontsize=12, fontweight='bold')
184
+
185
+ def display_specifications_in_subplot(self, ax):
186
+ """Display technical specifications in a subplot"""
187
+ specs = self.create_technical_specifications()
188
+
189
+ ax.axis('off')
190
+ ax.set_title('Technical Specifications', fontsize=12, fontweight='bold')
191
+
192
+ y_pos = 0.9
193
+ for key, value in specs.items():
194
+ ax.text(0.1, y_pos, f"{key}:", fontsize=10, fontweight='bold')
195
+ ax.text(0.6, y_pos, value, fontsize=10)
196
+ y_pos -= 0.12
197
+
198
+ ax.set_xlim(0, 1)
199
+ ax.set_ylim(0, 1)
200
+
201
+ def export_pattern_files(self):
202
+ """Export pattern data as downloadable files"""
203
+ if self.knot_map is None:
204
+ return None, None
205
+
206
+ # Create color mapping
207
+ color_map = {}
208
+ for i, color in enumerate(self.color_palette):
209
+ color_map[f"C{i+1:02d}"] = f"RGB({color[0]},{color[1]},{color[2]})"
210
+
211
+ # Create pattern grid with color codes
212
+ height, width = self.knot_map.shape[:2]
213
+ pattern_grid = np.zeros((height, width), dtype='U4')
214
+
215
+ for y in range(height):
216
+ for x in range(width):
217
+ pixel = self.knot_map[y, x]
218
+ # Find matching color in palette
219
+ for i, color in enumerate(self.color_palette):
220
+ if np.array_equal(pixel, color):
221
+ pattern_grid[y, x] = f"C{i+1:02d}"
222
+ break
223
+
224
+ # Save pattern grid as CSV
225
+ csv_file = tempfile.NamedTemporaryFile(delete=False, suffix='.csv', mode='w', dir=tempfile.gettempdir())
226
+ np.savetxt(csv_file.name, pattern_grid, delimiter=',', fmt='%s')
227
+ csv_file.close()
228
+
229
+ # Save color mapping as text file
230
+ txt_file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt', mode='w', dir=tempfile.gettempdir())
231
+ txt_file.write("Carpet Pattern Color Mapping\n")
232
+ txt_file.write("=" * 30 + "\n\n")
233
+ for code, rgb in color_map.items():
234
+ txt_file.write(f"{code}: {rgb}\n")
235
+ txt_file.write(f"\nSpecifications:\n")
236
+ specs = self.create_technical_specifications()
237
+ for key, value in specs.items():
238
+ txt_file.write(f"{key}: {value}\n")
239
+ txt_file.close()
240
+
241
+ return csv_file.name, txt_file.name
242
+
243
+ def process_image(image, knots_per_cm, carpet_width_cm, carpet_height_cm, n_colors, show_grid):
244
+ """Main processing function for Gradio interface"""
245
+ if image is None:
246
+ return None, None, None, "Please upload an image first."
247
+
248
+ try:
249
+ # Create mapper instance
250
+ mapper = CarpetWeavingMap()
251
+ mapper.original_image = Image.fromarray(image).convert('RGB')
252
+
253
+ # Create knot grid
254
+ mapper.create_knot_grid(
255
+ mapper.original_image,
256
+ knots_per_cm=knots_per_cm,
257
+ carpet_width_cm=carpet_width_cm,
258
+ carpet_height_cm=carpet_height_cm
259
+ )
260
+
261
+ # Extract color palette
262
+ mapper.extract_color_palette(mapper.original_image, n_colors=n_colors)
263
+
264
+ # Create visualization
265
+ visualization_path = mapper.create_visualization(show_grid=show_grid)
266
+
267
+ # Export pattern files
268
+ csv_path, txt_path = mapper.export_pattern_files()
269
+
270
+ # Create specifications text
271
+ specs = mapper.create_technical_specifications()
272
+ specs_text = "CARPET WEAVING SPECIFICATIONS:\n" + "="*40 + "\n"
273
+ for key, value in specs.items():
274
+ specs_text += f"{key}: {value}\n"
275
+
276
+ return visualization_path, csv_path, txt_path, specs_text
277
+
278
+ except Exception as e:
279
+ return None, None, None, f"Error processing image: {str(e)}"
280
+
281
+ # Gradio Interface
282
+ def create_gradio_interface():
283
+ """Create the Gradio interface"""
284
+
285
+ title = "🧶 Carpet Weaving Map Generator"
286
+ description = """
287
+ Convert any image into a technical carpet weaving pattern with precise knot mapping and color specifications.
288
+
289
+ **Features:**
290
+ - Generate grid-based knot patterns
291
+ - Customizable knot density (4-20 knots/cm)
292
+ - Color palette extraction and mapping
293
+ - Exportable CSV pattern files
294
+ - Technical specifications for weavers
295
+
296
+ **Instructions:**
297
+ 1. Upload your image
298
+ 2. Adjust carpet dimensions and knot density
299
+ 3. Set number of colors for the palette
300
+ 4. Download the pattern files for weaving
301
+ """
302
+
303
+ with gr.Blocks(title=title, theme=gr.themes.Soft()) as interface:
304
+ gr.Markdown(f"# {title}")
305
+ gr.Markdown(description)
306
+
307
+ with gr.Row():
308
+ with gr.Column(scale=1):
309
+ # Input controls
310
+ image_input = gr.Image(
311
+ label="Upload Image",
312
+ type="numpy"
313
+ )
314
+
315
+ with gr.Row():
316
+ knots_per_cm = gr.Slider(
317
+ minimum=4, maximum=20, value=10, step=1,
318
+ label="Knots per CM (density)"
319
+ )
320
+ n_colors = gr.Slider(
321
+ minimum=8, maximum=32, value=16, step=1,
322
+ label="Number of Colors"
323
+ )
324
+
325
+ with gr.Row():
326
+ carpet_width = gr.Number(
327
+ value=80, label="Carpet Width (cm)", precision=0
328
+ )
329
+ carpet_height = gr.Number(
330
+ value=120, label="Carpet Height (cm)", precision=0
331
+ )
332
+
333
+ show_grid = gr.Checkbox(
334
+ value=True, label="Show Grid Lines"
335
+ )
336
+
337
+ process_btn = gr.Button("Generate Carpet Pattern", variant="primary")
338
+
339
+ with gr.Column(scale=2):
340
+ # Output displays
341
+ output_image = gr.Image(
342
+ label="Carpet Weaving Map",
343
+ type="filepath"
344
+ )
345
+
346
+ specifications = gr.Textbox(
347
+ label="Technical Specifications",
348
+ lines=10,
349
+ max_lines=15
350
+ )
351
+
352
+ with gr.Row():
353
+ # Download files
354
+ csv_download = gr.File(
355
+ label="Download Pattern Grid (CSV)",
356
+ visible=True
357
+ )
358
+ txt_download = gr.File(
359
+ label="Download Color Mapping (TXT)",
360
+ visible=True
361
+ )
362
+
363
+ # Examples
364
+ gr.Examples(
365
+ examples=[
366
+ ["examples/example1.jpg", 10, 80, 120, 16, True],
367
+ ["examples/example2.jpg", 15, 60, 90, 20, True],
368
+ ["examples/example3.jpg", 6, 100, 150, 12, False],
369
+ ],
370
+ inputs=[image_input, knots_per_cm, carpet_width, carpet_height, n_colors, show_grid],
371
+ label="Example Settings"
372
+ )
373
+
374
+ # Processing function
375
+ process_btn.click(
376
+ fn=process_image,
377
+ inputs=[image_input, knots_per_cm, carpet_width, carpet_height, n_colors, show_grid],
378
+ outputs=[output_image, csv_download, txt_download, specifications]
379
+ )
380
+
381
+ # Footer information
382
+ gr.Markdown("""
383
+ ### Knot Density Guide:
384
+ - **Fine Persian Carpets**: 15-20 knots/cm (very detailed)
385
+ - **Standard Quality**: 8-12 knots/cm (good balance)
386
+ - **Rustic/Tribal**: 4-6 knots/cm (traditional style)
387
+
388
+ ### File Outputs:
389
+ - **CSV File**: Grid pattern with color codes for each knot position
390
+ - **TXT File**: Color mapping with RGB values and specifications
391
+ - **PNG Image**: Visual weaving map with grid overlay
392
+ """)
393
+
394
+ return interface
395
+
396
+ # Create and launch the interface
397
+ if __name__ == "__main__":
398
+ interface = create_gradio_interface()
399
+ interface.launch()
400
+
401
+ # Requirements for requirements.txt:
402
+ """
403
+ gradio>=3.40.0
404
+ opencv-python>=4.8.0
405
+ numpy>=1.24.0
406
+ matplotlib>=3.7.0
407
+ Pillow>=10.0.0
408
+ scikit-learn>=1.3.0
409
+ pandas>=2.0.0
410
+ """