BODDUSWATHISREE commited on
Commit
397da19
Β·
verified Β·
1 Parent(s): 86a298b

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +561 -37
src/streamlit_app.py CHANGED
@@ -1,40 +1,564 @@
1
- import altair as alt
 
2
  import numpy as np
 
 
3
  import pandas as pd
4
- import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ import streamlit as st
2
+ import cv2
3
  import numpy as np
4
+ import networkx as nx
5
+ import matplotlib.pyplot as plt
6
  import pandas as pd
7
+ from cryptography.fernet import Fernet
8
+ import io
9
+ import base64
10
+ from PIL import Image
11
+ import plotly.graph_objects as go
12
+ from plotly.subplots import make_subplots
13
+ import plotly.express as px
14
+
15
+ # Page configuration
16
+ st.set_page_config(
17
+ page_title="Kolam Design Analyzer",
18
+ page_icon="🎨",
19
+ layout="wide",
20
+ initial_sidebar_state="expanded"
21
+ )
22
+
23
+ # Custom CSS for professional styling
24
+ st.markdown("""
25
+ <style>
26
+ .main-header {
27
+ background: linear-gradient(90deg, #FF6B35 0%, #F7931E 100%);
28
+ padding: 2rem;
29
+ border-radius: 10px;
30
+ margin-bottom: 2rem;
31
+ color: white;
32
+ text-align: center;
33
+ }
34
+
35
+ .metric-card {
36
+ background: white;
37
+ padding: 1rem;
38
+ border-radius: 8px;
39
+ border-left: 4px solid #FF6B35;
40
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
41
+ margin: 0.5rem 0;
42
+ }
43
+
44
+ .analysis-section {
45
+ background: #f8f9fa;
46
+ padding: 1.5rem;
47
+ border-radius: 10px;
48
+ margin: 1rem 0;
49
+ }
50
+
51
+ .upload-section {
52
+ border: 2px dashed #FF6B35;
53
+ padding: 2rem;
54
+ border-radius: 10px;
55
+ text-align: center;
56
+ margin: 1rem 0;
57
+ background: #fff9f7;
58
+ }
59
+
60
+ .stButton > button {
61
+ background: linear-gradient(90deg, #FF6B35 0%, #F7931E 100%);
62
+ color: white;
63
+ border: none;
64
+ border-radius: 5px;
65
+ padding: 0.5rem 2rem;
66
+ font-weight: bold;
67
+ transition: all 0.3s;
68
+ }
69
+
70
+ .stButton > button:hover {
71
+ transform: translateY(-2px);
72
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
73
+ }
74
+ </style>
75
+ """, unsafe_allow_html=True)
76
+
77
+ # Title and header
78
+ st.markdown("""
79
+ <div class="main-header">
80
+ <h1>🎨 Kolam Design Analyzer</h1>
81
+ <h3>Smart India Hackathon 2024 - AI-Powered Traditional Art Analysis</h3>
82
+ <p>Discover the mathematical principles and geometric patterns behind traditional Kolam designs</p>
83
+ </div>
84
+ """, unsafe_allow_html=True)
85
+
86
+ # Sidebar
87
+ with st.sidebar:
88
+ st.markdown("### πŸ”§ Analysis Parameters")
89
+
90
+ image_size = st.slider("Image Processing Size", 128, 512, 256, step=64)
91
+ threshold_value = st.slider("Binary Threshold", 50, 200, 127)
92
+ canny_low = st.slider("Canny Low Threshold", 10, 100, 30)
93
+ canny_high = st.slider("Canny High Threshold", 50, 200, 100)
94
+ max_corners = st.slider("Maximum Corners", 50, 200, 100)
95
+ min_line_length = st.slider("Minimum Line Length", 3, 20, 5)
96
+
97
+ st.markdown("---")
98
+ st.markdown("### πŸ“Š About This Tool")
99
+ st.info("This application uses computer vision and graph theory to analyze traditional Kolam designs, extracting geometric patterns and design principles.")
100
+
101
+ class KolamAnalyzer:
102
+ def __init__(self):
103
+ self.cipher = None
104
+ self.encryption_key = None
105
+
106
+ def generate_encryption_key(self):
107
+ """Generate encryption key for graph data"""
108
+ self.encryption_key = Fernet.generate_key()
109
+ self.cipher = Fernet(self.encryption_key)
110
+ return self.encryption_key.decode()
111
+
112
+ def preprocess_image(self, image, size, threshold_val, canny_low, canny_high):
113
+ """Preprocess uploaded image"""
114
+ # Convert PIL image to OpenCV format
115
+ img_array = np.array(image)
116
+ if len(img_array.shape) == 3:
117
+ img_gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
118
+ else:
119
+ img_gray = img_array
120
+
121
+ # Resize image
122
+ img_resized = cv2.resize(img_gray, (size, size))
123
+
124
+ # Apply binary threshold
125
+ _, thresh = cv2.threshold(img_resized, threshold_val, 255, cv2.THRESH_BINARY_INV)
126
+
127
+ # Edge detection
128
+ edges = cv2.Canny(thresh, canny_low, canny_high)
129
+
130
+ return img_resized, thresh, edges
131
+
132
+ def detect_nodes(self, edges, max_corners):
133
+ """Detect corner points as graph nodes"""
134
+ corners = cv2.goodFeaturesToTrack(
135
+ edges,
136
+ maxCorners=max_corners,
137
+ qualityLevel=0.01,
138
+ minDistance=5
139
+ )
140
+ if corners is None:
141
+ return []
142
+ return [tuple(pt.ravel()) for pt in corners.astype(int)]
143
+
144
+ def detect_edges(self, edges, nodes, min_line_length):
145
+ """Detect lines and create graph edges"""
146
+ lines = cv2.HoughLinesP(
147
+ edges,
148
+ 1,
149
+ np.pi/180,
150
+ threshold=30,
151
+ minLineLength=min_line_length,
152
+ maxLineGap=10
153
+ )
154
+
155
+ graph_edges = []
156
+ if lines is not None:
157
+ for x1, y1, x2, y2 in lines[:,0]:
158
+ if len(nodes) > 0:
159
+ n1 = min(range(len(nodes)),
160
+ key=lambda i: np.linalg.norm(np.array(nodes[i]) - np.array([x1,y1])))
161
+ n2 = min(range(len(nodes)),
162
+ key=lambda i: np.linalg.norm(np.array(nodes[i]) - np.array([x2,y2])))
163
+ if n1 != n2 and (n1, n2) not in graph_edges and (n2, n1) not in graph_edges:
164
+ graph_edges.append((n1, n2))
165
+
166
+ # Fallback: connect nearby nodes if no lines detected
167
+ if len(graph_edges) == 0:
168
+ graph_edges = self.connect_nearby_nodes(nodes, max_distance=30)
169
+
170
+ return graph_edges
171
+
172
+ def connect_nearby_nodes(self, nodes, max_distance=30):
173
+ """Connect nearby nodes as fallback"""
174
+ edges = []
175
+ for i, (x1, y1) in enumerate(nodes):
176
+ for j, (x2, y2) in enumerate(nodes):
177
+ if i < j:
178
+ distance = np.linalg.norm(np.array([x1, y1]) - np.array([x2, y2]))
179
+ if distance <= max_distance:
180
+ edges.append((i, j))
181
+ return edges
182
+
183
+ def build_graph(self, nodes, edges):
184
+ """Build NetworkX graph from nodes and edges"""
185
+ G = nx.Graph()
186
+ for idx, node in enumerate(nodes):
187
+ G.add_node(idx, pos=node)
188
+ for n1, n2 in edges:
189
+ G.add_edge(n1, n2)
190
+ return G
191
+
192
+ def extract_graph_features(self, G):
193
+ """Extract mathematical features from the graph"""
194
+ num_nodes = G.number_of_nodes()
195
+ num_edges = G.number_of_edges()
196
+ degrees = [d for _, d in G.degree()]
197
+ avg_degree = np.mean(degrees) if degrees else 0
198
+ max_degree = max(degrees) if degrees else 0
199
+ min_degree = min(degrees) if degrees else 0
200
+
201
+ # Calculate cycles
202
+ try:
203
+ num_cycles = sum(1 for c in nx.cycle_basis(G))
204
+ except:
205
+ num_cycles = 0
206
+
207
+ # Calculate connectivity
208
+ is_connected = nx.is_connected(G) if num_nodes > 0 else False
209
+ num_components = nx.number_connected_components(G)
210
+
211
+ # Calculate centrality measures
212
+ try:
213
+ betweenness = nx.betweenness_centrality(G)
214
+ avg_betweenness = np.mean(list(betweenness.values())) if betweenness else 0
215
+
216
+ closeness = nx.closeness_centrality(G)
217
+ avg_closeness = np.mean(list(closeness.values())) if closeness else 0
218
+ except:
219
+ avg_betweenness = 0
220
+ avg_closeness = 0
221
+
222
+ return {
223
+ "num_nodes": num_nodes,
224
+ "num_edges": num_edges,
225
+ "avg_degree": round(avg_degree, 2),
226
+ "max_degree": max_degree,
227
+ "min_degree": min_degree,
228
+ "num_cycles": num_cycles,
229
+ "is_connected": is_connected,
230
+ "num_components": num_components,
231
+ "avg_betweenness": round(avg_betweenness, 4),
232
+ "avg_closeness": round(avg_closeness, 4),
233
+ "density": round(nx.density(G), 4) if num_nodes > 1 else 0
234
+ }
235
+
236
+ def encrypt_graph(self, G):
237
+ """Encrypt graph data for security"""
238
+ if not self.cipher:
239
+ self.generate_encryption_key()
240
+
241
+ adj_matrix = nx.to_numpy_array(G)
242
+ adj_bytes = adj_matrix.tobytes()
243
+ encrypted = self.cipher.encrypt(adj_bytes)
244
+ return encrypted
245
+
246
+ def create_interactive_graph(self, G):
247
+ """Create interactive graph visualization using Plotly"""
248
+ pos = nx.get_node_attributes(G, 'pos')
249
+
250
+ if not pos:
251
+ # If no positions, use spring layout
252
+ pos = nx.spring_layout(G)
253
+
254
+ # Extract edges
255
+ edge_x = []
256
+ edge_y = []
257
+ for edge in G.edges():
258
+ x0, y0 = pos[edge[0]]
259
+ x1, y1 = pos[edge[1]]
260
+ edge_x.extend([x0, x1, None])
261
+ edge_y.extend([y0, y1, None])
262
+
263
+ # Create edge trace
264
+ edge_trace = go.Scatter(
265
+ x=edge_x, y=edge_y,
266
+ line=dict(width=2, color='#FF6B35'),
267
+ hoverinfo='none',
268
+ mode='lines'
269
+ )
270
+
271
+ # Extract nodes
272
+ node_x = []
273
+ node_y = []
274
+ node_text = []
275
+ node_degree = []
276
+
277
+ for node in G.nodes():
278
+ x, y = pos[node]
279
+ node_x.append(x)
280
+ node_y.append(y)
281
+ degree = G.degree(node)
282
+ node_degree.append(degree)
283
+ node_text.append(f'Node {node}<br>Degree: {degree}')
284
+
285
+ # Create node trace
286
+ node_trace = go.Scatter(
287
+ x=node_x, y=node_y,
288
+ mode='markers',
289
+ hoverinfo='text',
290
+ text=node_text,
291
+ marker=dict(
292
+ size=[max(10, d*3) for d in node_degree],
293
+ color=node_degree,
294
+ colorscale='Viridis',
295
+ colorbar=dict(
296
+ thickness=15,
297
+ len=0.5,
298
+ x=1.02,
299
+ title="Node Degree"
300
+ ),
301
+ line=dict(width=2, color='white')
302
+ )
303
+ )
304
+
305
+ # Create figure
306
+ fig = go.Figure(data=[edge_trace, node_trace],
307
+ layout=go.Layout(
308
+ title='Interactive Kolam Graph Structure',
309
+ titlefont_size=16,
310
+ showlegend=False,
311
+ hovermode='closest',
312
+ margin=dict(b=20,l=5,r=5,t=40),
313
+ annotations=[ dict(
314
+ text="Node size represents degree centrality",
315
+ showarrow=False,
316
+ xref="paper", yref="paper",
317
+ x=0.005, y=-0.002,
318
+ xanchor="left", yanchor="bottom",
319
+ font=dict(size=12)
320
+ )],
321
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
322
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
323
+ plot_bgcolor='white'
324
+ ))
325
+
326
+ return fig
327
+
328
+ # Initialize analyzer
329
+ analyzer = KolamAnalyzer()
330
+
331
+ # Main content area
332
+ col1, col2 = st.columns([1, 2])
333
+
334
+ try:
335
+ import pandas as pd
336
+ PANDAS_AVAILABLE = True
337
+ except ImportError as e:
338
+ st.warning("⚠️ Pandas not available due to NumPy compatibility. Using basic data structures.")
339
+ PANDAS_AVAILABLE = False
340
+
341
+ try:
342
+ import plotly.graph_objects as go
343
+ import plotly.express as px
344
+ PLOTLY_AVAILABLE = True
345
+ except ImportError as e:
346
+ st.warning("⚠️ Plotly not available due to NumPy compatibility. Using matplotlib for visualizations.")
347
+ PLOTLY_AVAILABLE = False
348
+
349
+ try:
350
+ from cryptography.fernet import Fernet
351
+ CRYPTO_AVAILABLE = True
352
+ except ImportError as e:
353
+ st.warning("⚠️ Cryptography not available. Encryption features disabled.")
354
+ CRYPTO_AVAILABLE = False
355
+
356
+ with col1:
357
+ st.markdown("### πŸ“€ Upload Kolam Image")
358
+
359
+ uploaded_file = st.file_uploader(
360
+ "Choose a Kolam image...",
361
+ type=["png", "jpg", "jpeg"],
362
+ help="Upload a clear image of a Kolam design for analysis"
363
+ )
364
+
365
+ if uploaded_file:
366
+ # Display uploaded image
367
+ image = Image.open(uploaded_file)
368
+ st.image(image, caption="Uploaded Kolam", use_column_width=True)
369
+
370
+ # Analysis button
371
+ if st.button("πŸ” Analyze Kolam Design", type="primary"):
372
+ with st.spinner("Analyzing Kolam design..."):
373
+ # Process image
374
+ original, thresh, edges = analyzer.preprocess_image(
375
+ image, image_size, threshold_value, canny_low, canny_high
376
+ )
377
+
378
+ # Detect nodes and edges
379
+ nodes = analyzer.detect_nodes(edges, max_corners)
380
+ graph_edges = analyzer.detect_edges(edges, nodes, min_line_length)
381
+
382
+ # Build graph
383
+ G = analyzer.build_graph(nodes, graph_edges)
384
+
385
+ # Extract features
386
+ features = analyzer.extract_graph_features(G)
387
+
388
+ # Store results in session state
389
+ st.session_state.analysis_complete = True
390
+ st.session_state.original_img = original
391
+ st.session_state.thresh_img = thresh
392
+ st.session_state.edges_img = edges
393
+ st.session_state.nodes = nodes
394
+ st.session_state.graph = G
395
+ st.session_state.features = features
396
+
397
+ # Generate encryption key
398
+ encryption_key = analyzer.generate_encryption_key()
399
+ st.session_state.encryption_key = encryption_key
400
+
401
+ st.success("βœ… Analysis completed successfully!")
402
+
403
+ with col2:
404
+ st.markdown("### πŸ“Š Analysis Results")
405
+
406
+ if hasattr(st.session_state, 'analysis_complete') and st.session_state.analysis_complete:
407
+
408
+ # Create tabs for different visualizations
409
+ tab1, tab2, tab3, tab4 = st.tabs(["πŸ–ΌοΈ Image Processing", "πŸ“ˆ Graph Analysis", "πŸ“Š Features", "πŸ” Security"])
410
+
411
+ with tab1:
412
+ st.markdown("#### Image Processing Pipeline")
413
+
414
+ # Create subplot for processed images
415
+ fig, axes = plt.subplots(1, 3, figsize=(15, 5))
416
+
417
+ axes[0].imshow(st.session_state.original_img, cmap='gray')
418
+ axes[0].set_title('Original Grayscale', fontsize=12, fontweight='bold')
419
+ axes[0].axis('off')
420
+
421
+ axes[1].imshow(st.session_state.thresh_img, cmap='gray')
422
+ axes[1].set_title('Binary Threshold', fontsize=12, fontweight='bold')
423
+ axes[1].axis('off')
424
+
425
+ axes[2].imshow(st.session_state.edges_img, cmap='gray')
426
+ axes[2].set_title('Edge Detection', fontsize=12, fontweight='bold')
427
+ axes[2].axis('off')
428
+
429
+ plt.tight_layout()
430
+ st.pyplot(fig)
431
+
432
+ # Show detected nodes
433
+ st.markdown("#### Detected Corner Points")
434
+ img_with_nodes = st.session_state.original_img.copy()
435
+ for x, y in st.session_state.nodes:
436
+ cv2.circle(img_with_nodes, (int(x), int(y)), 3, (255), -1)
437
+
438
+ fig_nodes, ax_nodes = plt.subplots(1, 1, figsize=(8, 8))
439
+ ax_nodes.imshow(img_with_nodes, cmap='gray')
440
+ ax_nodes.set_title(f'Detected Nodes: {len(st.session_state.nodes)}',
441
+ fontsize=14, fontweight='bold')
442
+ ax_nodes.axis('off')
443
+ st.pyplot(fig_nodes)
444
+
445
+ with tab2:
446
+ st.markdown("#### Interactive Graph Visualization")
447
+
448
+ # Create interactive graph
449
+ if st.session_state.graph.number_of_nodes() > 0:
450
+ fig_interactive = analyzer.create_interactive_graph(st.session_state.graph)
451
+ st.plotly_chart(fig_interactive, use_container_width=True)
452
+ else:
453
+ st.warning("No graph structure detected in the image.")
454
+
455
+ # Graph statistics
456
+ col_a, col_b = st.columns(2)
457
+ with col_a:
458
+ st.metric("Total Nodes", st.session_state.features['num_nodes'])
459
+ st.metric("Total Edges", st.session_state.features['num_edges'])
460
+ st.metric("Graph Density", st.session_state.features['density'])
461
+
462
+ with col_b:
463
+ st.metric("Average Degree", st.session_state.features['avg_degree'])
464
+ st.metric("Number of Cycles", st.session_state.features['num_cycles'])
465
+ st.metric("Connected Components", st.session_state.features['num_components'])
466
+
467
+ with tab3:
468
+ st.markdown("#### Mathematical Properties")
469
+
470
+ # Create metrics dataframe
471
+ features_df = pd.DataFrame([
472
+ {"Property": "Nodes", "Value": st.session_state.features['num_nodes']},
473
+ {"Property": "Edges", "Value": st.session_state.features['num_edges']},
474
+ {"Property": "Average Degree", "Value": st.session_state.features['avg_degree']},
475
+ {"Property": "Maximum Degree", "Value": st.session_state.features['max_degree']},
476
+ {"Property": "Minimum Degree", "Value": st.session_state.features['min_degree']},
477
+ {"Property": "Cycles", "Value": st.session_state.features['num_cycles']},
478
+ {"Property": "Graph Density", "Value": st.session_state.features['density']},
479
+ {"Property": "Average Betweenness", "Value": st.session_state.features['avg_betweenness']},
480
+ {"Property": "Average Closeness", "Value": st.session_state.features['avg_closeness']},
481
+ {"Property": "Connected", "Value": "Yes" if st.session_state.features['is_connected'] else "No"},
482
+ {"Property": "Components", "Value": st.session_state.features['num_components']}
483
+ ])
484
+
485
+ st.dataframe(features_df, use_container_width=True)
486
+
487
+ # Visualize degree distribution
488
+ if st.session_state.graph.number_of_nodes() > 0:
489
+ degrees = [d for _, d in st.session_state.graph.degree()]
490
+ fig_hist = px.histogram(
491
+ x=degrees,
492
+ title="Degree Distribution",
493
+ labels={'x': 'Node Degree', 'y': 'Frequency'},
494
+ color_discrete_sequence=['#FF6B35']
495
+ )
496
+ fig_hist.update_layout(
497
+ plot_bgcolor='white',
498
+ paper_bgcolor='white'
499
+ )
500
+ st.plotly_chart(fig_hist, use_container_width=True)
501
+
502
+ with tab4:
503
+ st.markdown("#### Security & Data Protection")
504
+
505
+ if CRYPTO_AVAILABLE:
506
+ # Encrypt graph
507
+ encrypted_data = analyzer.encrypt_graph(st.session_state.graph)
508
+
509
+ if encrypted_data:
510
+ col_x, col_y = st.columns(2)
511
+ with col_x:
512
+ st.success(f"πŸ” Graph data encrypted successfully!")
513
+ st.info(f"Encrypted data size: {len(encrypted_data)} bytes")
514
+
515
+ with col_y:
516
+ if hasattr(st.session_state, 'encryption_key'):
517
+ st.code(f"Encryption Key:\n{st.session_state.encryption_key}", language="text")
518
+ else:
519
+ st.error("Failed to encrypt graph data")
520
+ else:
521
+ st.warning("πŸ”’ Encryption not available due to package compatibility issues.")
522
+ st.info("Graph data will be stored in plain text format.")
523
+
524
+ # Download options
525
+ st.markdown("#### πŸ“₯ Download Results")
526
+
527
+ col_dl1, col_dl2 = st.columns(2)
528
+ with col_dl1:
529
+ # Prepare features for download
530
+ if PANDAS_AVAILABLE:
531
+ features_json = pd.DataFrame([st.session_state.features]).to_json(orient='records')
532
+ else:
533
+ import json
534
+ features_json = json.dumps([st.session_state.features], indent=2)
535
+
536
+ st.download_button(
537
+ "πŸ“Š Download Features (JSON)",
538
+ data=features_json,
539
+ file_name="kolam_features.json",
540
+ mime="application/json"
541
+ )
542
+
543
+ with col_dl2:
544
+ # Prepare adjacency matrix
545
+ adj_matrix = nx.to_numpy_array(st.session_state.graph)
546
+ adj_buffer = io.BytesIO()
547
+ np.save(adj_buffer, adj_matrix)
548
+ st.download_button(
549
+ "πŸ”’ Download Adjacency Matrix",
550
+ data=adj_buffer.getvalue(),
551
+ file_name="kolam_adjacency.npy",
552
+ mime="application/octet-stream"
553
+ )
554
+ else:
555
+ st.info("πŸ‘† Please upload a Kolam image and click 'Analyze' to see results")
556
 
557
+ # Footer
558
+ st.markdown("---")
559
+ st.markdown("""
560
+ <div style="text-align: center; color: #666; padding: 1rem;">
561
+ <p><strong>Kolam Design Analyzer</strong> | Smart India Hackathon 2024</p>
562
+ <p>Preserving traditional art through modern technology 🎨✨</p>
563
+ </div>
564
+ """, unsafe_allow_html=True)