Pointf5ive commited on
Commit
1754c00
Β·
1 Parent(s): 4ac6999

Add manual OCR page selection and deterministic review ordering

Browse files
Files changed (1) hide show
  1. 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
- if queue_rows:
1540
- qdf = pd.DataFrame(queue_rows)
1541
- if QUEUE_CSV.exists():
1542
- existing = pd.read_csv(QUEUE_CSV)
1543
- qdf = pd.concat([existing, qdf], ignore_index=True).drop_duplicates(subset=["region_id"], keep="last")
1544
- qdf.to_csv(QUEUE_CSV, index=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  )