gvlktejaswi commited on
Commit
0e99718
Β·
verified Β·
1 Parent(s): 5ea82d9

Update page_files/Upload_Data.py

Browse files
Files changed (1) hide show
  1. page_files/Upload_Data.py +102 -25
page_files/Upload_Data.py CHANGED
@@ -45,9 +45,8 @@ from categorized.Backend.PDF_DataExtraction import (
45
  # and category_push). Ensure image2.py / mapper5.py / category_push.py are on
46
  # the import path (same directory or added to sys.path above). If you keep them
47
  # under categorized/Backend/, change these three lines to:
48
- # from categorized.Backend import Pdf_ImageExtraction, mapper5, category_push
49
-
50
- from categorized.Backend import Pdf_ImageExtraction
51
  from categorized.Backend import mapper5, category_push
52
 
53
  # Manual-entry path (unchanged β€” writes via the existing data_loader).
@@ -115,15 +114,24 @@ def _df_to_meta(df: pd.DataFrame) -> dict:
115
  # which is exactly what mapper5.map_plots_to_properties and the display expect.
116
  # ─────────────────────────────────────────────────────────────────────────────
117
 
118
- def extract_images(pdf_path: str) -> list:
 
 
 
 
 
 
119
  try:
120
  with open(pdf_path, "rb") as f:
121
  pdf_bytes = f.read()
122
- plot_results, coverage = Pdf_ImageExtraction.extract_and_verify_plots(
123
- pdf_bytes, verify_engines=["gemini"]
 
 
 
124
  )
125
  # Drop captions naming photos/micrographs/logos and crops the verifier
126
- # marked "discard" (keeps "keep"/"recrop"). Pure selection, non-mutating.
127
  plot_results = mapper5.select_real_plots(plot_results)
128
  st.session_state["plot_coverage"] = coverage
129
  except Exception as e:
@@ -535,7 +543,7 @@ def render_material_data_tab(pdf_path: str):
535
  unsafe_allow_html=True,
536
  )
537
  timer.caption(
538
- f"⏱ Elapsed: {elapsed:.0f}s"
539
  + (f" Β· Est. remaining: ~{est_remaining}s" if est_remaining > 0 else "")
540
  )
541
 
@@ -569,7 +577,7 @@ def render_material_data_tab(pdf_path: str):
569
  st.session_state.pdf_data_extracted = True
570
  st.session_state.pdf_extracted_meta = _df_to_meta(df)
571
  st.session_state.pdf_doi = doi
572
- st.success(f"Extracted {len(df)} properties in {elapsed_total:.0f}s")
573
  else:
574
  st.warning("No data extracted from PDF.")
575
  return
@@ -619,18 +627,57 @@ def _links_for_group(links_df: pd.DataFrame, gi: int) -> pd.DataFrame:
619
  return links_df[links_df["group_idx"] == gi]
620
 
621
 
 
 
 
 
 
 
 
 
 
 
622
  def render_plots_tab(pdf_path: str, paper_id: str):
623
  st.subheader("Extracted Plot Images & Property Mapping")
624
 
625
- # 1) Extract plots once (image2 detection + recovery + crop verification)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
  if not st.session_state.pdf_processed:
627
- with st.spinner("Extracting plots from PDF…"):
628
- st.session_state.plot_results = extract_images(pdf_path)
 
 
 
 
 
 
629
  st.session_state.pdf_processed = True
630
- st.session_state.mapping_done = False
631
- st.session_state.links_df = pd.DataFrame()
632
- st.session_state.df_aug = pd.DataFrame()
633
- st.session_state.store = None
 
 
634
 
635
  plot_results = st.session_state.plot_results
636
  if not plot_results:
@@ -655,14 +702,14 @@ def render_plots_tab(pdf_path: str, paper_id: str):
655
  # Downloads (image2's native zipper)
656
  d1, d2 = st.columns(2)
657
  d1.download_button(
658
- "⬇ Images + metadata (ZIP)",
659
- data=Pdf_ImageExtraction.create_plot_zip(plot_results, include_json=True),
660
  file_name=f"{paper_id}_plots.zip", mime="application/zip",
661
  use_container_width=True, key="dl_plots_zip",
662
  )
663
  d2.download_button(
664
- "⬇ Images only (ZIP)",
665
- data=Pdf_ImageExtraction.create_plot_zip(plot_results, include_json=False),
666
  file_name=f"{paper_id}_images.zip", mime="application/zip",
667
  use_container_width=True, key="dl_images_zip",
668
  )
@@ -703,14 +750,24 @@ def render_plots_tab(pdf_path: str, paper_id: str):
703
 
704
  st.divider()
705
 
706
- # 3) Figure-centric review β€” each figure with the property rows linked to it
 
 
 
 
707
  for gi, group in enumerate(plot_results):
708
  caption = group.get("caption", f"Figure {gi+1}")
709
  page = group.get("page", "?")
710
  imgs = group.get("image_data", [])
711
 
712
  with st.container(border=True):
713
- st.markdown(f"**Page {page}** β€” {caption}")
 
 
 
 
 
 
714
 
715
  icols = st.columns(min(len(imgs), 4) or 1)
716
  for pos, im in enumerate(imgs):
@@ -720,10 +777,28 @@ def render_plots_tab(pdf_path: str, paper_id: str):
720
  sub = im.get("subplot_label") or ""
721
  st.image(arr, channels="BGR", width=200,
722
  caption=(sub or None))
 
723
  v = im.get("verification") or {}
724
  act = v.get("majority_action") if isinstance(v, dict) else None
725
  if act and act != "keep":
726
- st.caption(f"crop verdict: {act}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
 
728
  if mapping_done:
729
  grp = _links_for_group(links_df, gi)
@@ -770,7 +845,7 @@ def render_plots_tab(pdf_path: str, paper_id: str):
770
  "attaches each crop's path. Push conforms those rows to the chosen "
771
  "category table's 33-column schema and uploads them.")
772
 
773
- if st.button(" Store to database (SQLite)", use_container_width=True):
774
  with st.spinner("Saving crops and writing SQLite…"):
775
  st.session_state.store = mapper5.store_properties_with_plots(
776
  st.session_state.df_aug,
@@ -858,6 +933,8 @@ def main():
858
  "pdf_extracted_df": pd.DataFrame(),
859
  "pdf_extracted_meta": {},
860
  "pdf_doi": "",
 
 
861
  }
862
  for k, v in defaults.items():
863
  if k not in st.session_state:
@@ -898,7 +975,7 @@ def main():
898
  st.tabs(["Material Data", "Extracted Plots"])
899
  return
900
 
901
- tab1, tab2 = st.tabs([" Material Data", "Extracted Plots"])
902
 
903
  tmp_file = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False, prefix="matdb_")
904
  try:
 
45
  # and category_push). Ensure image2.py / mapper5.py / category_push.py are on
46
  # the import path (same directory or added to sys.path above). If you keep them
47
  # under categorized/Backend/, change these three lines to:
48
+ # from categorized.Backend import image2, mapper5, category_push
49
+ from categorized.Backend import Pdf_ImageExtraction as image2 # image2.py renamed
 
50
  from categorized.Backend import mapper5, category_push
51
 
52
  # Manual-entry path (unchanged β€” writes via the existing data_loader).
 
114
  # which is exactly what mapper5.map_plots_to_properties and the display expect.
115
  # ─────────────────────────────────────────────────────────────────────────────
116
 
117
+ def extract_images(pdf_path: str, engines=None, check_missed: bool = True) -> list:
118
+ """Detect + (optionally) recover + crop-verify plots.
119
+ `engines` selects the verifier LLMs: ["gemini"] (default, fast) or
120
+ ["gemini","gpt","claude"] for a majority-vote cross-check that rejects more
121
+ non-plots (needs OPENAI_API_KEY / ANTHROPIC_API_KEY; missing keys just make
122
+ that engine abstain). `check_missed` toggles the per-page Gemini recovery scan."""
123
+ engines = engines or ["gemini"]
124
  try:
125
  with open(pdf_path, "rb") as f:
126
  pdf_bytes = f.read()
127
+ plot_results, coverage = image2.extract_and_verify_plots(
128
+ pdf_bytes,
129
+ check_missed_plots=check_missed,
130
+ verify_crops=True,
131
+ verify_engines=engines,
132
  )
133
  # Drop captions naming photos/micrographs/logos and crops the verifier
134
+ # marked "discard" (keeps "keep"/"recrop"/"unverified"). Non-mutating.
135
  plot_results = mapper5.select_real_plots(plot_results)
136
  st.session_state["plot_coverage"] = coverage
137
  except Exception as e:
 
543
  unsafe_allow_html=True,
544
  )
545
  timer.caption(
546
+ f" Elapsed: {elapsed:.0f}s"
547
  + (f" Β· Est. remaining: ~{est_remaining}s" if est_remaining > 0 else "")
548
  )
549
 
 
577
  st.session_state.pdf_data_extracted = True
578
  st.session_state.pdf_extracted_meta = _df_to_meta(df)
579
  st.session_state.pdf_doi = doi
580
+ st.success(f" Extracted {len(df)} properties in {elapsed_total:.0f}s")
581
  else:
582
  st.warning("No data extracted from PDF.")
583
  return
 
627
  return links_df[links_df["group_idx"] == gi]
628
 
629
 
630
+ def _reset_plot_mapping():
631
+ """Structural edits (removing a crop/figure) shift the positional group_idx
632
+ that links_df is keyed on, so any edit invalidates the current mapping.
633
+ Clearing it forces a clean re-map on the pruned set."""
634
+ st.session_state.mapping_done = False
635
+ st.session_state.links_df = pd.DataFrame()
636
+ st.session_state.df_aug = pd.DataFrame()
637
+ st.session_state.store = None
638
+
639
+
640
  def render_plots_tab(pdf_path: str, paper_id: str):
641
  st.subheader("Extracted Plot Images & Property Mapping")
642
 
643
+ # ── Extraction settings: multi-LLM cross-check + recovery toggle ──────────
644
+ with st.expander("βš™ Extraction settings", expanded=not st.session_state.pdf_processed):
645
+ multi = st.checkbox(
646
+ "Cross-check each figure with GPT + Claude (majority vote β€” rejects more "
647
+ "non-plots, but ~3Γ— slower and needs OPENAI_API_KEY / ANTHROPIC_API_KEY)",
648
+ value=st.session_state.get("verify_multi", False),
649
+ key="verify_multi",
650
+ )
651
+ recover = st.checkbox(
652
+ "Missed-plot recovery (per-page Gemini scan β€” catches plots OpenCV missed, slower)",
653
+ value=st.session_state.get("recover_missed", True),
654
+ key="recover_missed",
655
+ )
656
+ if multi and not (os.getenv("OPENAI_API_KEY") and os.getenv("ANTHROPIC_API_KEY")):
657
+ st.caption(" One or both of OPENAI_API_KEY / ANTHROPIC_API_KEY is not set β€” "
658
+ "that engine will abstain; the vote falls back to the ones available.")
659
+ if st.session_state.pdf_processed and st.button(" Re-extract with these settings"):
660
+ st.session_state.pdf_processed = False
661
+ _reset_plot_mapping()
662
+ st.rerun()
663
+
664
+ # 1) Extract plots once (detection + recovery + crop verification)
665
  if not st.session_state.pdf_processed:
666
+ engines = ["gemini", "gpt", "claude"] if st.session_state.get("verify_multi") else ["gemini"]
667
+ with st.spinner("Extracting plots from PDF…"
668
+ + (" (cross-checking with GPT + Claude β€” this is slower)"
669
+ if st.session_state.get("verify_multi") else "")):
670
+ st.session_state.plot_results = extract_images(
671
+ pdf_path, engines=engines,
672
+ check_missed=st.session_state.get("recover_missed", True),
673
+ )
674
  st.session_state.pdf_processed = True
675
+ _reset_plot_mapping()
676
+
677
+ plot_results = st.session_state.plot_results
678
+ if not plot_results:
679
+ st.warning("No plots found in this PDF.")
680
+ return
681
 
682
  plot_results = st.session_state.plot_results
683
  if not plot_results:
 
702
  # Downloads (image2's native zipper)
703
  d1, d2 = st.columns(2)
704
  d1.download_button(
705
+ " Images + metadata (ZIP)",
706
+ data=image2.create_plot_zip(plot_results, include_json=True),
707
  file_name=f"{paper_id}_plots.zip", mime="application/zip",
708
  use_container_width=True, key="dl_plots_zip",
709
  )
710
  d2.download_button(
711
+ " Images only (ZIP)",
712
+ data=image2.create_plot_zip(plot_results, include_json=False),
713
  file_name=f"{paper_id}_images.zip", mime="application/zip",
714
  use_container_width=True, key="dl_images_zip",
715
  )
 
750
 
751
  st.divider()
752
 
753
+ # 3) Figure-centric review β€” each figure with delete/remove controls and
754
+ # (after mapping) the property rows linked to it.
755
+ st.caption("Review the crops below. Remove a bad crop with **βœ•**, or drop a whole "
756
+ "figure with **πŸ—‘**. Editing figures clears the current mapping β€” re-run "
757
+ "mapping afterwards.")
758
  for gi, group in enumerate(plot_results):
759
  caption = group.get("caption", f"Figure {gi+1}")
760
  page = group.get("page", "?")
761
  imgs = group.get("image_data", [])
762
 
763
  with st.container(border=True):
764
+ head, dele = st.columns([0.85, 0.15])
765
+ head.markdown(f"**Page {page}** β€” {caption}")
766
+ if dele.button(" Delete ", key=f"delfig_{gi}", use_container_width=True):
767
+ plot_results.pop(gi)
768
+ st.session_state.plot_results = plot_results
769
+ _reset_plot_mapping()
770
+ st.rerun()
771
 
772
  icols = st.columns(min(len(imgs), 4) or 1)
773
  for pos, im in enumerate(imgs):
 
777
  sub = im.get("subplot_label") or ""
778
  st.image(arr, channels="BGR", width=200,
779
  caption=(sub or None))
780
+
781
  v = im.get("verification") or {}
782
  act = v.get("majority_action") if isinstance(v, dict) else None
783
  if act and act != "keep":
784
+ st.caption(f"crop verdict: **{act}**")
785
+ # per-engine detail when the multi-LLM cross-check ran
786
+ by = v.get("by_engine") if isinstance(v, dict) else None
787
+ if isinstance(by, dict):
788
+ flagged = [e for e, vd in by.items()
789
+ if isinstance(vd, dict) and vd.get("recommended_action") == "discard"]
790
+ if flagged:
791
+ st.caption("flagged non-plot by: " + ", ".join(flagged))
792
+ elif act in (None, "unverified"):
793
+ st.caption("crop verdict: unverified")
794
+
795
+ if st.button("βœ• Remove", key=f"rmimg_{gi}_{pos}", use_container_width=True):
796
+ imgs.pop(pos)
797
+ if not imgs:
798
+ plot_results.pop(gi)
799
+ st.session_state.plot_results = plot_results
800
+ _reset_plot_mapping()
801
+ st.rerun()
802
 
803
  if mapping_done:
804
  grp = _links_for_group(links_df, gi)
 
845
  "attaches each crop's path. Push conforms those rows to the chosen "
846
  "category table's 33-column schema and uploads them.")
847
 
848
+ if st.button("Store to database (SQLite)", use_container_width=True):
849
  with st.spinner("Saving crops and writing SQLite…"):
850
  st.session_state.store = mapper5.store_properties_with_plots(
851
  st.session_state.df_aug,
 
933
  "pdf_extracted_df": pd.DataFrame(),
934
  "pdf_extracted_meta": {},
935
  "pdf_doi": "",
936
+ "verify_multi": False,
937
+ "recover_missed": True,
938
  }
939
  for k, v in defaults.items():
940
  if k not in st.session_state:
 
975
  st.tabs(["Material Data", "Extracted Plots"])
976
  return
977
 
978
+ tab1, tab2 = st.tabs([" Material Data", " Extracted Plots"])
979
 
980
  tmp_file = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False, prefix="matdb_")
981
  try: