Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import pytesseract
|
4 |
+
import requests
|
5 |
+
import pandas as pd
|
6 |
+
import gradio as gr
|
7 |
+
from io import BytesIO
|
8 |
+
|
9 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
10 |
+
# 1. Utility: Detect rectangular contours (approximate book covers)
|
11 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
12 |
+
def detect_book_regions(image: np.ndarray, min_area=10000, eps_coef=0.02):
|
13 |
+
"""
|
14 |
+
Detect rectangular regions in an image that likely correspond to book covers.
|
15 |
+
Returns a list of bounding boxes: (x, y, w, h).
|
16 |
+
"""
|
17 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
18 |
+
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
19 |
+
edges = cv2.Canny(blurred, 50, 150)
|
20 |
+
|
21 |
+
# Dilate + erode to close gaps
|
22 |
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
|
23 |
+
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
|
24 |
+
|
25 |
+
contours, _ = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
26 |
+
boxes = []
|
27 |
+
|
28 |
+
for cnt in contours:
|
29 |
+
area = cv2.contourArea(cnt)
|
30 |
+
if area < min_area:
|
31 |
+
continue
|
32 |
+
|
33 |
+
peri = cv2.arcLength(cnt, True)
|
34 |
+
approx = cv2.approxPolyDP(cnt, eps_coef * peri, True)
|
35 |
+
|
36 |
+
# Keep only quadrilaterals
|
37 |
+
if len(approx) == 4:
|
38 |
+
x, y, w, h = cv2.boundingRect(approx)
|
39 |
+
ar = w / float(h)
|
40 |
+
# Filter by typical book-cover aspect ratios
|
41 |
+
if 0.4 < ar < 0.9 or 1.0 < ar < 1.6:
|
42 |
+
boxes.append((x, y, w, h))
|
43 |
+
|
44 |
+
# Sort leftβright, topβbottom
|
45 |
+
boxes = sorted(boxes, key=lambda b: (b[1], b[0]))
|
46 |
+
return boxes
|
47 |
+
|
48 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
49 |
+
# 2. OCR on a cropped region
|
50 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
51 |
+
def ocr_on_region(image: np.ndarray, box: tuple):
|
52 |
+
"""
|
53 |
+
Crop the image to the given box and run Tesseract OCR.
|
54 |
+
Return the raw OCR text.
|
55 |
+
"""
|
56 |
+
x, y, w, h = box
|
57 |
+
cropped = image[y:y+h, x:x+w]
|
58 |
+
gray_crop = cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY)
|
59 |
+
_, thresh_crop = cv2.threshold(gray_crop, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
60 |
+
custom_config = r'--oem 3 --psm 6'
|
61 |
+
text = pytesseract.image_to_string(thresh_crop, config=custom_config)
|
62 |
+
return text.strip()
|
63 |
+
|
64 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
65 |
+
# 3. Query OpenLibrary API
|
66 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
67 |
+
def query_openlibrary(title_text: str, author_text: str = None):
|
68 |
+
"""
|
69 |
+
Search OpenLibrary by title (and optional author).
|
70 |
+
Return a dict with title, author_name, publisher, first_publish_year, or None.
|
71 |
+
"""
|
72 |
+
base_url = "https://openlibrary.org/search.json"
|
73 |
+
params = {"title": title_text}
|
74 |
+
if author_text:
|
75 |
+
params["author"] = author_text
|
76 |
+
|
77 |
+
try:
|
78 |
+
resp = requests.get(base_url, params=params, timeout=5)
|