AI-Solutions-KK commited on
Commit
e769a12
Β·
unverified Β·
1 Parent(s): 6a8526a

Update app.py

Browse files

APP TEXT DIGITALIS FEATURE ADDED

Files changed (1) hide show
  1. app.py +427 -311
app.py CHANGED
@@ -5,36 +5,175 @@ import pandas as pd
5
  from io import BytesIO
6
  from docx import Document
7
  from docx.shared import Inches
 
 
8
 
9
- # Configure page
10
  st.set_page_config(
11
- page_title="Auto Flowchart Converter",
12
- page_icon="πŸ€–",
13
- layout="wide"
 
14
  )
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def preprocess_image(image):
17
  """Enhanced image preprocessing for better shape detection"""
18
- # Convert to grayscale
19
  if image.mode != 'L':
20
  gray = image.convert('L')
21
  else:
22
  gray = image
23
 
24
- # Enhance contrast
25
  enhancer = ImageEnhance.Contrast(gray)
26
  enhanced = enhancer.enhance(2.0)
27
-
28
- # Apply blur to reduce noise
29
  blurred = enhanced.filter(ImageFilter.GaussianBlur(radius=0.5))
30
-
31
- # Convert to numpy
32
  gray_array = np.array(gray)
33
  blurred_array = np.array(blurred)
34
 
35
- # Adaptive threshold - better for handwritten content
36
- # Simple threshold since we don't have cv2
37
- threshold = np.mean(blurred_array) - 20 # Adaptive based on image
38
  thresh = blurred_array < threshold
39
  thresh = thresh.astype(np.uint8) * 255
40
 
@@ -43,14 +182,11 @@ def preprocess_image(image):
43
  def detect_shapes_and_text(binary_image, original_gray):
44
  """Detect shapes and estimate text content"""
45
  shapes_detected = []
46
-
47
- # Convert to boolean for processing
48
  binary = binary_image > 128
49
  height, width = binary.shape
50
  visited = np.zeros_like(binary, dtype=bool)
51
 
52
  def flood_fill(start_y, start_x):
53
- """Flood fill to find connected components"""
54
  if (start_y < 0 or start_y >= height or
55
  start_x < 0 or start_x >= width or
56
  visited[start_y, start_x] or
@@ -71,7 +207,6 @@ def detect_shapes_and_text(binary_image, original_gray):
71
  visited[y, x] = True
72
  points.append((y, x))
73
 
74
- # Add 8-connected neighbors for better detection
75
  for dy in [-1, 0, 1]:
76
  for dx in [-1, 0, 1]:
77
  if dy != 0 or dx != 0:
@@ -80,25 +215,16 @@ def detect_shapes_and_text(binary_image, original_gray):
80
  return points
81
 
82
  def analyze_shape_type(points, bbox):
83
- """Analyze shape characteristics to determine type"""
84
  min_y, min_x, max_y, max_x = bbox
85
  w = max_x - min_x + 1
86
  h = max_y - min_y + 1
87
  area = len(points)
88
- perimeter_approx = 2 * (w + h) # Rough perimeter
89
-
90
- # Calculate shape metrics
91
  aspect_ratio = w / h if h > 0 else 1
92
  fill_ratio = area / (w * h) if (w * h) > 0 else 0
93
 
94
- # Analyze shape distribution
95
  center_x, center_y = (min_x + max_x) / 2, (min_y + max_y) / 2
96
-
97
- # Calculate how circular the shape is
98
- distances_from_center = []
99
- for y, x in points:
100
- dist = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5
101
- distances_from_center.append(dist)
102
 
103
  if distances_from_center:
104
  avg_distance = np.mean(distances_from_center)
@@ -107,55 +233,25 @@ def detect_shapes_and_text(binary_image, original_gray):
107
  else:
108
  circularity = 0
109
 
110
- # Classify shape
111
  if circularity > 0.7 and fill_ratio > 0.5:
112
  return "oval"
113
  elif aspect_ratio > 2 or aspect_ratio < 0.5:
114
- return "rectangle" # Elongated rectangle
115
  elif 0.8 <= aspect_ratio <= 1.2 and fill_ratio > 0.6:
116
  return "square"
117
- elif fill_ratio < 0.3: # Likely diamond or outline only
118
  return "diamond"
119
  else:
120
  return "rectangle"
121
 
122
- def extract_text_from_region(region_points, original_img):
123
- """Simple text extraction - detect if region likely contains text"""
124
- if not region_points:
125
- return ""
126
-
127
- # Get bounding box
128
- ys, xs = zip(*region_points)
129
- min_y, max_y = min(ys), max(ys)
130
- min_x, max_x = min(xs), max(xs)
131
-
132
- # Extract region
133
- roi = original_img[min_y:max_y+1, min_x:max_x+1]
134
-
135
- # Simple heuristic: if region has moderate density, likely contains text
136
- if roi.size > 0:
137
- density = np.sum(roi < 128) / roi.size
138
- if 0.1 < density < 0.8: # Not too empty, not too full
139
- # Estimate text based on common flowchart terms
140
- area = len(region_points)
141
- if area > 1000:
142
- return "Process Step"
143
- elif area > 500:
144
- return "Decision"
145
- else:
146
- return "Start/End"
147
- return ""
148
-
149
  shape_id = 0
150
 
151
- # Find all connected components (shapes)
152
  for y in range(height):
153
  for x in range(width):
154
  if binary[y, x] and not visited[y, x]:
155
  points = flood_fill(y, x)
156
 
157
- if len(points) > 200: # Minimum size for a flowchart shape
158
- # Calculate bounding box
159
  ys, xs = zip(*points)
160
  min_y, max_y = min(ys), max(ys)
161
  min_x, max_x = min(xs), max(xs)
@@ -163,15 +259,19 @@ def detect_shapes_and_text(binary_image, original_gray):
163
  w = max_x - min_x + 1
164
  h = max_y - min_y + 1
165
 
166
- # Skip very thin lines (likely connectors)
167
  if w < 20 or h < 20:
168
  continue
169
 
170
- # Analyze shape type
171
  shape_type = analyze_shape_type(points, (min_y, min_x, max_y, max_x))
172
 
173
- # Extract text
174
- text_content = extract_text_from_region(points, original_gray)
 
 
 
 
 
 
175
 
176
  shapes_detected.append({
177
  'id': shape_id,
@@ -195,33 +295,29 @@ def create_clean_digital_flowchart(shapes, canvas_width=None, canvas_height=None
195
  if not shapes:
196
  return None
197
 
198
- # Calculate canvas size if not provided
199
  if canvas_width is None or canvas_height is None:
200
  max_x = max([s['x'] + s['width'] for s in shapes]) + 100
201
  max_y = max([s['y'] + s['height'] for s in shapes]) + 100
202
- canvas_width = max(max_x, 800) # Minimum width
203
- canvas_height = max(max_y, 600) # Minimum height
204
 
205
- # Create white canvas
206
  canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
207
  draw = ImageDraw.Draw(canvas)
208
 
209
- # Define colors and styles for professional look
210
  colors = {
211
- 'rectangle': '#E3F2FD', # Light blue
212
- 'square': '#F3E5F5', # Light purple
213
- 'oval': '#E8F5E8', # Light green
214
- 'diamond': '#FFF3E0' # Light orange
215
  }
216
 
217
  border_colors = {
218
- 'rectangle': '#1976D2', # Blue
219
- 'square': '#7B1FA2', # Purple
220
- 'oval': '#388E3C', # Green
221
- 'diamond': '#F57C00' # Orange
222
  }
223
 
224
- # Sort shapes by area (larger shapes first, so smaller ones appear on top)
225
  sorted_shapes = sorted(shapes, key=lambda x: x['area'], reverse=True)
226
 
227
  for shape in sorted_shapes:
@@ -230,300 +326,320 @@ def create_clean_digital_flowchart(shapes, canvas_width=None, canvas_height=None
230
  shape_type = shape['type']
231
  text = shape['text']
232
 
233
- # Get colors
234
  fill_color = colors.get(shape_type, '#F5F5F5')
235
  border_color = border_colors.get(shape_type, '#424242')
236
 
237
- # Draw shape based on type
238
  if shape_type == 'rectangle' or shape_type == 'square':
239
  draw.rectangle([x, y, x + w, y + h],
240
  fill=fill_color, outline=border_color, width=3)
241
-
242
  elif shape_type == 'oval':
243
  draw.ellipse([x, y, x + w, y + h],
244
  fill=fill_color, outline=border_color, width=3)
245
-
246
  elif shape_type == 'diamond':
247
- # Draw diamond shape
248
  points = [
249
- (x + w//2, y), # Top
250
- (x + w, y + h//2), # Right
251
- (x + w//2, y + h), # Bottom
252
- (x, y + h//2) # Left
253
  ]
254
  draw.polygon(points, fill=fill_color, outline=border_color, width=3)
255
 
256
- # Add text with better formatting
257
  if text and text.strip():
258
  try:
259
- # Calculate font size based on shape size
260
  font_size = min(w // max(len(text), 1) + 5, h // 3, 16)
261
- font_size = max(font_size, 10) # Minimum readable size
262
 
263
- # Get text dimensions for centering
264
  text_bbox = draw.textbbox((0, 0), text)
265
  text_width = text_bbox[2] - text_bbox[0]
266
  text_height = text_bbox[3] - text_bbox[1]
267
 
268
- # Center text in shape
269
  text_x = x + (w - text_width) // 2
270
  text_y = y + (h - text_height) // 2
271
 
272
- # Draw text with shadow effect
273
- draw.text((text_x + 1, text_y + 1), text, fill='#CCCCCC') # Shadow
274
- draw.text((text_x, text_y), text, fill='#212121') # Main text
275
 
276
  except Exception:
277
- # Fallback simple text placement
278
  draw.text((x + 5, y + h//2 - 5), text, fill='#212121')
279
 
280
  return canvas
281
 
282
- def export_to_word_comparison(original_image, digital_image):
283
- """Create Word document comparing original and digital versions"""
284
- doc = Document()
285
- doc.add_heading('πŸ€– Automatic Flowchart Conversion', 0)
286
-
287
- # Add description
288
- p = doc.add_paragraph()
289
- p.add_run('Automatically converted handwritten flowchart to clean digital format using AI detection.\n')
290
- p.add_run(f'Generated on: {pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")}')
291
-
292
- # Original image
293
- doc.add_heading('πŸ“ Original Handwritten Version', level=1)
294
- if original_image:
295
- img_buffer = BytesIO()
296
- if original_image.mode != 'RGB':
297
- original_image = original_image.convert('RGB')
298
- original_image.save(img_buffer, format='PNG')
299
- img_buffer.seek(0)
300
- doc.add_picture(img_buffer, width=Inches(6))
301
-
302
- # Digital version
303
- doc.add_heading('✨ Generated Digital Version', level=1)
304
- if digital_image:
305
- digital_buffer = BytesIO()
306
- if digital_image.mode != 'RGB':
307
- digital_image = digital_image.convert('RGB')
308
- digital_image.save(digital_buffer, format='PNG')
309
- digital_buffer.seek(0)
310
- doc.add_picture(digital_buffer, width=Inches(6))
311
-
312
- # Features
313
- doc.add_heading('🎯 Features', level=1)
314
- features = [
315
- "βœ… Automatic shape detection and classification",
316
- "βœ… Professional color scheme and styling",
317
- "βœ… Clean geometric shapes replace hand-drawn ones",
318
- "βœ… Intelligent text placement and sizing",
319
- "βœ… Maintains original layout and flow"
320
- ]
321
-
322
- for feature in features:
323
- doc.add_paragraph(feature)
324
-
325
- # Save to buffer
326
- doc_buffer = BytesIO()
327
- doc.save(doc_buffer)
328
- doc_buffer.seek(0)
329
- return doc_buffer.getvalue()
330
-
331
  def main():
332
- st.title("πŸ€– AI Handwritten to Digital Flowchart Converter")
333
- st.markdown("**Automatically** convert messy handwritten diagrams into professional digital flowcharts!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
  # Initialize session state
 
 
336
  if 'converted' not in st.session_state:
337
  st.session_state.converted = False
338
- if 'original_image' not in st.session_state:
339
- st.session_state.original_image = None
340
- if 'digital_image' not in st.session_state:
341
- st.session_state.digital_image = None
342
- if 'detected_shapes' not in st.session_state:
343
- st.session_state.detected_shapes = []
344
-
345
- # Settings sidebar
346
- st.sidebar.header("βš™οΈ Detection Settings")
347
- min_shape_size = st.sidebar.slider("Minimum Shape Size", 100, 1000, 300)
348
- enhance_contrast = st.sidebar.checkbox("Enhance Contrast", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
 
350
  # File uploader
 
 
 
 
 
 
 
351
  uploaded_file = st.file_uploader(
352
- "πŸ“€ Upload Your Handwritten Flowchart",
353
- type=['jpg', 'jpeg', 'png', 'bmp'],
354
- help="Upload a clear photo or scan of your handwritten flowchart"
355
  )
356
 
357
- if uploaded_file is not None:
358
- # Load image
359
  image = Image.open(uploaded_file)
360
- st.session_state.original_image = image
361
 
362
  col1, col2 = st.columns(2)
363
 
364
  with col1:
365
- st.subheader("πŸ“ Your Handwritten Original")
366
- st.image(image, width=None)
 
367
 
368
- # Control buttons
369
- col_btn1, col_btn2 = st.columns(2)
370
- with col_btn1:
371
- convert_btn = st.button("πŸ€– Auto Convert", type="primary", help="Automatically detect shapes and create digital version")
372
- with col_btn2:
373
- refresh_btn = st.button("πŸ”„ Reset", help="Clear results and start over")
374
-
375
- # Handle button actions
376
- if refresh_btn:
377
- st.session_state.converted = False
378
- st.session_state.digital_image = None
379
- st.session_state.detected_shapes = []
380
- st.rerun()
381
 
382
- if convert_btn:
383
- with st.spinner("πŸ” Analyzing handwritten flowchart..."):
384
- # Preprocess image
385
- processed_img, gray_img = preprocess_image(image)
386
-
387
- # Detect shapes and text
388
- shapes = detect_shapes_and_text(processed_img, gray_img)
389
 
390
- # Filter by size
391
- shapes = [s for s in shapes if s['area'] >= min_shape_size]
 
392
 
393
- st.session_state.detected_shapes = shapes
 
 
 
 
 
 
394
 
395
- if shapes:
396
- with st.spinner("✨ Creating professional digital version..."):
397
- # Generate clean digital flowchart
398
- digital_flowchart = create_clean_digital_flowchart(shapes)
399
- st.session_state.digital_image = digital_flowchart
400
- st.session_state.converted = True
401
 
402
- st.success(f"πŸŽ‰ Successfully converted! Detected {len(shapes)} shapes and created digital flowchart.")
 
 
 
 
 
 
 
 
 
403
  else:
404
- st.warning("❌ No shapes detected. Try adjusting the minimum shape size or upload a clearer image.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
 
406
- with col2:
407
- st.subheader("✨ AI Generated Digital Version")
 
 
 
 
408
 
409
- if st.session_state.converted and st.session_state.digital_image:
410
- st.image(st.session_state.digital_image, width=None)
411
-
412
- # Show what was detected
413
- with st.expander(f"πŸ” Detected {len(st.session_state.detected_shapes)} shapes"):
414
- for i, shape in enumerate(st.session_state.detected_shapes):
415
- st.text(f"{i+1}. {shape['type'].title()} - '{shape['text']}' ({shape['width']}x{shape['height']})")
 
 
 
 
 
 
 
416
 
417
- elif st.session_state.converted and not st.session_state.digital_image:
418
- st.info("No shapes detected in the image. Try adjusting settings.")
419
- else:
420
- st.info("πŸ‘† Click 'Auto Convert' to generate digital version")
421
 
422
- # Export options
423
- if st.session_state.converted and st.session_state.digital_image:
424
- st.subheader("πŸ“₯ Download Your Digital Flowchart")
425
-
426
- col1, col2, col3 = st.columns(3)
427
 
428
- with col1:
429
- # PNG download
430
- png_buffer = BytesIO()
431
- st.session_state.digital_image.save(png_buffer, format='PNG')
432
- png_buffer.seek(0)
433
 
434
- st.download_button(
435
- label="πŸ–ΌοΈ Download PNG",
436
- data=png_buffer.getvalue(),
437
- file_name="digital_flowchart.png",
438
- mime="image/png"
439
- )
440
-
441
- with col2:
442
- # JPG download
443
- jpg_buffer = BytesIO()
444
- rgb_img = st.session_state.digital_image.convert('RGB')
445
- rgb_img.save(jpg_buffer, format='JPEG', quality=95)
446
- jpg_buffer.seek(0)
447
 
448
- st.download_button(
449
- label="πŸ“Έ Download JPG",
450
- data=jpg_buffer.getvalue(),
451
- file_name="digital_flowchart.jpg",
452
- mime="image/jpeg"
453
- )
454
-
455
- with col3:
456
- # Word comparison document
457
- word_doc = export_to_word_comparison(
458
- st.session_state.original_image,
459
- st.session_state.digital_image
460
- )
461
 
462
- st.download_button(
463
- label="πŸ“„ Download Word Report",
464
- data=word_doc,
465
- file_name="flowchart_conversion_report.docx",
466
- mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
467
- )
 
 
 
 
 
 
 
 
 
 
 
468
 
469
- else:
470
- # Instructions when no file uploaded
471
- st.info("πŸ‘† Upload a handwritten flowchart to get started")
472
-
473
- st.subheader("🎯 How It Works:")
474
- st.markdown("""
475
- ### πŸ€– **Fully Automatic Process**
476
- 1. **Upload** your handwritten flowchart photo/scan
477
- 2. **Click "Auto Convert"** - AI does everything automatically:
478
- - πŸ” **Detects** all shapes (rectangles, circles, diamonds)
479
- - πŸ“ **Identifies** text content in each shape
480
- - 🎨 **Creates** clean, professional digital version
481
- - 🌈 **Applies** color coding and professional styling
482
- 3. **Download** your perfect digital flowchart
483
-
484
- ### ✨ **What You Get:**
485
- - **Perfect geometric shapes** instead of hand-drawn ones
486
- - **Professional color scheme** (blue for process, green for start/end, etc.)
487
- - **Clean, readable text** properly centered in shapes
488
- - **Maintains your original layout** and connections
489
- - **Multiple formats** (PNG, JPG, Word document)
490
-
491
- ### πŸ“Έ **Best Results Tips:**
492
- - Use **good lighting** when photographing
493
- - Keep **shapes clearly separated**
494
- - Make sure **text is readable**
495
- - Avoid **shadows and glare**
496
-
497
- **Perfect for:** Converting meeting notes, whiteboard diagrams, paper sketches into presentation-ready flowcharts!
498
- """)
499
-
500
- # Example section
501
- st.subheader("πŸ“‹ Example Use Cases:")
502
- col1, col2, col3 = st.columns(3)
503
-
504
- with col1:
505
- st.markdown("""
506
- **πŸ“ Meeting Notes**
507
- - Whiteboard diagrams
508
- - Brainstorming sessions
509
- - Process mapping
510
- """)
511
-
512
- with col2:
513
- st.markdown("""
514
- **πŸŽ“ Study Materials**
515
- - Hand-drawn flowcharts
516
- - Algorithm diagrams
517
- - Process flows
518
- """)
519
-
520
- with col3:
521
- st.markdown("""
522
- **πŸ’Ό Business Process**
523
- - Workflow sketches
524
- - Decision trees
525
- - System diagrams
526
- """)
527
 
528
  if __name__ == "__main__":
529
  main()
 
5
  from io import BytesIO
6
  from docx import Document
7
  from docx.shared import Inches
8
+ import pytesseract
9
+ import cv2
10
 
11
+ # Configure page with modern styling
12
  st.set_page_config(
13
+ page_title="AI Digitizer Pro",
14
+ page_icon="πŸš€",
15
+ layout="wide",
16
+ initial_sidebar_state="collapsed"
17
  )
18
 
19
+ # Custom CSS for professional look
20
+ st.markdown("""
21
+ <style>
22
+ .main { padding-top: 0rem; }
23
+ .stApp { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
24
+
25
+ .hero-container {
26
+ background: rgba(255, 255, 255, 0.1);
27
+ backdrop-filter: blur(10px);
28
+ border-radius: 20px;
29
+ padding: 2rem;
30
+ margin: 1rem 0;
31
+ border: 1px solid rgba(255, 255, 255, 0.2);
32
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
33
+ }
34
+
35
+ .mode-card {
36
+ background: rgba(255, 255, 255, 0.95);
37
+ border-radius: 15px;
38
+ padding: 1.5rem;
39
+ margin: 1rem 0;
40
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
41
+ border-left: 4px solid #667eea;
42
+ transition: transform 0.3s ease;
43
+ }
44
+
45
+ .mode-card:hover {
46
+ transform: translateY(-5px);
47
+ box-shadow: 0 12px 35px rgba(0, 0, 0, 0.15);
48
+ }
49
+
50
+ .feature-grid {
51
+ display: grid;
52
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
53
+ gap: 1.5rem;
54
+ margin: 2rem 0;
55
+ }
56
+
57
+ .feature-item {
58
+ background: rgba(255, 255, 255, 0.9);
59
+ border-radius: 12px;
60
+ padding: 1.5rem;
61
+ text-align: center;
62
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
63
+ }
64
+
65
+ .upload-zone {
66
+ border: 2px dashed #667eea;
67
+ border-radius: 15px;
68
+ padding: 2rem;
69
+ text-align: center;
70
+ background: rgba(255, 255, 255, 0.9);
71
+ margin: 1rem 0;
72
+ }
73
+
74
+ .result-container {
75
+ background: rgba(255, 255, 255, 0.95);
76
+ border-radius: 15px;
77
+ padding: 1.5rem;
78
+ margin: 1rem 0;
79
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
80
+ }
81
+
82
+ .stButton > button {
83
+ background: linear-gradient(45deg, #667eea, #764ba2);
84
+ color: white;
85
+ border: none;
86
+ border-radius: 25px;
87
+ padding: 0.5rem 2rem;
88
+ font-weight: 600;
89
+ transition: all 0.3s ease;
90
+ }
91
+
92
+ .stButton > button:hover {
93
+ transform: translateY(-2px);
94
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
95
+ }
96
+
97
+ .metric-card {
98
+ background: linear-gradient(45deg, #667eea, #764ba2);
99
+ color: white;
100
+ border-radius: 12px;
101
+ padding: 1rem;
102
+ text-align: center;
103
+ margin: 0.5rem;
104
+ }
105
+ </style>
106
+ """, unsafe_allow_html=True)
107
+
108
+ def preprocess_for_ocr(image):
109
+ """Enhanced preprocessing for OCR"""
110
+ # Convert to OpenCV format
111
+ opencv_img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
112
+ gray = cv2.cvtColor(opencv_img, cv2.COLOR_BGR2GRAY)
113
+
114
+ # Noise removal
115
+ denoised = cv2.fastNlMeansDenoising(gray)
116
+
117
+ # Thresholding
118
+ _, thresh = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
119
+
120
+ # Morphological operations
121
+ kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
122
+ processed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
123
+
124
+ return Image.fromarray(processed)
125
+
126
+ def extract_text_with_ocr(image):
127
+ """Extract text using Tesseract OCR"""
128
+ try:
129
+ # Preprocess image for better OCR
130
+ processed_img = preprocess_for_ocr(image)
131
+
132
+ # Configure Tesseract
133
+ custom_config = r'--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,!?;:()[]{}/"\'- '
134
+
135
+ # Extract text
136
+ text = pytesseract.image_to_string(processed_img, config=custom_config)
137
+
138
+ # Get word-level data for positioning
139
+ data = pytesseract.image_to_data(processed_img, output_type=pytesseract.Output.DICT)
140
+
141
+ return text.strip(), data
142
+ except Exception as e:
143
+ st.error(f"OCR Error: {str(e)}")
144
+ return "", None
145
+
146
+ def create_digital_text_document(text, confidence_data=None):
147
+ """Create a clean digital version of extracted text"""
148
+ if not text:
149
+ return None
150
+
151
+ # Create a simple formatted document
152
+ lines = text.split('\n')
153
+ formatted_lines = []
154
+
155
+ for line in lines:
156
+ line = line.strip()
157
+ if line:
158
+ formatted_lines.append(line)
159
+
160
+ return '\n'.join(formatted_lines)
161
+
162
+ # [Keep the existing diagram processing functions]
163
  def preprocess_image(image):
164
  """Enhanced image preprocessing for better shape detection"""
 
165
  if image.mode != 'L':
166
  gray = image.convert('L')
167
  else:
168
  gray = image
169
 
 
170
  enhancer = ImageEnhance.Contrast(gray)
171
  enhanced = enhancer.enhance(2.0)
 
 
172
  blurred = enhanced.filter(ImageFilter.GaussianBlur(radius=0.5))
 
 
173
  gray_array = np.array(gray)
174
  blurred_array = np.array(blurred)
175
 
176
+ threshold = np.mean(blurred_array) - 20
 
 
177
  thresh = blurred_array < threshold
178
  thresh = thresh.astype(np.uint8) * 255
179
 
 
182
  def detect_shapes_and_text(binary_image, original_gray):
183
  """Detect shapes and estimate text content"""
184
  shapes_detected = []
 
 
185
  binary = binary_image > 128
186
  height, width = binary.shape
187
  visited = np.zeros_like(binary, dtype=bool)
188
 
189
  def flood_fill(start_y, start_x):
 
190
  if (start_y < 0 or start_y >= height or
191
  start_x < 0 or start_x >= width or
192
  visited[start_y, start_x] or
 
207
  visited[y, x] = True
208
  points.append((y, x))
209
 
 
210
  for dy in [-1, 0, 1]:
211
  for dx in [-1, 0, 1]:
212
  if dy != 0 or dx != 0:
 
215
  return points
216
 
217
  def analyze_shape_type(points, bbox):
 
218
  min_y, min_x, max_y, max_x = bbox
219
  w = max_x - min_x + 1
220
  h = max_y - min_y + 1
221
  area = len(points)
 
 
 
222
  aspect_ratio = w / h if h > 0 else 1
223
  fill_ratio = area / (w * h) if (w * h) > 0 else 0
224
 
 
225
  center_x, center_y = (min_x + max_x) / 2, (min_y + max_y) / 2
226
+ distances_from_center = [((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5
227
+ for y, x in points]
 
 
 
 
228
 
229
  if distances_from_center:
230
  avg_distance = np.mean(distances_from_center)
 
233
  else:
234
  circularity = 0
235
 
 
236
  if circularity > 0.7 and fill_ratio > 0.5:
237
  return "oval"
238
  elif aspect_ratio > 2 or aspect_ratio < 0.5:
239
+ return "rectangle"
240
  elif 0.8 <= aspect_ratio <= 1.2 and fill_ratio > 0.6:
241
  return "square"
242
+ elif fill_ratio < 0.3:
243
  return "diamond"
244
  else:
245
  return "rectangle"
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  shape_id = 0
248
 
 
249
  for y in range(height):
250
  for x in range(width):
251
  if binary[y, x] and not visited[y, x]:
252
  points = flood_fill(y, x)
253
 
254
+ if len(points) > 200:
 
255
  ys, xs = zip(*points)
256
  min_y, max_y = min(ys), max(ys)
257
  min_x, max_x = min(xs), max(xs)
 
259
  w = max_x - min_x + 1
260
  h = max_y - min_y + 1
261
 
 
262
  if w < 20 or h < 20:
263
  continue
264
 
 
265
  shape_type = analyze_shape_type(points, (min_y, min_x, max_y, max_x))
266
 
267
+ # Simple text estimation
268
+ area = len(points)
269
+ if area > 1000:
270
+ text_content = "Process Step"
271
+ elif area > 500:
272
+ text_content = "Decision"
273
+ else:
274
+ text_content = "Start/End"
275
 
276
  shapes_detected.append({
277
  'id': shape_id,
 
295
  if not shapes:
296
  return None
297
 
 
298
  if canvas_width is None or canvas_height is None:
299
  max_x = max([s['x'] + s['width'] for s in shapes]) + 100
300
  max_y = max([s['y'] + s['height'] for s in shapes]) + 100
301
+ canvas_width = max(max_x, 800)
302
+ canvas_height = max(max_y, 600)
303
 
 
304
  canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
305
  draw = ImageDraw.Draw(canvas)
306
 
 
307
  colors = {
308
+ 'rectangle': '#E3F2FD',
309
+ 'square': '#F3E5F5',
310
+ 'oval': '#E8F5E8',
311
+ 'diamond': '#FFF3E0'
312
  }
313
 
314
  border_colors = {
315
+ 'rectangle': '#1976D2',
316
+ 'square': '#7B1FA2',
317
+ 'oval': '#388E3C',
318
+ 'diamond': '#F57C00'
319
  }
320
 
 
321
  sorted_shapes = sorted(shapes, key=lambda x: x['area'], reverse=True)
322
 
323
  for shape in sorted_shapes:
 
326
  shape_type = shape['type']
327
  text = shape['text']
328
 
 
329
  fill_color = colors.get(shape_type, '#F5F5F5')
330
  border_color = border_colors.get(shape_type, '#424242')
331
 
 
332
  if shape_type == 'rectangle' or shape_type == 'square':
333
  draw.rectangle([x, y, x + w, y + h],
334
  fill=fill_color, outline=border_color, width=3)
 
335
  elif shape_type == 'oval':
336
  draw.ellipse([x, y, x + w, y + h],
337
  fill=fill_color, outline=border_color, width=3)
 
338
  elif shape_type == 'diamond':
 
339
  points = [
340
+ (x + w//2, y),
341
+ (x + w, y + h//2),
342
+ (x + w//2, y + h),
343
+ (x, y + h//2)
344
  ]
345
  draw.polygon(points, fill=fill_color, outline=border_color, width=3)
346
 
 
347
  if text and text.strip():
348
  try:
 
349
  font_size = min(w // max(len(text), 1) + 5, h // 3, 16)
350
+ font_size = max(font_size, 10)
351
 
 
352
  text_bbox = draw.textbbox((0, 0), text)
353
  text_width = text_bbox[2] - text_bbox[0]
354
  text_height = text_bbox[3] - text_bbox[1]
355
 
 
356
  text_x = x + (w - text_width) // 2
357
  text_y = y + (h - text_height) // 2
358
 
359
+ draw.text((text_x + 1, text_y + 1), text, fill='#CCCCCC')
360
+ draw.text((text_x, text_y), text, fill='#212121')
 
361
 
362
  except Exception:
 
363
  draw.text((x + 5, y + h//2 - 5), text, fill='#212121')
364
 
365
  return canvas
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  def main():
368
+ # Hero Section
369
+ st.markdown("""
370
+ <div class="hero-container">
371
+ <h1 style="text-align: center; color: white; font-size: 3rem; margin-bottom: 1rem;">
372
+ πŸš€ AI Digitizer Pro
373
+ </h1>
374
+ <p style="text-align: center; color: rgba(255,255,255,0.9); font-size: 1.2rem;">
375
+ Transform handwritten content into professional digital formats with AI
376
+ </p>
377
+ </div>
378
+ """, unsafe_allow_html=True)
379
+
380
+ # Mode Selection
381
+ st.markdown("### 🎯 Choose Your Digitization Mode")
382
+
383
+ col1, col2 = st.columns(2)
384
+
385
+ with col1:
386
+ st.markdown("""
387
+ <div class="mode-card">
388
+ <h3>πŸ“ Text Digitizer</h3>
389
+ <p>Convert handwritten notes, documents, and text into clean digital format using advanced OCR technology.</p>
390
+ <ul>
391
+ <li>✨ Advanced OCR recognition</li>
392
+ <li>πŸ“„ Multiple output formats</li>
393
+ <li>πŸ”§ Text cleanup & formatting</li>
394
+ <li>πŸ“Š Confidence analysis</li>
395
+ </ul>
396
+ </div>
397
+ """, unsafe_allow_html=True)
398
+
399
+ text_mode = st.button("πŸš€ Launch Text Digitizer", key="text_btn", type="primary")
400
+
401
+ with col2:
402
+ st.markdown("""
403
+ <div class="mode-card">
404
+ <h3>πŸ”„ Diagram Digitizer</h3>
405
+ <p>Transform hand-drawn flowcharts and diagrams into professional digital versions with perfect geometry.</p>
406
+ <ul>
407
+ <li>πŸ€– AI shape detection</li>
408
+ <li>🎨 Professional styling</li>
409
+ <li>πŸ“ Perfect geometry</li>
410
+ <li>🌈 Color-coded elements</li>
411
+ </ul>
412
+ </div>
413
+ """, unsafe_allow_html=True)
414
+
415
+ diagram_mode = st.button("πŸš€ Launch Diagram Digitizer", key="diagram_btn", type="primary")
416
 
417
  # Initialize session state
418
+ if 'mode' not in st.session_state:
419
+ st.session_state.mode = None
420
  if 'converted' not in st.session_state:
421
  st.session_state.converted = False
422
+
423
+ # Set mode based on button clicks
424
+ if text_mode:
425
+ st.session_state.mode = 'text'
426
+ st.session_state.converted = False
427
+ elif diagram_mode:
428
+ st.session_state.mode = 'diagram'
429
+ st.session_state.converted = False
430
+
431
+ # Display selected mode interface
432
+ if st.session_state.mode == 'text':
433
+ text_digitizer_interface()
434
+ elif st.session_state.mode == 'diagram':
435
+ diagram_digitizer_interface()
436
+ else:
437
+ # Show features when no mode selected
438
+ show_features()
439
+
440
+ def text_digitizer_interface():
441
+ st.markdown("---")
442
+ st.markdown("## πŸ“ Text Digitizer Mode")
443
+
444
+ col1, col2 = st.columns([1, 3])
445
+ with col1:
446
+ if st.button("← Back to Home", type="secondary"):
447
+ st.session_state.mode = None
448
+ st.rerun()
449
 
450
  # File uploader
451
+ st.markdown("""
452
+ <div class="upload-zone">
453
+ <h3>πŸ“€ Upload Your Handwritten Text</h3>
454
+ <p>Supports: JPG, PNG, PDF, and more</p>
455
+ </div>
456
+ """, unsafe_allow_html=True)
457
+
458
  uploaded_file = st.file_uploader(
459
+ "Choose file",
460
+ type=['jpg', 'jpeg', 'png', 'bmp', 'tiff'],
461
+ label_visibility="collapsed"
462
  )
463
 
464
+ if uploaded_file:
 
465
  image = Image.open(uploaded_file)
 
466
 
467
  col1, col2 = st.columns(2)
468
 
469
  with col1:
470
+ st.markdown("""<div class="result-container">""", unsafe_allow_html=True)
471
+ st.markdown("#### πŸ“„ Original Document")
472
+ st.image(image, use_column_width=True)
473
 
474
+ if st.button("πŸ€– Extract Text", type="primary"):
475
+ with st.spinner("πŸ” Analyzing text with AI OCR..."):
476
+ extracted_text, confidence_data = extract_text_with_ocr(image)
477
+
478
+ if extracted_text:
479
+ st.session_state.extracted_text = extracted_text
480
+ st.session_state.confidence_data = confidence_data
481
+ st.session_state.converted = True
482
+ st.success("βœ… Text extraction completed!")
483
+ else:
484
+ st.error("❌ No text detected. Try a clearer image.")
485
+
486
+ st.markdown("</div>", unsafe_allow_html=True)
487
 
488
+ with col2:
489
+ st.markdown("""<div class="result-container">""", unsafe_allow_html=True)
490
+ st.markdown("#### ✨ Digitized Text")
491
+
492
+ if hasattr(st.session_state, 'extracted_text') and st.session_state.extracted_text:
493
+ # Show extracted text
494
+ st.text_area("Extracted Text:", st.session_state.extracted_text, height=300)
495
 
496
+ # Download options
497
+ st.markdown("#### πŸ“₯ Download Options")
498
+ col_a, col_b = st.columns(2)
499
 
500
+ with col_a:
501
+ st.download_button(
502
+ "πŸ“„ Download as TXT",
503
+ st.session_state.extracted_text,
504
+ file_name="extracted_text.txt",
505
+ mime="text/plain"
506
+ )
507
 
508
+ with col_b:
509
+ # Create Word document
510
+ doc = Document()
511
+ doc.add_heading('Extracted Text', 0)
512
+ doc.add_paragraph(st.session_state.extracted_text)
 
513
 
514
+ doc_buffer = BytesIO()
515
+ doc.save(doc_buffer)
516
+ doc_buffer.seek(0)
517
+
518
+ st.download_button(
519
+ "πŸ“„ Download as DOCX",
520
+ doc_buffer.getvalue(),
521
+ file_name="extracted_text.docx",
522
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
523
+ )
524
  else:
525
+ st.info("πŸ‘† Upload an image and click 'Extract Text' to see results")
526
+
527
+ st.markdown("</div>", unsafe_allow_html=True)
528
+
529
+ def diagram_digitizer_interface():
530
+ st.markdown("---")
531
+ st.markdown("## πŸ”„ Diagram Digitizer Mode")
532
+
533
+ col1, col2 = st.columns([1, 3])
534
+ with col1:
535
+ if st.button("← Back to Home", type="secondary"):
536
+ st.session_state.mode = None
537
+ st.rerun()
538
+
539
+ # Settings
540
+ with st.expander("βš™οΈ Advanced Settings"):
541
+ min_shape_size = st.slider("Minimum Shape Size", 100, 1000, 300)
542
+
543
+ # File uploader
544
+ st.markdown("""
545
+ <div class="upload-zone">
546
+ <h3>πŸ“€ Upload Your Hand-drawn Diagram</h3>
547
+ <p>Flowcharts, process diagrams, mind maps, etc.</p>
548
+ </div>
549
+ """, unsafe_allow_html=True)
550
+
551
+ uploaded_file = st.file_uploader(
552
+ "Choose file",
553
+ type=['jpg', 'jpeg', 'png', 'bmp'],
554
+ label_visibility="collapsed"
555
+ )
556
+
557
+ if uploaded_file:
558
+ image = Image.open(uploaded_file)
559
 
560
+ col1, col2 = st.columns(2)
561
+
562
+ with col1:
563
+ st.markdown("""<div class="result-container">""", unsafe_allow_html=True)
564
+ st.markdown("#### πŸ“ Original Diagram")
565
+ st.image(image, use_column_width=True)
566
 
567
+ if st.button("πŸ€– Convert to Digital", type="primary"):
568
+ with st.spinner("πŸ” Analyzing diagram structure..."):
569
+ processed_img, gray_img = preprocess_image(image)
570
+ shapes = detect_shapes_and_text(processed_img, gray_img)
571
+ shapes = [s for s in shapes if s['area'] >= min_shape_size]
572
+
573
+ if shapes:
574
+ digital_flowchart = create_clean_digital_flowchart(shapes)
575
+ st.session_state.digital_image = digital_flowchart
576
+ st.session_state.detected_shapes = shapes
577
+ st.session_state.converted = True
578
+ st.success(f"βœ… Converted! Detected {len(shapes)} shapes")
579
+ else:
580
+ st.warning("❌ No shapes detected. Try adjusting settings.")
581
 
582
+ st.markdown("</div>", unsafe_allow_html=True)
 
 
 
583
 
584
+ with col2:
585
+ st.markdown("""<div class="result-container">""", unsafe_allow_html=True)
586
+ st.markdown("#### ✨ Digital Version")
 
 
587
 
588
+ if hasattr(st.session_state, 'digital_image') and st.session_state.digital_image:
589
+ st.image(st.session_state.digital_image, use_column_width=True)
 
 
 
590
 
591
+ # Download options
592
+ st.markdown("#### πŸ“₯ Download Options")
593
+ col_a, col_b = st.columns(2)
 
 
 
 
 
 
 
 
 
 
594
 
595
+ with col_a:
596
+ png_buffer = BytesIO()
597
+ st.session_state.digital_image.save(png_buffer, format='PNG')
598
+ st.download_button(
599
+ "πŸ–ΌοΈ Download PNG",
600
+ png_buffer.getvalue(),
601
+ file_name="digital_diagram.png",
602
+ mime="image/png"
603
+ )
 
 
 
 
604
 
605
+ with col_b:
606
+ jpg_buffer = BytesIO()
607
+ rgb_img = st.session_state.digital_image.convert('RGB')
608
+ rgb_img.save(jpg_buffer, format='JPEG', quality=95)
609
+ st.download_button(
610
+ "πŸ“Έ Download JPG",
611
+ jpg_buffer.getvalue(),
612
+ file_name="digital_diagram.jpg",
613
+ mime="image/jpeg"
614
+ )
615
+ else:
616
+ st.info("πŸ‘† Upload a diagram and click 'Convert to Digital'")
617
+
618
+ st.markdown("</div>", unsafe_allow_html=True)
619
+
620
+ def show_features():
621
+ st.markdown("### ✨ Why Choose AI Digitizer Pro?")
622
 
623
+ st.markdown("""
624
+ <div class="feature-grid">
625
+ <div class="feature-item">
626
+ <h3>πŸ€– AI-Powered</h3>
627
+ <p>Advanced machine learning algorithms for accurate recognition and conversion</p>
628
+ </div>
629
+ <div class="feature-item">
630
+ <h3>⚑ Lightning Fast</h3>
631
+ <p>Process images in seconds with optimized performance</p>
632
+ </div>
633
+ <div class="feature-item">
634
+ <h3>🎨 Professional Output</h3>
635
+ <p>Clean, polished results ready for presentations and documents</p>
636
+ </div>
637
+ <div class="feature-item">
638
+ <h3>πŸ“± Multi-Format</h3>
639
+ <p>Export to PNG, JPG, TXT, DOCX, and more formats</p>
640
+ </div>
641
+ </div>
642
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
 
644
  if __name__ == "__main__":
645
  main()