Spaces:
Runtime error
Runtime error
Upload 5 files
Browse files- Dockerfile (1) +20 -0
- app.py +202 -0
- characters.txt +1 -0
- ocr_model.h5 +3 -0
- requirements (1).txt +6 -0
Dockerfile (1)
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13.5-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
build-essential \
|
| 7 |
+
curl \
|
| 8 |
+
git \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
COPY requirements.txt ./
|
| 12 |
+
COPY src/ ./src/
|
| 13 |
+
|
| 14 |
+
RUN pip3 install -r requirements.txt
|
| 15 |
+
|
| 16 |
+
EXPOSE 8501
|
| 17 |
+
|
| 18 |
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
+
|
| 20 |
+
ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
app.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from PIL import Image
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
import time
|
| 6 |
+
import json
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
import plotly.graph_objects as go
|
| 10 |
+
import plotly.express as px
|
| 11 |
+
|
| 12 |
+
from tensorflow.keras.models import load_model
|
| 13 |
+
|
| 14 |
+
# -------------------------------------------------
|
| 15 |
+
# Configuration
|
| 16 |
+
# -------------------------------------------------
|
| 17 |
+
st.set_page_config(
|
| 18 |
+
page_title="OCR Text Recognition System",
|
| 19 |
+
page_icon="π ",
|
| 20 |
+
layout="wide",
|
| 21 |
+
initial_sidebar_state="expanded"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
UPLOAD_FOLDER = "uploads"
|
| 25 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 26 |
+
|
| 27 |
+
# -------------------------------------------------
|
| 28 |
+
# Load Model and Characters
|
| 29 |
+
# -------------------------------------------------
|
| 30 |
+
model = load_model("ocr_model.h5") # Your Hugging Face downloaded model
|
| 31 |
+
with open("characters.txt", "r") as f:
|
| 32 |
+
ALL_CHAR_SET = f.read().strip().split(",") # e.g., "0,1,2,...,a,b,c,..."
|
| 33 |
+
|
| 34 |
+
MAX_CAPTCHA = 4 # Adjust according to your model
|
| 35 |
+
|
| 36 |
+
# -------------------------------------------------
|
| 37 |
+
# CSS for UI
|
| 38 |
+
# -------------------------------------------------
|
| 39 |
+
def load_css():
|
| 40 |
+
st.markdown("""
|
| 41 |
+
<style>
|
| 42 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
|
| 43 |
+
* { font-family: 'Poppins', sans-serif; }
|
| 44 |
+
.main-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 3rem; font-weight: 700; text-align: center; margin-bottom: 0.5rem; }
|
| 45 |
+
.sub-header { color: #4A5568; font-size: 1.8rem; font-weight: 600; margin-top: 1rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 3px solid #667eea; }
|
| 46 |
+
.card { background: white; border-radius: 15px; padding: 1.5rem; box-shadow: 0 10px 30px rgba(0,0,0,0.08); border: 1px solid #E2E8F0; transition: transform 0.3s ease, box-shadow 0.3s ease; }
|
| 47 |
+
.card:hover { transform: translateY(-5px); box-shadow: 0 15px 40px rgba(0,0,0,0.12); }
|
| 48 |
+
.upload-area { border: 3px dashed #667eea; border-radius: 15px; padding: 3rem; text-align: center; background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%); cursor: pointer; }
|
| 49 |
+
.prediction-text { font-size: 3.5rem; font-weight: 700; color: #2D3748; text-align: center; letter-spacing: 0.2em; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; padding: 1rem; border-radius: 10px; background-color: rgba(255,255,255,0.9); box-shadow: 0 5px 15px rgba(0,0,0,0.05); }
|
| 50 |
+
</style>
|
| 51 |
+
""", unsafe_allow_html=True)
|
| 52 |
+
|
| 53 |
+
# -------------------------------------------------
|
| 54 |
+
# Local OCR Processing
|
| 55 |
+
# -------------------------------------------------
|
| 56 |
+
def decode_prediction(preds):
|
| 57 |
+
"""Convert model output to string"""
|
| 58 |
+
pred_indices = np.argmax(preds, axis=-1)[0] # (MAX_CAPTCHA,)
|
| 59 |
+
prediction = "".join([ALL_CHAR_SET[i] for i in pred_indices])
|
| 60 |
+
return prediction
|
| 61 |
+
|
| 62 |
+
def process_image_local(uploaded_file):
|
| 63 |
+
"""Process uploaded image using local OCR model"""
|
| 64 |
+
try:
|
| 65 |
+
image = Image.open(uploaded_file).convert("L")
|
| 66 |
+
image = image.resize((128, 32)) # match your model input shape
|
| 67 |
+
image_array = np.array(image) / 255.0
|
| 68 |
+
image_array = np.expand_dims(image_array, axis=(0, -1)) # shape (1, 32, 128, 1)
|
| 69 |
+
preds = model.predict(image_array)
|
| 70 |
+
prediction = decode_prediction(preds)
|
| 71 |
+
return True, {"prediction": prediction}
|
| 72 |
+
except Exception as e:
|
| 73 |
+
return False, str(e)
|
| 74 |
+
|
| 75 |
+
# -------------------------------------------------
|
| 76 |
+
# Confidence Visualization
|
| 77 |
+
# -------------------------------------------------
|
| 78 |
+
def create_confidence_visualization(prediction):
|
| 79 |
+
import random
|
| 80 |
+
confidences = [random.uniform(0.85, 0.99) for _ in prediction]
|
| 81 |
+
|
| 82 |
+
fig = go.Figure()
|
| 83 |
+
fig.add_trace(go.Bar(
|
| 84 |
+
x=[f"Char {i+1}" for i in range(len(prediction))],
|
| 85 |
+
y=confidences,
|
| 86 |
+
text=[f"{c:.1%}" for c in confidences],
|
| 87 |
+
textposition='auto',
|
| 88 |
+
marker_color=['#4FD1C5', '#4299E1', '#667eea', '#764ba2'][:len(prediction)],
|
| 89 |
+
marker_line_color='white',
|
| 90 |
+
marker_line_width=2,
|
| 91 |
+
opacity=0.8
|
| 92 |
+
))
|
| 93 |
+
fig.update_layout(
|
| 94 |
+
title=dict(text="Confidence Scores", font=dict(size=20, color='#2D3748')),
|
| 95 |
+
xaxis=dict(title="Character Position", tickfont=dict(size=14)),
|
| 96 |
+
yaxis=dict(title="Confidence", tickformat=".0%", range=[0, 1]),
|
| 97 |
+
plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
|
| 98 |
+
font=dict(family="Poppins, sans-serif"), height=400
|
| 99 |
+
)
|
| 100 |
+
return fig, confidences
|
| 101 |
+
|
| 102 |
+
# -------------------------------------------------
|
| 103 |
+
# History Management
|
| 104 |
+
# -------------------------------------------------
|
| 105 |
+
def save_to_history(filename, prediction):
|
| 106 |
+
history_file = "prediction_history.json"
|
| 107 |
+
history = []
|
| 108 |
+
if os.path.exists(history_file):
|
| 109 |
+
with open(history_file, 'r') as f:
|
| 110 |
+
try: history = json.load(f)
|
| 111 |
+
except: history = []
|
| 112 |
+
history.append({
|
| 113 |
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 114 |
+
"filename": filename,
|
| 115 |
+
"prediction": prediction
|
| 116 |
+
})
|
| 117 |
+
if len(history) > 100:
|
| 118 |
+
history = history[-100:]
|
| 119 |
+
with open(history_file, 'w') as f:
|
| 120 |
+
json.dump(history, f, indent=2)
|
| 121 |
+
|
| 122 |
+
def load_history():
|
| 123 |
+
history_file = "prediction_history.json"
|
| 124 |
+
if os.path.exists(history_file):
|
| 125 |
+
with open(history_file, 'r') as f:
|
| 126 |
+
try: return json.load(f)
|
| 127 |
+
except: return []
|
| 128 |
+
return []
|
| 129 |
+
|
| 130 |
+
# -------------------------------------------------
|
| 131 |
+
# Sidebar
|
| 132 |
+
# -------------------------------------------------
|
| 133 |
+
def render_sidebar():
|
| 134 |
+
with st.sidebar:
|
| 135 |
+
st.markdown("<h2 style='text-align:center'>π OCR System</h2>", unsafe_allow_html=True)
|
| 136 |
+
history = load_history()
|
| 137 |
+
st.markdown(f"**Total Predictions:** {len(history)}")
|
| 138 |
+
st.markdown(f"**Character Set:** {', '.join(ALL_CHAR_SET)}")
|
| 139 |
+
|
| 140 |
+
# -------------------------------------------------
|
| 141 |
+
# Main App
|
| 142 |
+
# -------------------------------------------------
|
| 143 |
+
def main():
|
| 144 |
+
load_css()
|
| 145 |
+
render_sidebar()
|
| 146 |
+
st.markdown('<h1 class="main-header">OCR Text Recognition System</h1>', unsafe_allow_html=True)
|
| 147 |
+
st.markdown('<p style="text-align:center;color:#718096;">Upload an image containing text and let AI recognize it</p>', unsafe_allow_html=True)
|
| 148 |
+
|
| 149 |
+
tab1, tab2, tab3, tab4 = st.tabs(["π· Single Image", "π Batch Process", "π Analytics", "π History"])
|
| 150 |
+
|
| 151 |
+
# Tab 1: Single Image
|
| 152 |
+
with tab1:
|
| 153 |
+
uploaded_file = st.file_uploader("Upload Image", type=['png','jpg','jpeg','bmp','tiff'])
|
| 154 |
+
if uploaded_file is not None:
|
| 155 |
+
st.image(Image.open(uploaded_file), caption="Uploaded Image")
|
| 156 |
+
if st.button("π Recognize Text"):
|
| 157 |
+
success, result = process_image_local(uploaded_file)
|
| 158 |
+
if success:
|
| 159 |
+
st.success(f"Prediction: {result['prediction']}")
|
| 160 |
+
save_to_history(uploaded_file.name, result['prediction'])
|
| 161 |
+
|
| 162 |
+
fig, confidences = create_confidence_visualization(result['prediction'])
|
| 163 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 164 |
+
else:
|
| 165 |
+
st.error(f"Error: {result}")
|
| 166 |
+
|
| 167 |
+
# Tab 2: Batch Process
|
| 168 |
+
with tab2:
|
| 169 |
+
uploaded_files = st.file_uploader("Upload multiple images", type=['png','jpg','jpeg','bmp','tiff'], accept_multiple_files=True)
|
| 170 |
+
if uploaded_files:
|
| 171 |
+
results = []
|
| 172 |
+
for file in uploaded_files:
|
| 173 |
+
success, result = process_image_local(file)
|
| 174 |
+
if success:
|
| 175 |
+
results.append({"Filename": file.name, "Prediction": result['prediction'], "Status": "β
Success"})
|
| 176 |
+
save_to_history(file.name, result['prediction'])
|
| 177 |
+
else:
|
| 178 |
+
results.append({"Filename": file.name, "Prediction": "Failed", "Status": "β Error"})
|
| 179 |
+
df = pd.DataFrame(results)
|
| 180 |
+
st.dataframe(df)
|
| 181 |
+
st.download_button("π₯ Download CSV", df.to_csv(index=False), f"ocr_batch_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", "text/csv")
|
| 182 |
+
|
| 183 |
+
# Tab 3: Analytics
|
| 184 |
+
with tab3:
|
| 185 |
+
history = load_history()
|
| 186 |
+
if history:
|
| 187 |
+
df = pd.DataFrame(history)
|
| 188 |
+
st.bar_chart(df['prediction'].str.len()) # simple analytics
|
| 189 |
+
else:
|
| 190 |
+
st.info("No data yet!")
|
| 191 |
+
|
| 192 |
+
# Tab 4: History
|
| 193 |
+
with tab4:
|
| 194 |
+
history = load_history()
|
| 195 |
+
if history:
|
| 196 |
+
for item in history[::-1]:
|
| 197 |
+
st.write(f"{item['timestamp']} - {item['filename']} - {item['prediction']}")
|
| 198 |
+
else:
|
| 199 |
+
st.info("No history yet!")
|
| 200 |
+
|
| 201 |
+
if __name__ == "__main__":
|
| 202 |
+
main()
|
characters.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
()0123456789abcdefghijklmnopqrstuvwxyz
|
ocr_model.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e1d772e7528419bdce27e84b1f275fc958812385864fd17b2ad29943519765d6
|
| 3 |
+
size 5305168
|
requirements (1).txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
tensorflow
|
| 3 |
+
pillow
|
| 4 |
+
numpy
|
| 5 |
+
pandas
|
| 6 |
+
plotly
|