Added OCR region drawing tab to visualize what the OCR is looking at
Browse files- api_client.py +28 -0
- app.py +41 -4
api_client.py
CHANGED
|
@@ -3,6 +3,8 @@ import os
|
|
| 3 |
import requests
|
| 4 |
import base64
|
| 5 |
import streamlit as st
|
|
|
|
|
|
|
| 6 |
|
| 7 |
#BACKEND_URL = "http://localhost:8000"
|
| 8 |
BACKEND_URL = os.getenv("BACKEND_URL", "https://dcorcoran-pokemon-card-image-processor-api.hf.space")
|
|
@@ -55,3 +57,29 @@ def get_all_cards(limit: int = 100) -> list:
|
|
| 55 |
return response.json()
|
| 56 |
except Exception as e:
|
| 57 |
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import requests
|
| 4 |
import base64
|
| 5 |
import streamlit as st
|
| 6 |
+
import io
|
| 7 |
+
from PIL import Image
|
| 8 |
|
| 9 |
#BACKEND_URL = "http://localhost:8000"
|
| 10 |
BACKEND_URL = os.getenv("BACKEND_URL", "https://dcorcoran-pokemon-card-image-processor-api.hf.space")
|
|
|
|
| 57 |
return response.json()
|
| 58 |
except Exception as e:
|
| 59 |
raise
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def visualize_regions(image_bytes: bytes, filename: str) -> Image.Image | None:
|
| 63 |
+
try:
|
| 64 |
+
response = requests.post(
|
| 65 |
+
f"{BACKEND_URL}/visualize",
|
| 66 |
+
files={"file": (filename, image_bytes, "image/jpeg")},
|
| 67 |
+
timeout=30
|
| 68 |
+
)
|
| 69 |
+
response.raise_for_status()
|
| 70 |
+
|
| 71 |
+
# Decode the returned PNG bytes into a Pillow Image
|
| 72 |
+
return Image.open(io.BytesIO(response.content))
|
| 73 |
+
|
| 74 |
+
except requests.exceptions.HTTPError as http_err:
|
| 75 |
+
st.error(f"Visualization failed (HTTP {response.status_code}): {http_err}")
|
| 76 |
+
return None
|
| 77 |
+
except requests.exceptions.ConnectionError as conn_err:
|
| 78 |
+
st.error(f"Connection error during visualization: {conn_err}")
|
| 79 |
+
return None
|
| 80 |
+
except requests.exceptions.Timeout:
|
| 81 |
+
st.error("Visualization request timed out.")
|
| 82 |
+
return None
|
| 83 |
+
except Exception as e:
|
| 84 |
+
st.error(f"Unexpected error during visualization: {e}")
|
| 85 |
+
return None
|
app.py
CHANGED
|
@@ -4,7 +4,7 @@ import io
|
|
| 4 |
import requests
|
| 5 |
import os
|
| 6 |
|
| 7 |
-
from api_client import predict, health_check, get_all_cards
|
| 8 |
from components.upload_section import render_upload_section
|
| 9 |
from components.display_structured_data import render_card_data
|
| 10 |
from components.similarity_grid import render_similarity_grid
|
|
@@ -42,7 +42,7 @@ if not health_check():
|
|
| 42 |
# ----- UPLOAD -------------------
|
| 43 |
# --------------------------------
|
| 44 |
|
| 45 |
-
tab1, tab2 = st.tabs(["Upload Pokemon Card", "Card Database"])
|
| 46 |
|
| 47 |
|
| 48 |
with tab1:
|
|
@@ -62,7 +62,7 @@ with tab1:
|
|
| 62 |
|
| 63 |
with col2:
|
| 64 |
with st.spinner("Analyzing card..."):
|
| 65 |
-
result = predict(image_bytes, uploaded_file.name)
|
| 66 |
|
| 67 |
# Check if there was an error in the predict endpoint
|
| 68 |
if "error_type" in result:
|
|
@@ -107,4 +107,41 @@ with tab2:
|
|
| 107 |
st.markdown("π")
|
| 108 |
st.caption(card.get("name", "Unknown"))
|
| 109 |
else:
|
| 110 |
-
st.info("No cards to display.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import requests
|
| 5 |
import os
|
| 6 |
|
| 7 |
+
from api_client import predict, health_check, get_all_cards, visualize_regions
|
| 8 |
from components.upload_section import render_upload_section
|
| 9 |
from components.display_structured_data import render_card_data
|
| 10 |
from components.similarity_grid import render_similarity_grid
|
|
|
|
| 42 |
# ----- UPLOAD -------------------
|
| 43 |
# --------------------------------
|
| 44 |
|
| 45 |
+
tab1, tab2, tab3 = st.tabs(["Upload Pokemon Card", "Card Database", "OCR Card Visualizer"])
|
| 46 |
|
| 47 |
|
| 48 |
with tab1:
|
|
|
|
| 62 |
|
| 63 |
with col2:
|
| 64 |
with st.spinner("Analyzing card..."):
|
| 65 |
+
result = predict(image_bytes, uploaded_file.name)
|
| 66 |
|
| 67 |
# Check if there was an error in the predict endpoint
|
| 68 |
if "error_type" in result:
|
|
|
|
| 107 |
st.markdown("π")
|
| 108 |
st.caption(card.get("name", "Unknown"))
|
| 109 |
else:
|
| 110 |
+
st.info("No cards to display.")
|
| 111 |
+
|
| 112 |
+
with tab3:
|
| 113 |
+
# Tab header and description
|
| 114 |
+
st.header("OCR Region Visualizer")
|
| 115 |
+
st.caption("Upload a card to see exactly which regions the OCR is scanning.")
|
| 116 |
+
|
| 117 |
+
# Reuse the shared upload component to accept a card image
|
| 118 |
+
vis_file = render_upload_section()
|
| 119 |
+
|
| 120 |
+
if vis_file:
|
| 121 |
+
# Read the uploaded file bytes and decode into a Pillow Image for local display
|
| 122 |
+
image_bytes = vis_file.read()
|
| 123 |
+
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 124 |
+
|
| 125 |
+
# Split the view into two side-by-side columns: original vs. annotated
|
| 126 |
+
col1, col2 = st.columns(2)
|
| 127 |
+
|
| 128 |
+
# Display the unmodified card image on the left
|
| 129 |
+
with col1:
|
| 130 |
+
st.subheader("Original")
|
| 131 |
+
st.image(image, use_container_width=True)
|
| 132 |
+
|
| 133 |
+
# Display the modified card image on the right
|
| 134 |
+
with col2:
|
| 135 |
+
st.subheader("OCR Regions")
|
| 136 |
+
annotated = visualize_regions(image_bytes, vis_file.name)
|
| 137 |
+
if annotated:
|
| 138 |
+
st.image(annotated, use_container_width=True)
|
| 139 |
+
|
| 140 |
+
st.divider()
|
| 141 |
+
|
| 142 |
+
# Color legend mapping each box color to its corresponding OCR region
|
| 143 |
+
st.markdown("**Legend**")
|
| 144 |
+
cols = st.columns(3)
|
| 145 |
+
cols[0].markdown("π΄ **Name**")
|
| 146 |
+
cols[1].markdown("π΅ **HP**")
|
| 147 |
+
cols[2].markdown("π’ **Moves**")
|