import streamlit as st
import cv2
import numpy as np
import networkx as nx
import matplotlib
matplotlib.use('Agg') # Use non-interactive backend
import matplotlib.pyplot as plt
import pandas as pd
import io
import base64
from PIL import Image
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px
import json
# Fix for Hugging Face Spaces permissions
import os
import tempfile
os.environ['STREAMLIT_BROWSER_GATHER_USAGE_STATS'] = 'false'
os.environ['MPLCONFIGDIR'] = tempfile.gettempdir()
# Page configuration - MUST be first Streamlit command
st.set_page_config(
page_title="Kolam Design Analyzer",
page_icon="🎨",
layout="wide",
initial_sidebar_state="expanded"
)
# --- Session state initialization ---
def initialize_session_state():
"""Initialize all session state variables"""
defaults = {
'uploaded_image': None,
'analysis_complete': False,
'analysis_results': {},
'processing': False,
'image_uploaded': False,
'analysis_hash': None,
'cached_figures': {},
'params_changed': False,
'file_hash': None
}
for key, value in defaults.items():
if key not in st.session_state:
st.session_state[key] = value
# Initialize session state
initialize_session_state()
# Custom CSS for professional styling and anti-flicker
st.markdown("""
""", unsafe_allow_html=True)
# Title and header
st.markdown("""
🎨 Kolam Design Analyzer
Smart India Hackathon 2025 - AI-Powered Traditional Art Analysis
Discover the mathematical principles and geometric patterns behind traditional Kolam designs
""", unsafe_allow_html=True)
class KolamAnalyzer:
def __init__(self):
self.cipher = None
self.encryption_key = None
def generate_encryption_key(self):
"""Generate encryption key for graph data"""
try:
from cryptography.fernet import Fernet
self.encryption_key = Fernet.generate_key()
self.cipher = Fernet(self.encryption_key)
return self.encryption_key.decode()
except ImportError:
return "Encryption not available"
def preprocess_image(self, image, size, threshold_val, canny_low, canny_high):
"""Preprocess uploaded image"""
try:
# Convert PIL image to OpenCV format
img_array = np.array(image)
if len(img_array.shape) == 3:
img_gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
else:
img_gray = img_array
# Resize image
img_resized = cv2.resize(img_gray, (size, size))
# Apply binary threshold
_, thresh = cv2.threshold(img_resized, threshold_val, 255, cv2.THRESH_BINARY_INV)
# Edge detection
edges = cv2.Canny(thresh, canny_low, canny_high)
return img_resized, thresh, edges
except Exception as e:
st.error(f"Error in image preprocessing: {str(e)}")
return None, None, None
def detect_nodes(self, edges, max_corners):
"""Detect corner points as graph nodes"""
try:
corners = cv2.goodFeaturesToTrack(
edges,
maxCorners=max_corners,
qualityLevel=0.01,
minDistance=5
)
if corners is None:
return []
return [tuple(pt.ravel()) for pt in corners.astype(int)]
except Exception as e:
return []
def detect_edges(self, edges, nodes, min_line_length):
"""Detect lines and create graph edges"""
try:
lines = cv2.HoughLinesP(
edges,
1,
np.pi/180,
threshold=30,
minLineLength=min_line_length,
maxLineGap=10
)
graph_edges = []
if lines is not None and len(nodes) > 0:
for x1, y1, x2, y2 in lines[:,0]:
n1 = min(range(len(nodes)),
key=lambda i: np.linalg.norm(np.array(nodes[i]) - np.array([x1,y1])))
n2 = min(range(len(nodes)),
key=lambda i: np.linalg.norm(np.array(nodes[i]) - np.array([x2,y2])))
if n1 != n2 and (n1, n2) not in graph_edges and (n2, n1) not in graph_edges:
graph_edges.append((n1, n2))
# Fallback: connect nearby nodes if no lines detected
if len(graph_edges) == 0:
graph_edges = self.connect_nearby_nodes(nodes, max_distance=30)
return graph_edges
except Exception as e:
return []
def connect_nearby_nodes(self, nodes, max_distance=30):
"""Connect nearby nodes as fallback"""
edges = []
for i, (x1, y1) in enumerate(nodes):
for j, (x2, y2) in enumerate(nodes):
if i < j:
distance = np.linalg.norm(np.array([x1, y1]) - np.array([x2, y2]))
if distance <= max_distance:
edges.append((i, j))
return edges
def build_graph(self, nodes, edges):
"""Build NetworkX graph from nodes and edges"""
try:
G = nx.Graph()
for idx, node in enumerate(nodes):
G.add_node(idx, pos=node)
for n1, n2 in edges:
G.add_edge(n1, n2)
return G
except Exception as e:
return nx.Graph()
def extract_graph_features(self, G):
"""Extract mathematical features from the graph"""
try:
num_nodes = G.number_of_nodes()
num_edges = G.number_of_edges()
degrees = [d for _, d in G.degree()]
avg_degree = np.mean(degrees) if degrees else 0
max_degree = max(degrees) if degrees else 0
min_degree = min(degrees) if degrees else 0
# Calculate cycles
try:
num_cycles = sum(1 for c in nx.cycle_basis(G))
except:
num_cycles = 0
# Calculate connectivity
is_connected = nx.is_connected(G) if num_nodes > 0 else False
num_components = nx.number_connected_components(G)
# Calculate centrality measures
try:
betweenness = nx.betweenness_centrality(G)
avg_betweenness = np.mean(list(betweenness.values())) if betweenness else 0
closeness = nx.closeness_centrality(G)
avg_closeness = np.mean(list(closeness.values())) if closeness else 0
except:
avg_betweenness = 0
avg_closeness = 0
return {
"num_nodes": num_nodes,
"num_edges": num_edges,
"avg_degree": round(avg_degree, 2),
"max_degree": max_degree,
"min_degree": min_degree,
"num_cycles": num_cycles,
"is_connected": is_connected,
"num_components": num_components,
"avg_betweenness": round(avg_betweenness, 4),
"avg_closeness": round(avg_closeness, 4),
"density": round(nx.density(G), 4) if num_nodes > 1 else 0
}
except Exception as e:
return {}
# Initialize analyzer - cached to prevent recreation
@st.cache_resource
def get_analyzer():
return KolamAnalyzer()
analyzer = get_analyzer()
# Check library availability
PANDAS_AVAILABLE = True
PLOTLY_AVAILABLE = True
CRYPTO_AVAILABLE = True
try:
import pandas as pd
except ImportError:
PANDAS_AVAILABLE = False
try:
import plotly.graph_objects as go
import plotly.express as px
except ImportError:
PLOTLY_AVAILABLE = False
try:
from cryptography.fernet import Fernet
except ImportError:
CRYPTO_AVAILABLE = False
# Helper function to create stable matplotlib figures
@st.cache_data(hash_funcs={np.ndarray: lambda x: x.tobytes()})
def create_processing_figure(original_img, thresh_img, edges_img):
"""Create cached matplotlib figure for image processing"""
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(original_img, cmap='gray')
axes[0].set_title('Original Grayscale', fontsize=12, fontweight='bold')
axes[0].axis('off')
axes[1].imshow(thresh_img, cmap='gray')
axes[1].set_title('Binary Threshold', fontsize=12, fontweight='bold')
axes[1].axis('off')
axes[2].imshow(edges_img, cmap='gray')
axes[2].set_title('Edge Detection', fontsize=12, fontweight='bold')
axes[2].axis('off')
plt.tight_layout()
return fig
@st.cache_data(hash_funcs={np.ndarray: lambda x: x.tobytes()})
def create_nodes_figure(original_img, nodes):
"""Create cached matplotlib figure for detected nodes"""
img_with_nodes = original_img.copy()
for x, y in nodes:
cv2.circle(img_with_nodes, (int(x), int(y)), 3, (255), -1)
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
ax.imshow(img_with_nodes, cmap='gray')
ax.set_title(f'Detected Nodes: {len(nodes)}', fontsize=14, fontweight='bold')
ax.axis('off')
return fig
@st.cache_data(hash_funcs={nx.Graph: lambda g: str(sorted(g.edges()))})
def create_interactive_graph(G):
"""Create cached interactive graph visualization"""
if G.number_of_nodes() == 0:
return None
pos = nx.get_node_attributes(G, 'pos')
if not pos:
pos = nx.spring_layout(G, seed=42) # Fixed seed for consistency
# Extract edges
edge_x = []
edge_y = []
for edge in G.edges():
x0, y0 = pos[edge[0]]
x1, y1 = pos[edge[1]]
edge_x.extend([x0, x1, None])
edge_y.extend([y0, y1, None])
# Create edge trace
edge_trace = go.Scatter(
x=edge_x, y=edge_y,
line=dict(width=2, color='#FF6B35'),
hoverinfo='none',
mode='lines',
name='Edges'
)
# Extract nodes
node_x = []
node_y = []
node_text = []
node_degree = []
for node in G.nodes():
x, y = pos[node]
node_x.append(x)
node_y.append(y)
degree = G.degree(node)
node_degree.append(degree)
node_text.append(f'Node {node}
Kolam Design Analyzer | Smart India Hackathon 2025
Preserving traditional art through modern technology 🎨✨
""", unsafe_allow_html=True)