Update page_files/categorized/Backend/mapper5.py
Browse files
page_files/categorized/Backend/mapper5.py
CHANGED
|
@@ -5,10 +5,7 @@ mapper.py — Plot ⇄ Property mapping + persistence
|
|
| 5 |
Combines what used to be two files (Code 3 "mapping" + Code 4 "storage")
|
| 6 |
into one module. It sits on top of:
|
| 7 |
|
| 8 |
-
|
| 9 |
-
(imported via importlib, since a module name can't start
|
| 10 |
-
with a digit)
|
| 11 |
-
image2.py — Code 2: plot/figure extraction + crop verification
|
| 12 |
|
| 13 |
WHAT IT DOES
|
| 14 |
------------
|
|
@@ -314,7 +311,7 @@ def _get_shared_embed_model() -> Optional[Any]:
|
|
| 314 |
_embed_tried = True
|
| 315 |
try:
|
| 316 |
import importlib
|
| 317 |
-
_embed_model = importlib.import_module("
|
| 318 |
log.info("mapper: reusing Code 1's SciBERT embedding model.")
|
| 319 |
return _embed_model
|
| 320 |
except Exception as e:
|
|
@@ -847,7 +844,9 @@ def store_properties_with_plots(
|
|
| 847 |
df = df_augmented.reset_index(drop=True).copy()
|
| 848 |
|
| 849 |
mapped_gis = set(int(g) for g in links_df["group_idx"].tolist()) if not links_df.empty else set()
|
| 850 |
-
|
|
|
|
|
|
|
| 851 |
|
| 852 |
path_map, groups = save_plot_images(plot_results, out_dir, pdf_stem, group_idxs=group_idxs)
|
| 853 |
group_by_idx = {g["group_idx"]: g for g in groups}
|
|
@@ -1116,7 +1115,7 @@ def _extract(pdf_bytes: bytes, doi_override: str = "",
|
|
| 1116 |
"""Run Code 1 (2.py) + Code 2 (image2.py) ONCE. Returns a bundle with all
|
| 1117 |
six property tables + plot_results + coverage + meta + errors."""
|
| 1118 |
import importlib
|
| 1119 |
-
run_pipeline = importlib.import_module("
|
| 1120 |
from Pdf_ImageExtraction import extract_and_verify_plots
|
| 1121 |
(dfc, dfv, dfcv, dfg, dfp, dfcl, _chunks, errors, meta) = run_pipeline(
|
| 1122 |
pdf_bytes, doi_override=doi_override)
|
|
@@ -1135,7 +1134,7 @@ def _extract(pdf_bytes: bytes, doi_override: str = "",
|
|
| 1135 |
def get_or_extract(pdf_bytes: bytes, doi_override: str = "", verify_engines=None,
|
| 1136 |
use_disk_cache: bool = True, force: bool = False):
|
| 1137 |
"""Extraction cached by the PDF's CONTENT HASH — never by source_table.
|
| 1138 |
-
On a cache hit,
|
| 1139 |
pruning / re-mapping is free. Returns (pdf_hash, bundle)."""
|
| 1140 |
import pickle
|
| 1141 |
h = _pdf_hash(pdf_bytes)
|
|
@@ -1194,7 +1193,7 @@ def _run_streamlit() -> None:
|
|
| 1194 |
|
| 1195 |
st.set_page_config(page_title="mapper — Plot ⇄ Property", page_icon="🔗", layout="wide")
|
| 1196 |
st.title("🔗 mapper — Plot ⇄ Property mapping + database")
|
| 1197 |
-
st.caption("Runs
|
| 1198 |
"prune what's left, then attaches plots to properties on Map.")
|
| 1199 |
|
| 1200 |
ss = st.session_state
|
|
@@ -1244,13 +1243,13 @@ def _run_streamlit() -> None:
|
|
| 1244 |
|
| 1245 |
# ---- sidebar ------------------------------------------------------------
|
| 1246 |
with st.sidebar:
|
| 1247 |
-
st.header("
|
| 1248 |
st.subheader("Extraction")
|
| 1249 |
use_gpt = st.checkbox("Also verify crops with GPT", value=False)
|
| 1250 |
use_claude = st.checkbox("Also verify crops with Claude", value=False)
|
| 1251 |
doi_manual = st.text_input("DOI override (optional)", placeholder="10.xxxx/...")
|
| 1252 |
use_disk_cache = st.checkbox("Use on-disk extraction cache", value=True,
|
| 1253 |
-
help="Skip
|
| 1254 |
"PDF was extracted before.")
|
| 1255 |
st.divider()
|
| 1256 |
st.subheader("Auto figure filter")
|
|
@@ -1292,12 +1291,12 @@ def _run_streamlit() -> None:
|
|
| 1292 |
use_container_width=True)
|
| 1293 |
else:
|
| 1294 |
ec1.success("✓ This paper is loaded — switch tables / prune / map freely; "
|
| 1295 |
-
"
|
| 1296 |
force = ec2.button("↻ Force re-extract", use_container_width=True)
|
| 1297 |
|
| 1298 |
if do_extract or force:
|
| 1299 |
engines = ["gemini"] + (["gpt"] if use_gpt else []) + (["claude"] if use_claude else [])
|
| 1300 |
-
with st.spinner("Running
|
| 1301 |
_h, bundle = get_or_extract(pdf_bytes, doi_override=doi_manual,
|
| 1302 |
verify_engines=engines,
|
| 1303 |
use_disk_cache=use_disk_cache, force=force)
|
|
@@ -1314,15 +1313,15 @@ def _run_streamlit() -> None:
|
|
| 1314 |
bundle = ss["bundle"]
|
| 1315 |
stem = ss["stem"]
|
| 1316 |
if bundle.get("_cache") == "disk":
|
| 1317 |
-
st.caption("
|
| 1318 |
|
| 1319 |
# ---- three top-level views ---------------------------------------------
|
| 1320 |
view_props, view_plots, work = st.tabs(
|
| 1321 |
-
["
|
| 1322 |
|
| 1323 |
with view_props:
|
| 1324 |
if bundle.get("api_errors"):
|
| 1325 |
-
with st.expander(f"
|
| 1326 |
for e in bundle["api_errors"]:
|
| 1327 |
st.code(e)
|
| 1328 |
meta = bundle.get("meta", {})
|
|
@@ -1384,7 +1383,7 @@ def _run_streamlit() -> None:
|
|
| 1384 |
with st.container(border=True):
|
| 1385 |
hc1, hc2 = st.columns([6, 1])
|
| 1386 |
hc1.markdown(f"**Page {group.get('page')}** — {group.get('caption', '')}")
|
| 1387 |
-
if hc2.button("
|
| 1388 |
_remove_figure(gi)
|
| 1389 |
images = group.get("image_data", [])
|
| 1390 |
icols = st.columns(min(len(images), 4) or 1)
|
|
@@ -1399,7 +1398,7 @@ def _run_streamlit() -> None:
|
|
| 1399 |
if st.button("x remove", key=f"rmimg_{gi}_{idx}"):
|
| 1400 |
_remove_image(gi, idx)
|
| 1401 |
|
| 1402 |
-
map_label = "
|
| 1403 |
if st.button(map_label, type="primary", use_container_width=True):
|
| 1404 |
source_df = bundle["table_map"][source_table]
|
| 1405 |
with st.spinner("Mapping property rows to figures…"):
|
|
@@ -1468,13 +1467,13 @@ def _run_streamlit() -> None:
|
|
| 1468 |
out.append((imgs[ix].get("array"), cap))
|
| 1469 |
return out
|
| 1470 |
|
| 1471 |
-
names = ["
|
| 1472 |
-
f"
|
| 1473 |
-
f"
|
| 1474 |
-
"
|
| 1475 |
if store:
|
| 1476 |
-
names.insert(3, f"
|
| 1477 |
-
names.append("
|
| 1478 |
wt = st.tabs(names)
|
| 1479 |
prop_img_tab = wt[-1]
|
| 1480 |
|
|
@@ -1584,7 +1583,7 @@ def _run_streamlit() -> None:
|
|
| 1584 |
st.markdown("**Store the curated result to the database**")
|
| 1585 |
st.caption("Writes the pruned figures + edited links; each stored row points at "
|
| 1586 |
"its saved crop on disk. Re-running for the same PDF overwrites cleanly.")
|
| 1587 |
-
if st.button("
|
| 1588 |
with st.spinner("Saving crops and writing SQLite…"):
|
| 1589 |
ss["store"] = store_properties_with_plots(
|
| 1590 |
ss["df_aug"], ss["links_df"], ss["plot_curated"],
|
|
@@ -1608,14 +1607,14 @@ def _run_streamlit() -> None:
|
|
| 1608 |
embed_img = st.checkbox("Embed matched plot into the table's image column",
|
| 1609 |
value=True)
|
| 1610 |
cA, cB = st.columns(2)
|
| 1611 |
-
if cA.button("
|
| 1612 |
try:
|
| 1613 |
db_healthcheck()
|
| 1614 |
st.success(f"Connected to {DB_HOST} ({DB_KIND}).")
|
| 1615 |
except Exception as e:
|
| 1616 |
st.error(f"Connection failed: {e}")
|
| 1617 |
push_ready = bool(store) and store.get("n_properties", 0) > 0
|
| 1618 |
-
if cB.button(f"
|
| 1619 |
use_container_width=True, disabled=not push_ready):
|
| 1620 |
try:
|
| 1621 |
with st.spinner(f"Conforming + writing rows to '{category}'…"):
|
|
@@ -1628,24 +1627,24 @@ def _run_streamlit() -> None:
|
|
| 1628 |
except Exception as e:
|
| 1629 |
st.error(f"Push failed: {e}")
|
| 1630 |
if not push_ready:
|
| 1631 |
-
st.caption("Click **
|
| 1632 |
"uploads those stored rows.")
|
| 1633 |
|
| 1634 |
st.divider()
|
| 1635 |
st.markdown("**Downloads**")
|
| 1636 |
d1, d2, d3 = st.columns(3)
|
| 1637 |
d1.download_button(
|
| 1638 |
-
"
|
| 1639 |
links_df.drop(columns=["link_id"], errors="ignore").to_csv(index=False).encode()
|
| 1640 |
if not links_df.empty else b"",
|
| 1641 |
f"{stem}_links.csv", "text/csv", use_container_width=True, disabled=links_df.empty)
|
| 1642 |
d2.download_button(
|
| 1643 |
-
"
|
| 1644 |
df_aug.drop(columns=["doi_url"], errors="ignore").to_csv(index=False).encode(),
|
| 1645 |
f"{stem}_properties_mapped.csv", "text/csv", use_container_width=True)
|
| 1646 |
if store:
|
| 1647 |
d3.download_button(
|
| 1648 |
-
"
|
| 1649 |
data=bundle_zip(store["out_dir"], store["pdf_stem"], store["db_filename"]),
|
| 1650 |
file_name=f"{stem}_db_bundle.zip", mime="application/zip",
|
| 1651 |
use_container_width=True)
|
|
@@ -1680,4 +1679,6 @@ if __name__ == "__main__":
|
|
| 1680 |
" links_df, df_aug = map_plots_to_properties(df_consensus, plot_results)\n"
|
| 1681 |
" store_properties_with_plots(df_aug, links_df, plot_results,\n"
|
| 1682 |
" out_dir='./aim_efrc_db', pdf_stem='paper1')\n"
|
| 1683 |
-
)
|
|
|
|
|
|
|
|
|
| 5 |
Combines what used to be two files (Code 3 "mapping" + Code 4 "storage")
|
| 6 |
into one module. It sits on top of:
|
| 7 |
|
| 8 |
+
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
WHAT IT DOES
|
| 11 |
------------
|
|
|
|
| 311 |
_embed_tried = True
|
| 312 |
try:
|
| 313 |
import importlib
|
| 314 |
+
_embed_model = importlib.import_module("Pdf_DataExtraction")._get_embed_model() # Code 1 = 2.py
|
| 315 |
log.info("mapper: reusing Code 1's SciBERT embedding model.")
|
| 316 |
return _embed_model
|
| 317 |
except Exception as e:
|
|
|
|
| 844 |
df = df_augmented.reset_index(drop=True).copy()
|
| 845 |
|
| 846 |
mapped_gis = set(int(g) for g in links_df["group_idx"].tolist()) if not links_df.empty else set()
|
| 847 |
+
# None = save every detected crop. If nothing mapped (empty links), fall back
|
| 848 |
+
# to saving all crops so images still persist instead of writing zero.
|
| 849 |
+
group_idxs = None if (save_all_group_images or not mapped_gis) else mapped_gis
|
| 850 |
|
| 851 |
path_map, groups = save_plot_images(plot_results, out_dir, pdf_stem, group_idxs=group_idxs)
|
| 852 |
group_by_idx = {g["group_idx"]: g for g in groups}
|
|
|
|
| 1115 |
"""Run Code 1 (2.py) + Code 2 (image2.py) ONCE. Returns a bundle with all
|
| 1116 |
six property tables + plot_results + coverage + meta + errors."""
|
| 1117 |
import importlib
|
| 1118 |
+
run_pipeline = importlib.import_module("Pdf_DataExtraction").run_pipeline
|
| 1119 |
from Pdf_ImageExtraction import extract_and_verify_plots
|
| 1120 |
(dfc, dfv, dfcv, dfg, dfp, dfcl, _chunks, errors, meta) = run_pipeline(
|
| 1121 |
pdf_bytes, doi_override=doi_override)
|
|
|
|
| 1134 |
def get_or_extract(pdf_bytes: bytes, doi_override: str = "", verify_engines=None,
|
| 1135 |
use_disk_cache: bool = True, force: bool = False):
|
| 1136 |
"""Extraction cached by the PDF's CONTENT HASH — never by source_table.
|
| 1137 |
+
On a cache hit, 2.py and image2.py are NOT re-run, so switching tables /
|
| 1138 |
pruning / re-mapping is free. Returns (pdf_hash, bundle)."""
|
| 1139 |
import pickle
|
| 1140 |
h = _pdf_hash(pdf_bytes)
|
|
|
|
| 1193 |
|
| 1194 |
st.set_page_config(page_title="mapper — Plot ⇄ Property", page_icon="🔗", layout="wide")
|
| 1195 |
st.title("🔗 mapper — Plot ⇄ Property mapping + database")
|
| 1196 |
+
st.caption("Runs 2.py + image2.py once (cached), auto-drops non-plots, lets you "
|
| 1197 |
"prune what's left, then attaches plots to properties on Map.")
|
| 1198 |
|
| 1199 |
ss = st.session_state
|
|
|
|
| 1243 |
|
| 1244 |
# ---- sidebar ------------------------------------------------------------
|
| 1245 |
with st.sidebar:
|
| 1246 |
+
st.header("Settings")
|
| 1247 |
st.subheader("Extraction")
|
| 1248 |
use_gpt = st.checkbox("Also verify crops with GPT", value=False)
|
| 1249 |
use_claude = st.checkbox("Also verify crops with Claude", value=False)
|
| 1250 |
doi_manual = st.text_input("DOI override (optional)", placeholder="10.xxxx/...")
|
| 1251 |
use_disk_cache = st.checkbox("Use on-disk extraction cache", value=True,
|
| 1252 |
+
help="Skip 2.py + image2.py entirely when this exact "
|
| 1253 |
"PDF was extracted before.")
|
| 1254 |
st.divider()
|
| 1255 |
st.subheader("Auto figure filter")
|
|
|
|
| 1291 |
use_container_width=True)
|
| 1292 |
else:
|
| 1293 |
ec1.success("✓ This paper is loaded — switch tables / prune / map freely; "
|
| 1294 |
+
"2.py and image2.py will NOT re-run.")
|
| 1295 |
force = ec2.button("↻ Force re-extract", use_container_width=True)
|
| 1296 |
|
| 1297 |
if do_extract or force:
|
| 1298 |
engines = ["gemini"] + (["gpt"] if use_gpt else []) + (["claude"] if use_claude else [])
|
| 1299 |
+
with st.spinner("Running 2.py extraction/consensus + image2.py plot detection…"):
|
| 1300 |
_h, bundle = get_or_extract(pdf_bytes, doi_override=doi_manual,
|
| 1301 |
verify_engines=engines,
|
| 1302 |
use_disk_cache=use_disk_cache, force=force)
|
|
|
|
| 1313 |
bundle = ss["bundle"]
|
| 1314 |
stem = ss["stem"]
|
| 1315 |
if bundle.get("_cache") == "disk":
|
| 1316 |
+
st.caption(" Loaded from on-disk cache — 2.py and image2.py were not re-run.")
|
| 1317 |
|
| 1318 |
# ---- three top-level views ---------------------------------------------
|
| 1319 |
view_props, view_plots, work = st.tabs(
|
| 1320 |
+
[" Extraction (2.py)", " Plots (image2.py)", " Map & curate"])
|
| 1321 |
|
| 1322 |
with view_props:
|
| 1323 |
if bundle.get("api_errors"):
|
| 1324 |
+
with st.expander(f" {len(bundle['api_errors'])} API error(s)"):
|
| 1325 |
for e in bundle["api_errors"]:
|
| 1326 |
st.code(e)
|
| 1327 |
meta = bundle.get("meta", {})
|
|
|
|
| 1383 |
with st.container(border=True):
|
| 1384 |
hc1, hc2 = st.columns([6, 1])
|
| 1385 |
hc1.markdown(f"**Page {group.get('page')}** — {group.get('caption', '')}")
|
| 1386 |
+
if hc2.button(" Remove figure", key=f"rmfig_{gi}"):
|
| 1387 |
_remove_figure(gi)
|
| 1388 |
images = group.get("image_data", [])
|
| 1389 |
icols = st.columns(min(len(images), 4) or 1)
|
|
|
|
| 1398 |
if st.button("x remove", key=f"rmimg_{gi}_{idx}"):
|
| 1399 |
_remove_image(gi, idx)
|
| 1400 |
|
| 1401 |
+
map_label = " Map — attach plots to properties" + (" (re-map)" if ss.get("mapped") else "")
|
| 1402 |
if st.button(map_label, type="primary", use_container_width=True):
|
| 1403 |
source_df = bundle["table_map"][source_table]
|
| 1404 |
with st.spinner("Mapping property rows to figures…"):
|
|
|
|
| 1467 |
out.append((imgs[ix].get("array"), cap))
|
| 1468 |
return out
|
| 1469 |
|
| 1470 |
+
names = [" Figures & attached rows (remove links)",
|
| 1471 |
+
f" All links ({len(links_df)})",
|
| 1472 |
+
f" Augmented ({n_rows})",
|
| 1473 |
+
" Store / Export"]
|
| 1474 |
if store:
|
| 1475 |
+
names.insert(3, f" Stored ({store['n_properties']})")
|
| 1476 |
+
names.append(" Properties + image")
|
| 1477 |
wt = st.tabs(names)
|
| 1478 |
prop_img_tab = wt[-1]
|
| 1479 |
|
|
|
|
| 1583 |
st.markdown("**Store the curated result to the database**")
|
| 1584 |
st.caption("Writes the pruned figures + edited links; each stored row points at "
|
| 1585 |
"its saved crop on disk. Re-running for the same PDF overwrites cleanly.")
|
| 1586 |
+
if st.button(" Store to database", type="primary", use_container_width=True):
|
| 1587 |
with st.spinner("Saving crops and writing SQLite…"):
|
| 1588 |
ss["store"] = store_properties_with_plots(
|
| 1589 |
ss["df_aug"], ss["links_df"], ss["plot_curated"],
|
|
|
|
| 1607 |
embed_img = st.checkbox("Embed matched plot into the table's image column",
|
| 1608 |
value=True)
|
| 1609 |
cA, cB = st.columns(2)
|
| 1610 |
+
if cA.button(" Test connection", use_container_width=True):
|
| 1611 |
try:
|
| 1612 |
db_healthcheck()
|
| 1613 |
st.success(f"Connected to {DB_HOST} ({DB_KIND}).")
|
| 1614 |
except Exception as e:
|
| 1615 |
st.error(f"Connection failed: {e}")
|
| 1616 |
push_ready = bool(store) and store.get("n_properties", 0) > 0
|
| 1617 |
+
if cB.button(f" Push to {category}", type="primary",
|
| 1618 |
use_container_width=True, disabled=not push_ready):
|
| 1619 |
try:
|
| 1620 |
with st.spinner(f"Conforming + writing rows to '{category}'…"):
|
|
|
|
| 1627 |
except Exception as e:
|
| 1628 |
st.error(f"Push failed: {e}")
|
| 1629 |
if not push_ready:
|
| 1630 |
+
st.caption("Click ** Store to database** first — the RDS push "
|
| 1631 |
"uploads those stored rows.")
|
| 1632 |
|
| 1633 |
st.divider()
|
| 1634 |
st.markdown("**Downloads**")
|
| 1635 |
d1, d2, d3 = st.columns(3)
|
| 1636 |
d1.download_button(
|
| 1637 |
+
" Links CSV",
|
| 1638 |
links_df.drop(columns=["link_id"], errors="ignore").to_csv(index=False).encode()
|
| 1639 |
if not links_df.empty else b"",
|
| 1640 |
f"{stem}_links.csv", "text/csv", use_container_width=True, disabled=links_df.empty)
|
| 1641 |
d2.download_button(
|
| 1642 |
+
" Augmented CSV",
|
| 1643 |
df_aug.drop(columns=["doi_url"], errors="ignore").to_csv(index=False).encode(),
|
| 1644 |
f"{stem}_properties_mapped.csv", "text/csv", use_container_width=True)
|
| 1645 |
if store:
|
| 1646 |
d3.download_button(
|
| 1647 |
+
" DB + plots (ZIP)",
|
| 1648 |
data=bundle_zip(store["out_dir"], store["pdf_stem"], store["db_filename"]),
|
| 1649 |
file_name=f"{stem}_db_bundle.zip", mime="application/zip",
|
| 1650 |
use_container_width=True)
|
|
|
|
| 1679 |
" links_df, df_aug = map_plots_to_properties(df_consensus, plot_results)\n"
|
| 1680 |
" store_properties_with_plots(df_aug, links_df, plot_results,\n"
|
| 1681 |
" out_dir='./aim_efrc_db', pdf_stem='paper1')\n"
|
| 1682 |
+
)
|
| 1683 |
+
|
| 1684 |
+
|