File size: 8,069 Bytes
54809ad a86e6ec 54809ad a86e6ec 54809ad 0a0379d 54809ad a86e6ec 54809ad a86e6ec 54809ad 0a0379d a86e6ec 54809ad 0a0379d 54809ad a86e6ec 0a0379d a86e6ec 0a0379d a86e6ec 54809ad a86e6ec 54809ad a86e6ec 54809ad a86e6ec 54809ad 0a0379d 54809ad a86e6ec 54809ad a86e6ec 54809ad a86e6ec 54809ad a86e6ec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import HTMLResponse
import pandas as pd
import io
import re
import os
from google import genai
from PIL import Image
app = FastAPI()
# 1. THE RAW HTML FRONTEND (Upgraded with JavaScript for dynamic loading)
@app.get("/")
def main_page():
return HTMLResponse("""
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>PLC Reader</title>
<style>
body { font-family: system-ui, sans-serif; padding: 20px; background-color: #f8fafc; color: #0f172a; text-align: center; margin: 0;}
.container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
input[type="password"] { width: 100%; padding: 12px; margin-bottom: 20px; border: 1px solid #cbd5e1; border-radius: 6px; box-sizing: border-box; font-size: 16px;}
input[type="file"] { display: none; }
.camera-btn { display: block; background-color: #4338ca; color: white; padding: 15px; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; border: none; width: 100%; box-sizing: border-box; margin-bottom: 20px;}
/* New Styles for Image Preview and Loading Spinner */
#preview-container { display: none; margin-bottom: 20px; }
#preview-img { max-width: 100%; border-radius: 8px; border: 2px solid #e2e8f0; }
.loader { display: none; font-weight: bold; color: #4338ca; margin: 20px 0; font-size: 1.1em;}
.spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(67,56,202,.3); border-radius: 50%; border-top-color: #4338ca; animation: spin 1s ease-in-out infinite; vertical-align: middle; margin-right: 8px;}
@keyframes spin { to { transform: rotate(360deg); } }
/* Table Styles injected by backend */
.table-container { overflow-x: auto; margin-top: 15px; text-align: left; }
table { border-collapse: collapse; width: 100%; background: white; font-size: 14px;}
th, td { border: 1px solid #e2e8f0; padding: 8px; white-space: nowrap; }
th { background-color: #f1f5f9; }
</style>
</head>
<body>
<div class="container">
<h2 style="margin-top:0; color: #1e293b;">🏭 PLC Data READER</h2>
<p style="color: #64748b; margin-bottom: 25px;">Capture sinter plant log sheets to digitize tabular data.</p>
<input type="password" id="api-key" placeholder="You are ready to go......">
<label class="camera-btn" id="camera-label">
<span id="btn-text">📸 Tap to Open Rear Camera</span>
<input type="file" id="file-input" accept="image/*" capture="environment">
</label>
<div id="preview-container">
<h4 style="margin-bottom: 10px; color: #475569; text-align: left;">Captured Image:</h4>
<img id="preview-img" alt="Captured your image">
</div>
<div id="loading" class="loader">
<span class="spinner"></span> Analyzing your image... this takes a few seconds.
</div>
<div id="results"></div>
</div>
<script>
// This JavaScript handles the background processing so the page doesn't refresh
document.getElementById('file-input').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
// 1. Instantly display the image on the screen
const reader = new FileReader();
reader.onload = function(event) {
document.getElementById('preview-img').src = event.target.result;
document.getElementById('preview-container').style.display = 'block';
};
reader.readAsDataURL(file);
// 2. Hide the camera button and show the loading spinner
document.getElementById('camera-label').style.display = 'none';
document.getElementById('results').innerHTML = ''; // Clear old tables
document.getElementById('loading').style.display = 'block';
// 3. Send the image to the Python backend in the background
const formData = new FormData();
formData.append('file', file);
formData.append('api_key_input', document.getElementById('api-key').value);
fetch('/extract', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(html => {
// 4. Extraction complete! Hide loading, show tables, restore button
document.getElementById('loading').style.display = 'none';
document.getElementById('results').innerHTML = html;
const camLabel = document.getElementById('camera-label');
camLabel.style.display = 'block';
document.getElementById('btn-text').innerText = '📸 Take Another Picture';
})
.catch(error => {
document.getElementById('loading').style.display = 'none';
document.getElementById('results').innerHTML = '<div style="color:#ef4444; padding:15px;"><b>❌ Connection Error</b><br>' + error + '</div>';
document.getElementById('camera-label').style.display = 'block';
});
});
</script>
</body>
</html>
""")
# 2. THE PYTHON BACKEND (Now only returns the formatted tables)
@app.post("/extract")
async def extract_data(api_key_input: str = Form(""), file: UploadFile = File(...)):
try:
api_key = os.environ.get("GEMINI_API_KEY") or api_key_input
if not api_key:
return HTMLResponse("<div style='padding:15px; background:#fef2f2; color:#991b1b; border-radius:8px;'><b>⚠️ Missing API Key.</b> Please enter it above.</div>")
image_bytes = await file.read()
img = Image.open(io.BytesIO(image_bytes))
client = genai.Client(api_key=api_key)
prompt = "Carefully extract all the data tables from this dashboard image. Format the output strictly as CSV. Enclose the CSV data inside ```csv ``` code blocks. Ensure column headers are included."
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[prompt, img]
)
csv_blocks = re.findall(r'```csv\n(.*?)\n```', response.text, re.DOTALL)
if not csv_blocks:
return HTMLResponse(f"<div style='padding:15px; background:#fffbeb; color:#92400e; border-radius:8px;'><b>⚠️ No tables found.</b> The model couldn't detect clear tables.<br><br>Raw Output:<br><pre style='white-space:pre-wrap; font-size:12px;'>{response.text}</pre></div>")
# Build the HTML snippet for the tables
html_output = f"<h3 style='color:#16a34a; margin-top:30px; border-top: 2px dashed #cbd5e1; padding-top:20px; text-align: left;'>✅ Extracted {len(csv_blocks)} Table(s)</h3>"
for i, csv_data in enumerate(csv_blocks):
df = pd.read_csv(io.StringIO(csv_data))
html_output += f"<h4 style='color:#334155; margin-bottom:5px; margin-top: 20px; text-align:left;'>Table {i+1}</h4>"
html_output += f"<div class='table-container'>{df.to_html(index=False, border=0)}</div>"
return HTMLResponse(html_output)
except Exception as e:
return HTMLResponse(f"<div style='padding:15px; background:#fef2f2; color:#991b1b; border-radius:8px;'><b>❌ Error Processing Image:</b><br>{str(e)}</div>") |