# app.py - Optimized for Hugging Face Spaces
import gradio as gr
import tensorflow as tf
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
import matplotlib
matplotlib.use('Agg') # Use non-interactive backend
import matplotlib.pyplot as plt
import io
import time
from datetime import datetime
import base64
# Load the trained model
print("🔄 Loading the trained model...")
try:
model = tf.keras.models.load_model('model.h5')
print("✅ Model loaded successfully!")
except Exception as e:
print(f"❌ Error loading model: {e}")
raise
class DigitRecognizer:
def __init__(self, model):
self.model = model
self.preprocessing_time = 0
self.prediction_time = 0
def preprocess_image(self, image):
"""Optimized preprocessing without OpenCV"""
start_time = time.time()
try:
# Convert to grayscale if needed
if image.mode != 'L':
image = image.convert('L')
# Enhance contrast
enhancer = ImageEnhance.Contrast(image)
image = enhancer.enhance(2.0)
# Apply simple blur for noise reduction (using PIL)
image = image.filter(ImageFilter.SMOOTH_MORE)
# Resize to 28x28 pixels
image = image.resize((28, 28), Image.Resampling.LANCZOS)
# Convert to numpy array
img_array = np.array(image)
# Normalize to 0-1
img_array = img_array.astype('float32') / 255.0
# Simple background detection and inversion
if np.mean(img_array) > 0.5:
img_array = 1.0 - img_array
# Reshape for model
img_array = img_array.reshape(1, 28, 28, 1)
self.preprocessing_time = time.time() - start_time
return img_array
except Exception as e:
print(f"Preprocessing error: {e}")
return None
def predict(self, image):
"""Prediction with confidence analysis"""
start_time = time.time()
try:
if image is None:
return None, None, None
# Preprocess image
processed_image = self.preprocess_image(image)
if processed_image is None:
return None, None, None
# Make prediction
predictions = self.model.predict(processed_image, verbose=0)[0]
# Get top prediction
predicted_digit = np.argmax(predictions)
confidence = np.max(predictions)
# Create confidence data
confidence_data = []
for i, prob in enumerate(predictions):
confidence_data.append({
'digit': i,
'confidence': float(prob),
'percentage': f"{prob:.2%}",
'is_top': i == predicted_digit
})
# Sort by confidence
confidence_data.sort(key=lambda x: x['confidence'], reverse=True)
self.prediction_time = time.time() - start_time
return predicted_digit, confidence, confidence_data
except Exception as e:
print(f"Prediction error: {e}")
return None, None, None
# Initialize recognizer
recognizer = DigitRecognizer(model)
def create_confidence_chart(confidence_data):
"""Create confidence visualization"""
if not confidence_data:
return None
digits = [str(item['digit']) for item in confidence_data]
confidences = [item['confidence'] for item in confidence_data]
colors = ['#FF4B4B' if item['is_top'] else '#4A90E2' for item in confidence_data]
plt.figure(figsize=(10, 5))
bars = plt.bar(digits, confidences, color=colors, alpha=0.8, edgecolor='white', linewidth=2)
# Add value labels
for bar, conf in zip(bars, confidences):
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height + 0.01,
f'{conf:.1%}', ha='center', va='bottom', fontweight='bold', fontsize=10)
plt.ylabel('Confidence Score', fontweight='bold', fontsize=12)
plt.xlabel('Digits', fontweight='bold', fontsize=12)
plt.title('Digit Recognition Confidence', fontsize=14, fontweight='bold', pad=20)
plt.ylim(0, 1)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
# Convert to image
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
buf.seek(0)
plt.close()
return Image.open(buf)
def create_confidence_table(confidence_data):
"""Create HTML confidence table"""
if not confidence_data:
return ""
html = """
🎯 Top Predictions
"""
for item in confidence_data[:3]: # Show top 3
bar_width = int(item['confidence'] * 100)
bar_color = "#FF4B4B" if item['is_top'] else "#4A90E2"
star = "⭐" if item['is_top'] else ""
html += f"""
{star} Digit {item['digit']}
{item['percentage']}
"""
html += "
"
return html
def recognize_digit(image):
"""Main recognition function"""
if image is None:
return "Please draw a digit first", None, "0 ms", "0 ms", ""
digit, confidence, confidence_data = recognizer.predict(image)
if digit is None:
return "Could not process image", None, "0 ms", "0 ms", ""
# Determine confidence level
if confidence > 0.95:
emoji = "🎯"
message = "Excellent match!"
color = "#10B981"
elif confidence > 0.8:
emoji = "✅"
message = "Good recognition!"
color = "#3B82F6"
elif confidence > 0.6:
emoji = "⚠️"
message = "Moderate confidence"
color = "#F59E0B"
else:
emoji = "❓"
message = "Low confidence"
color = "#EF4444"
# Format timing
preprocess_time = f"{recognizer.preprocessing_time*1000:.1f} ms"
predict_time = f"{recognizer.prediction_time*1000:.1f} ms"
# Create HTML output
output = f"""
{emoji}
Recognition Result
Confidence
{confidence:.1%}
Status: {message}
Time: {datetime.now().strftime('%H:%M:%S')}
"""
# Create visualizations
chart_image = create_confidence_chart(confidence_data)
table_html = create_confidence_table(confidence_data)
return output, chart_image, preprocess_time, predict_time, table_html
def clear_all():
"""Clear all inputs and outputs"""
return None, None, "0 ms", "0 ms", ""
# Custom CSS
custom_css = """
.gradio-container {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
}
.main-header {
text-align: center;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
margin-bottom: 2rem;
color: white;
}
.drawing-area {
border: 2px dashed #cbd5e1 !important;
border-radius: 15px !important;
background: #f8fafc !important;
min-height: 350px !important;
}
.predict-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border: none !important;
border-radius: 25px !important;
font-weight: 600 !important;
padding: 12px 24px !important;
transition: transform 0.2s !important;
}
.predict-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3) !important;
}
.clear-btn {
background: white !important;
border: 2px solid #667eea !important;
color: #667eea !important;
border-radius: 25px !important;
font-weight: 600 !important;
padding: 12px 24px !important;
}
.stats-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important;
padding: 15px !important;
border-radius: 12px !important;
border: none !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.05) !important;
}
.footer {
text-align: center;
padding: 20px;
color: #666;
font-size: 0.9em;
margin-top: 40px;
}
"""
# Create Gradio interface
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
# Header
with gr.Row():
with gr.Column():
gr.HTML("""
🔢 Handwritten Digit Recognition
Powered by Deep Learning CNN
""")
with gr.Row():
# Left Column
with gr.Column(scale=1):
gr.Markdown("### 🎨 Draw Your Digit")
input_image = gr.Image(
label="Draw here (0-9)",
image_mode="L",
type="pil",
height=350,
elem_classes=["drawing-area"]
)
with gr.Row():
clear_btn = gr.Button(
"🗑️ Clear",
elem_classes=["clear-btn"],
scale=1
)
predict_btn = gr.Button(
"🚀 Predict Digit",
elem_classes=["predict-btn"],
scale=1
)
# Performance metrics
gr.Markdown("### ⚡ Performance")
with gr.Row():
preprocess_time = gr.Textbox(
label="Preprocessing",
value="0 ms",
interactive=False,
elem_classes=["stats-card"]
)
inference_time = gr.Textbox(
label="Inference",
value="0 ms",
interactive=False,
elem_classes=["stats-card"]
)
# Right Column
with gr.Column(scale=1):
gr.Markdown("### 📊 Results")
output_text = gr.HTML(
label="Prediction",
value="🎨 Draw a digit to see results
"
)
gr.Markdown("### 📈 Confidence Distribution")
confidence_chart = gr.Image(
label="Confidence Chart",
interactive=False,
height=250
)
confidence_table = gr.HTML(
label="Confidence Details",
value=""
)
# Footer
gr.HTML("""
""")
# Event handlers
predict_btn.click(
fn=recognize_digit,
inputs=[input_image],
outputs=[output_text, confidence_chart, preprocess_time, inference_time, confidence_table]
)
clear_btn.click(
fn=clear_all,
inputs=[],
outputs=[input_image, output_text, preprocess_time, inference_time, confidence_table]
)
if __name__ == "__main__":
demo.launch()