can you make my frontend and backend more professional
my html is
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-Time Pakistani ANPR System</title>
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="title">Real-Time Pakistani Number Plate Detection System</h1>
<p class="subtitle">Intelligent ANPR for Pakistani Vehicles – Detect, Read, and Log in Real-Time</p>
<div class="car-gallery">
<div class="car-card">
<img src="https://images.unsplash.com/photo-1502877338535-766e1452684a?w=800" alt="Pakistani Car 1">
</div>
<div class="car-card">
<img src="https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=800" alt="Pakistani Car 2">
</div>
</div>
<div class="upload-section">
<form id="uploadForm" enctype="multipart/form-data">
<label for="fileInput" class="upload-label">
<div class="upload-box">
<span class="upload-icon">📷</span>
<p>Drag & Drop or Click to Upload Image/Video</p>
<p class="small">Supported: JPG, PNG, JFIF, MP4, AVI</p>
</div>
</label>
<input type="file" id="fileInput" name="file" accept="image/*,video/*" hidden>
<button type="submit" id="detectBtn">Detect & Read Plate</button>
</form>
<div id="loading" class="loading hidden">
<div class="spinner"></div>
<p>Processing... Please wait</p>
</div>
</div>
<div id="result" class="result hidden">
<h2>Detection Result</h2>
<p id="textResult"></p>
<p id="confResult"></p>
<div class="result-images">
<img id="annotatedImg" alt="Annotated Image">
<img id="croppedImg" alt="Cropped Plate">
</div>
<button id="copyTextBtn">Copy Plate Text</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
my css is
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, #667eea, #764ba2);
color: #333;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
padding: 40px;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
text-align: center;
}
.title {
font-size: 2.8em;
color: #1a237e;
margin-bottom: 10px;
animation: fadeInDown 1.2s ease-out;
}
.subtitle {
font-size: 1.2em;
color: #555;
margin-bottom: 40px;
opacity: 0;
animation: fadeInUp 1.2s ease-out 0.5s forwards;
}
.car-gallery {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 50px;
flex-wrap: wrap;
}
.car-card {
width: 300px;
overflow: hidden;
border-radius: 15px;
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.car-card:hover {
transform: translateY(-10px);
}
.car-card img {
width: 100%;
height: 200px;
object-fit: cover;
transition: transform 0.5s ease;
}
.car-card:hover img {
transform: scale(1.1);
}
.upload-section {
margin-bottom: 40px;
}
.upload-label {
display: block;
cursor: pointer;
}
.upload-box {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 60px 20px;
background: #f8f9ff;
transition: all 0.3s ease;
}
.upload-box:hover {
background: #e8f0ff;
border-color: #764ba2;
}
.upload-icon {
font-size: 3em;
margin-bottom: 10px;
}
button {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 15px 40px;
font-size: 1.1em;
border-radius: 50px;
cursor: pointer;
margin-top: 20px;
transition: all 0.3s ease;
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.result {
background: white;
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.result-images {
display: flex;
gap: 20px;
justify-content: center;
margin: 20px 0;
}
.result-images img {
max-width: 45%;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
margin: 40px 0;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fadeInDown {
from { opacity: 0; transform: translateY(-30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Existing style code here... */
.loading {
margin-top: 30px;
display: none;
flex-direction: column;
align-items: center;
}
.loading.hidden {
display: none;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #27ae60;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-message {
margin-top: 15px;
font-size: 1.2em;
color: #27ae60;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.result {
background: #ecf0f1;
padding: 20px;
border-radius: 8px;
margin-top: 30px;
}
.result.hidden {
display: none;
}
.result p {
font-size: 1.1em;
margin-bottom: 15px;
}
and my js is
document.getElementById('uploadForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const resultDiv = document.getElementById('result');
const loadingDiv = document.getElementById('loading');
loadingDiv.classList.remove('hidden');
resultDiv.classList.add('hidden');
try {
const response = await fetch('/predict', {
method: 'POST',
body: formData
});
const data = await response.json();
loadingDiv.classList.add('hidden');
if (data.error) {
resultDiv.innerHTML = `<p style="color:red;">Error: ${data.error}</p>`;
} else {
resultDiv.innerHTML = `
<h2>Detection Result</h2>
<p><strong>Detected Plate Text:</strong> ${data.text || 'N/A'}</p>
<p><strong>Detection Confidence:</strong> ${data.confidence.toFixed(2)}%</p>
<div class="result-images">
<img src="${data.annotated_image}" alt="Annotated Image">
<img src="${data.cropped_image}" alt="Cropped Plate">
</div>
`;
}
resultDiv.classList.remove('hidden');
} catch (error) {
loadingDiv.classList.add('hidden');
resultDiv.innerHTML = `<p style="color:red;">Failed to connect to server.</p>`;
resultDiv.classList.remove('hidden');
}
});
and my app/py is from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from ultralytics import YOLO
import cv2
import easyocr
import os
import time
import csv
from werkzeug.utils import secure_filename
import base64
app = Flask(__name__, static_folder='../frontend', static_url_path='')
CORS(app) # Connection error khatam
# Model load (absolute path)
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
model_path = os.path.join(project_root, "backend", "models", "best.pt")
model = YOLO(model_path)
# EasyOCR
reader = easyocr.Reader(['en'], gpu=False)
# Folders
UPLOAD_FOLDER = "uploads"
DETECTED_FOLDER = "detected_images"
CROPPED_FOLDER = "saved_plates"
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(DETECTED_FOLDER, exist_ok=True)
os.makedirs(CROPPED_FOLDER, exist_ok=True)
LOG_FILE = "plates_log.csv"
if not os.path.exists(LOG_FILE):
with open(LOG_FILE, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Timestamp", "Original_Image", "Annotated_Image", "Cropped_Plate", "YOLO_Confidence", "OCR_Text", "OCR_Confidence"])
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route('/')
def home():
return send_from_directory(app.static_folder, 'index.html')
@app.route('/predict', methods=['POST'])
def predict():
try:
if 'file' not in request.files:
return jsonify({"error": "No file uploaded"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No file selected"}), 400
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
# ✅ ALWAYS CONVERT TO JPG (supports .jfif, .avif, .webp etc.)
img = cv2.imread(file_path)
if img is None:
return jsonify({"error": "Invalid or unsupported image format"}), 400
# New filename as .jpg
new_filename = os.path.splitext(filename)[0] + '.jpg'
new_file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
cv2.imwrite(new_file_path, img)
# Delete original if different
if new_file_path != file_path:
os.remove(file_path)
file_path = new_file_path
filename = new_filename
# YOLO detection
results = model(file_path, conf=0.5)
# Annotated image save and base64
annotated_filename = f"annotated_{filename}"
annotated_path = os.path.join(DETECTED_FOLDER, annotated_filename)
results[0].save(annotated_path)
with open(annotated_path, "rb") as f:
annotated_base64 = base64.b64encode(f.read()).decode('utf-8')
# Crop and OCR
ocr_text = "N/A"
ocr_conf = 0.0
cropped_base64 = ""
for box in results[0].boxes:
if int(box.cls) == 0:
yolo_conf = float(box.conf)
x1, y1, x2, y2 = map(int, box.xyxy[0])
plate_crop = img[y1:y2, x1:x2]
timestamp = int(time.time())
cropped_filename = f"plate_crop_{timestamp}.jpg"
cropped_path = os.path.join(CROPPED_FOLDER, cropped_filename)
cv2.imwrite(cropped_path, plate_crop)
with open(cropped_path, "rb") as f:
cropped_base64 = base64.b64encode(f.read()).decode('utf-8')
# OCR
plate_gray = cv2.cvtColor(plate_crop, cv2.COLOR_BGR2GRAY)
ocr_result = reader.readtext(plate_gray)
if ocr_result:
ocr_text = ocr_result[0][1].upper().replace(" ", "")
ocr_conf = ocr_result[0][2]
# CSV log
with open(LOG_FILE, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow([time.strftime("%Y-%m-%d %H:%M:%S"), file_path, annotated_path, cropped_path, yolo_conf, ocr_text, ocr_conf])
break
return jsonify({
"text": ocr_text,
"confidence": round(ocr_conf * 100, 2),
"annotated_image": f"data:image/jpeg;base64,{annotated_base64}",
"cropped_image": f"data:image/jpeg;base64,{cropped_base64}" if cropped_base64 else ""
})
except Exception as e:
print(f"Error: {str(e)}")
return jsonify({"error": str(e)}), 500
@app.route('/<path:filename>')
def serve_static(filename):
return send_from_directory(app.static_folder, filename)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
plzz make the frorntend more professional