Spaces:
Sleeping
Sleeping
Pointf5ive commited on
Commit Β·
1754c00
1
Parent(s): 4ac6999
Add manual OCR page selection and deterministic review ordering
Browse files- smoke_signal_tab.py +101 -8
smoke_signal_tab.py
CHANGED
|
@@ -631,6 +631,48 @@ def log_line(msg: str) -> str:
|
|
| 631 |
return f"[{datetime.utcnow().strftime('%H:%M:%S')}] {msg}"
|
| 632 |
|
| 633 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
# ββ Step 1: INGEST βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 635 |
def ingest_pdfs(files, rights_class: str, notes: str) -> tuple:
|
| 636 |
"""Register uploaded PDFs into the source manifest."""
|
|
@@ -1196,7 +1238,7 @@ def _run_tesseract_batch(images):
|
|
| 1196 |
return outputs
|
| 1197 |
|
| 1198 |
|
| 1199 |
-
def run_ocr() -> tuple:
|
| 1200 |
"""Run Surya OCR on all profiled PDFs."""
|
| 1201 |
df = load_manifest_df()
|
| 1202 |
debug = f"[DEBUG] SS_ROOT={SS_ROOT}\nMANIFEST_CSV={MANIFEST_CSV}\nCSV exists={MANIFEST_CSV.exists()}\n"
|
|
@@ -1223,6 +1265,15 @@ def run_ocr() -> tuple:
|
|
| 1223 |
|
| 1224 |
log = []
|
| 1225 |
queue_rows = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1226 |
|
| 1227 |
surya, surya_error, reused = _get_surya_runtime_with_timeout(SS_SURYA_LOAD_TIMEOUT_SEC)
|
| 1228 |
if surya:
|
|
@@ -1254,6 +1305,18 @@ def run_ocr() -> tuple:
|
|
| 1254 |
|
| 1255 |
with open(profile_path, encoding="utf-8") as f:
|
| 1256 |
profile = json.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1257 |
|
| 1258 |
ocr_pages = []
|
| 1259 |
review_pages = []
|
|
@@ -1275,6 +1338,8 @@ def run_ocr() -> tuple:
|
|
| 1275 |
for page_data in profile.get("pages", []):
|
| 1276 |
page_num = page_data["page_number"]
|
| 1277 |
route = page_data["route"]
|
|
|
|
|
|
|
| 1278 |
|
| 1279 |
if route == "embedded_text":
|
| 1280 |
continue
|
|
@@ -1427,6 +1492,8 @@ def run_ocr() -> tuple:
|
|
| 1427 |
for page_data in profile.get("pages", []):
|
| 1428 |
page_num = page_data["page_number"]
|
| 1429 |
route = page_data["route"]
|
|
|
|
|
|
|
| 1430 |
|
| 1431 |
if route == "embedded_text":
|
| 1432 |
try:
|
|
@@ -1534,14 +1601,28 @@ def run_ocr() -> tuple:
|
|
| 1534 |
|
| 1535 |
df.loc[df["book_id"] == book_id, "status"] = "ocred"
|
| 1536 |
total_review = len(review_pages) + len(quarantine_pages)
|
| 1537 |
-
log.append(log_line(f"β {book_id}: {len(ocr_pages)}pp β review queue: {total_review}"))
|
| 1538 |
|
| 1539 |
-
|
| 1540 |
-
|
| 1541 |
-
|
| 1542 |
-
|
| 1543 |
-
|
| 1544 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1545 |
|
| 1546 |
save_manifest_df(df)
|
| 1547 |
return _ocr_status_html(), "\n".join(log)
|
|
@@ -1586,6 +1667,8 @@ def get_review_item(idx: int) -> tuple:
|
|
| 1586 |
|
| 1587 |
decided_ids = set(d_df["region_id"].tolist()) if not d_df.empty else set()
|
| 1588 |
pending = q_df[~q_df["region_id"].isin(decided_ids)]
|
|
|
|
|
|
|
| 1589 |
if pending.empty:
|
| 1590 |
return None, "All items reviewed!", "", len(q_df), len(q_df)
|
| 1591 |
|
|
@@ -1939,11 +2022,21 @@ def smoke_signal_tab():
|
|
| 1939 |
</div>
|
| 1940 |
</div>""")
|
| 1941 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1942 |
ocr_btn = gr.Button("Run OCR β", elem_classes=["ss-btn-run"])
|
| 1943 |
ocr_log = gr.Textbox(label="Log", lines=10, interactive=False, elem_classes=["ss-log"])
|
| 1944 |
|
| 1945 |
ocr_run_event = ocr_btn.click(
|
| 1946 |
run_ocr,
|
|
|
|
| 1947 |
outputs=[ocr_status, ocr_log],
|
| 1948 |
show_progress="minimal",
|
| 1949 |
)
|
|
|
|
| 631 |
return f"[{datetime.utcnow().strftime('%H:%M:%S')}] {msg}"
|
| 632 |
|
| 633 |
|
| 634 |
+
def _parse_page_selection(spec: str, max_page: int | None = None) -> tuple[set[int] | None, str | None]:
|
| 635 |
+
"""
|
| 636 |
+
Parse optional page-selection text.
|
| 637 |
+
Accepted forms:
|
| 638 |
+
- empty / all / * -> None (means all pages)
|
| 639 |
+
- "7"
|
| 640 |
+
- "3-8"
|
| 641 |
+
- "1,3,5-7"
|
| 642 |
+
"""
|
| 643 |
+
raw = (spec or "").strip().lower()
|
| 644 |
+
if raw in ("", "all", "*"):
|
| 645 |
+
return None, None
|
| 646 |
+
|
| 647 |
+
out: set[int] = set()
|
| 648 |
+
for token in [t.strip() for t in raw.split(",") if t.strip()]:
|
| 649 |
+
if "-" in token:
|
| 650 |
+
parts = token.split("-", 1)
|
| 651 |
+
if len(parts) != 2 or (not parts[0].isdigit()) or (not parts[1].isdigit()):
|
| 652 |
+
return None, f"Invalid page range token: '{token}'"
|
| 653 |
+
start = int(parts[0])
|
| 654 |
+
end = int(parts[1])
|
| 655 |
+
if start <= 0 or end <= 0:
|
| 656 |
+
return None, f"Pages must be >= 1 (token: '{token}')"
|
| 657 |
+
if end < start:
|
| 658 |
+
return None, f"Range end before start (token: '{token}')"
|
| 659 |
+
out.update(range(start, end + 1))
|
| 660 |
+
else:
|
| 661 |
+
if not token.isdigit():
|
| 662 |
+
return None, f"Invalid page token: '{token}'"
|
| 663 |
+
page = int(token)
|
| 664 |
+
if page <= 0:
|
| 665 |
+
return None, f"Pages must be >= 1 (token: '{token}')"
|
| 666 |
+
out.add(page)
|
| 667 |
+
|
| 668 |
+
if max_page is not None:
|
| 669 |
+
out = {p for p in out if p <= int(max_page)}
|
| 670 |
+
if not out:
|
| 671 |
+
return None, f"No selected pages fall within this PDF (max page {max_page})."
|
| 672 |
+
|
| 673 |
+
return out, None
|
| 674 |
+
|
| 675 |
+
|
| 676 |
# ββ Step 1: INGEST βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 677 |
def ingest_pdfs(files, rights_class: str, notes: str) -> tuple:
|
| 678 |
"""Register uploaded PDFs into the source manifest."""
|
|
|
|
| 1238 |
return outputs
|
| 1239 |
|
| 1240 |
|
| 1241 |
+
def run_ocr(page_selection: str = "", replace_book_queue: bool = True) -> tuple:
|
| 1242 |
"""Run Surya OCR on all profiled PDFs."""
|
| 1243 |
df = load_manifest_df()
|
| 1244 |
debug = f"[DEBUG] SS_ROOT={SS_ROOT}\nMANIFEST_CSV={MANIFEST_CSV}\nCSV exists={MANIFEST_CSV.exists()}\n"
|
|
|
|
| 1265 |
|
| 1266 |
log = []
|
| 1267 |
queue_rows = []
|
| 1268 |
+
processed_books = []
|
| 1269 |
+
selection_set, selection_error = _parse_page_selection(page_selection)
|
| 1270 |
+
if selection_error:
|
| 1271 |
+
return _ocr_status_html(), f"{debug}Invalid page selection: {selection_error}"
|
| 1272 |
+
if selection_set is None:
|
| 1273 |
+
log.append(log_line("βΉ Page selection: all pages"))
|
| 1274 |
+
else:
|
| 1275 |
+
selected_preview = ",".join(str(p) for p in sorted(selection_set))
|
| 1276 |
+
log.append(log_line(f"βΉ Page selection: {selected_preview}"))
|
| 1277 |
|
| 1278 |
surya, surya_error, reused = _get_surya_runtime_with_timeout(SS_SURYA_LOAD_TIMEOUT_SEC)
|
| 1279 |
if surya:
|
|
|
|
| 1305 |
|
| 1306 |
with open(profile_path, encoding="utf-8") as f:
|
| 1307 |
profile = json.load(f)
|
| 1308 |
+
pages_all = profile.get("pages", [])
|
| 1309 |
+
page_numbers = [int(p.get("page_number", 0) or 0) for p in pages_all]
|
| 1310 |
+
max_page = max(page_numbers) if page_numbers else None
|
| 1311 |
+
selected_pages_for_book = selection_set
|
| 1312 |
+
if selection_set is not None:
|
| 1313 |
+
selected_pages_for_book, sel_err = _parse_page_selection(page_selection, max_page=max_page)
|
| 1314 |
+
if sel_err:
|
| 1315 |
+
log.append(log_line(f"β {book_id}: {sel_err}"))
|
| 1316 |
+
continue
|
| 1317 |
+
selected_preview = ",".join(str(p) for p in sorted(selected_pages_for_book))
|
| 1318 |
+
log.append(log_line(f"βΉ {book_id}: selected pages {selected_preview}"))
|
| 1319 |
+
processed_books.append(book_id)
|
| 1320 |
|
| 1321 |
ocr_pages = []
|
| 1322 |
review_pages = []
|
|
|
|
| 1338 |
for page_data in profile.get("pages", []):
|
| 1339 |
page_num = page_data["page_number"]
|
| 1340 |
route = page_data["route"]
|
| 1341 |
+
if selected_pages_for_book is not None and int(page_num) not in selected_pages_for_book:
|
| 1342 |
+
continue
|
| 1343 |
|
| 1344 |
if route == "embedded_text":
|
| 1345 |
continue
|
|
|
|
| 1492 |
for page_data in profile.get("pages", []):
|
| 1493 |
page_num = page_data["page_number"]
|
| 1494 |
route = page_data["route"]
|
| 1495 |
+
if selected_pages_for_book is not None and int(page_num) not in selected_pages_for_book:
|
| 1496 |
+
continue
|
| 1497 |
|
| 1498 |
if route == "embedded_text":
|
| 1499 |
try:
|
|
|
|
| 1601 |
|
| 1602 |
df.loc[df["book_id"] == book_id, "status"] = "ocred"
|
| 1603 |
total_review = len(review_pages) + len(quarantine_pages)
|
| 1604 |
+
log.append(log_line(f"β {book_id}: {len(ocr_pages)} selected pp β review queue: {total_review}"))
|
| 1605 |
|
| 1606 |
+
qdf_new = pd.DataFrame(queue_rows)
|
| 1607 |
+
if QUEUE_CSV.exists():
|
| 1608 |
+
existing = pd.read_csv(QUEUE_CSV)
|
| 1609 |
+
else:
|
| 1610 |
+
existing = pd.DataFrame()
|
| 1611 |
+
|
| 1612 |
+
if replace_book_queue and processed_books and not existing.empty and "book_id" in existing.columns:
|
| 1613 |
+
existing = existing[~existing["book_id"].isin(processed_books)]
|
| 1614 |
+
|
| 1615 |
+
if not existing.empty and not qdf_new.empty:
|
| 1616 |
+
qdf_out = pd.concat([existing, qdf_new], ignore_index=True).drop_duplicates(subset=["region_id"], keep="last")
|
| 1617 |
+
elif not qdf_new.empty:
|
| 1618 |
+
qdf_out = qdf_new
|
| 1619 |
+
else:
|
| 1620 |
+
qdf_out = existing
|
| 1621 |
+
|
| 1622 |
+
if not qdf_out.empty:
|
| 1623 |
+
qdf_out.to_csv(QUEUE_CSV, index=False)
|
| 1624 |
+
elif QUEUE_CSV.exists():
|
| 1625 |
+
QUEUE_CSV.unlink()
|
| 1626 |
|
| 1627 |
save_manifest_df(df)
|
| 1628 |
return _ocr_status_html(), "\n".join(log)
|
|
|
|
| 1667 |
|
| 1668 |
decided_ids = set(d_df["region_id"].tolist()) if not d_df.empty else set()
|
| 1669 |
pending = q_df[~q_df["region_id"].isin(decided_ids)]
|
| 1670 |
+
if not pending.empty and {"book_id", "page"}.issubset(pending.columns):
|
| 1671 |
+
pending = pending.sort_values(["book_id", "page"], ascending=[True, True], kind="stable")
|
| 1672 |
if pending.empty:
|
| 1673 |
return None, "All items reviewed!", "", len(q_df), len(q_df)
|
| 1674 |
|
|
|
|
| 2022 |
</div>
|
| 2023 |
</div>""")
|
| 2024 |
|
| 2025 |
+
ocr_page_selection = gr.Textbox(
|
| 2026 |
+
label="Pages to OCR (optional)",
|
| 2027 |
+
placeholder="all or e.g. 7 or 3-8 or 1,4,9-12",
|
| 2028 |
+
lines=1,
|
| 2029 |
+
)
|
| 2030 |
+
ocr_replace_queue = gr.Checkbox(
|
| 2031 |
+
label="Replace existing review queue entries for processed books",
|
| 2032 |
+
value=True,
|
| 2033 |
+
)
|
| 2034 |
ocr_btn = gr.Button("Run OCR β", elem_classes=["ss-btn-run"])
|
| 2035 |
ocr_log = gr.Textbox(label="Log", lines=10, interactive=False, elem_classes=["ss-log"])
|
| 2036 |
|
| 2037 |
ocr_run_event = ocr_btn.click(
|
| 2038 |
run_ocr,
|
| 2039 |
+
inputs=[ocr_page_selection, ocr_replace_queue],
|
| 2040 |
outputs=[ocr_status, ocr_log],
|
| 2041 |
show_progress="minimal",
|
| 2042 |
)
|