gvlktejaswi commited on
Commit
40cbdc5
·
verified ·
1 Parent(s): 6c11134

Delete page_files/categorized/Backend/plot_property_mapper.py

Browse files
page_files/categorized/Backend/plot_property_mapper.py DELETED
@@ -1,385 +0,0 @@
1
- """
2
- plot_property_mapper.py
3
- -----------------------
4
- Maps extracted plot images to material properties stored in PostgreSQL.
5
-
6
- Strategy:
7
- 1. Fetch all properties for the material from the DB (Polymers / Fibers / Composites_materials)
8
- 2. For each plot: send image + caption + extracted JSON data to Gemini
9
- 3. Gemini returns the best-matching property_name + confidence reasoning
10
- 4. Caller can confirm/override and persist the match
11
-
12
- DB schema (per table: Polymers, Fibers, Composites_materials):
13
- material_name, material_abbreviation, section,
14
- property_name, value, unit, english, test_condition, comments
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- import base64
20
- import json
21
- import os
22
- import re
23
- from io import BytesIO
24
- from typing import Any
25
-
26
- import cv2
27
- import numpy as np
28
- import requests
29
- from PIL import Image
30
-
31
- # ---------------------------------------------------------------------------
32
- # Gemini config (re-uses the same key / model you already use)
33
- # ---------------------------------------------------------------------------
34
- _GEMINI_KEY = os.getenv(
35
- "GEMINI_API_KEY",
36
- "AIzaSyAuI-qwSCRpdAcGTvNNaS70NkxlLMSrWF0", # fallback – prefer env var
37
- )
38
- _GEMINI_MODEL = "gemini-2.5-flash-preview-09-2025"
39
- _GEMINI_URL = (
40
- f"https://generativelanguage.googleapis.com/v1beta/"
41
- f"models/{_GEMINI_MODEL}:generateContent?key={_GEMINI_KEY}"
42
- )
43
-
44
- # ---------------------------------------------------------------------------
45
- # Table routing (mirrors data_loader.py)
46
- # ---------------------------------------------------------------------------
47
- TABLE_MAP = {
48
- "Polymer": "Polymers",
49
- "Fiber": "Fibers",
50
- "Composite": "Composites_materials",
51
- }
52
-
53
- # ---------------------------------------------------------------------------
54
- # DB helpers (thin wrappers – import your existing fetch_all from db.py)
55
- # ---------------------------------------------------------------------------
56
-
57
- def fetch_properties_for_material(
58
- material_abbr: str,
59
- material_class: str,
60
- fetch_all_fn, # pass db.fetch_all so we don't re-import
61
- ) -> list[dict]:
62
- """
63
- Return all property rows for a given material abbreviation.
64
- Falls back to all rows in the table if nothing matches the abbreviation.
65
- """
66
- table = TABLE_MAP.get(material_class)
67
- if not table:
68
- raise ValueError(f"Unknown material_class: {material_class!r}")
69
-
70
- query = f"""
71
- SELECT
72
- material_name,
73
- material_abbreviation,
74
- section,
75
- property_name,
76
- value,
77
- unit,
78
- english,
79
- test_condition,
80
- comments
81
- FROM "{table}"
82
- WHERE LOWER(material_abbreviation) = LOWER(:abbr)
83
- ORDER BY section, property_name
84
- """
85
- rows = fetch_all_fn(query, {"abbr": material_abbr})
86
-
87
- # fallback: try by name fragment
88
- if not rows:
89
- query2 = f"""
90
- SELECT
91
- material_name, material_abbreviation, section,
92
- property_name, value, unit, english, test_condition, comments
93
- FROM "{table}"
94
- ORDER BY section, property_name
95
- """
96
- rows = fetch_all_fn(query2)
97
-
98
- return rows
99
-
100
-
101
- def save_plot_image_mapping(
102
- material_abbr: str,
103
- property_name: str,
104
- section: str,
105
- image_array: np.ndarray, # BGR numpy array from cv2
106
- save_dir: str = "images",
107
- ) -> str:
108
- """
109
- Save the plot image to disk as <save_dir>/<abbr>_<safe_property>.png
110
- Returns the file path.
111
- """
112
- os.makedirs(save_dir, exist_ok=True)
113
- safe_prop = re.sub(r"[^\w\s-]", "", property_name).strip().replace(" ", "_")
114
- filename = f"{material_abbr}_{safe_prop}.png"
115
- filepath = os.path.join(save_dir, filename)
116
- cv2.imwrite(filepath, image_array)
117
- return filepath
118
-
119
-
120
- # ---------------------------------------------------------------------------
121
- # Core: Gemini image + data → property match
122
- # ---------------------------------------------------------------------------
123
-
124
- def _encode_image_bgr(bgr: np.ndarray) -> tuple[str, str]:
125
- """Convert BGR numpy array → base64 PNG string."""
126
- rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
127
- pil = Image.fromarray(rgb)
128
- buf = BytesIO()
129
- pil.save(buf, format="PNG")
130
- b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
131
- return b64, "image/png"
132
-
133
-
134
- def map_plot_to_property(
135
- image_bgr: np.ndarray,
136
- caption: str,
137
- extracted_json: dict[str, Any], # full Gemini data-extraction output
138
- db_properties: list[dict], # rows from fetch_properties_for_material
139
- gemini_api_key: str | None = None,
140
- ) -> dict:
141
- """
142
- Ask Gemini which DB property best matches this plot.
143
-
144
- Returns
145
- -------
146
- {
147
- "property_name": str, # best match from DB
148
- "section": str,
149
- "confidence": "high"|"medium"|"low",
150
- "reasoning": str,
151
- "db_row": dict | None, # full matching DB row
152
- "all_candidates": list[dict], # top-3 ranked by Gemini
153
- }
154
- """
155
- key = gemini_api_key or _GEMINI_KEY
156
- url = (
157
- f"https://generativelanguage.googleapis.com/v1beta/"
158
- f"models/{_GEMINI_MODEL}:generateContent?key={key}"
159
- )
160
-
161
- # Build a compact list of DB properties for the prompt
162
- prop_list_text = "\n".join(
163
- f" - [{row['section']}] {row['property_name']} "
164
- f"(value={row['value']}, unit={row['unit']})"
165
- for row in db_properties[:80] # cap at 80 to stay within context
166
- )
167
-
168
- # Compact extracted JSON (just material + property names + values)
169
- extracted_summary = {
170
- "material_name": extracted_json.get("material_name", ""),
171
- "material_abbreviation": extracted_json.get("material_abbreviation", ""),
172
- "properties": [
173
- {
174
- "section": p.get("section"),
175
- "property_name": p.get("property_name"),
176
- "value": p.get("value"),
177
- "unit": p.get("unit"),
178
- }
179
- for p in extracted_json.get("mechanical_properties", [])[:40]
180
- ],
181
- }
182
-
183
- prompt = f"""You are an expert materials scientist.
184
-
185
- TASK: Identify which property from the DATABASE LIST best matches the provided plot image.
186
-
187
- PLOT CAPTION:
188
- "{caption}"
189
-
190
- EXTRACTED TEXT DATA FROM THE SAME PDF (JSON summary):
191
- {json.dumps(extracted_summary, indent=2)}
192
-
193
- DATABASE PROPERTIES FOR THIS MATERIAL:
194
- {prop_list_text}
195
-
196
- INSTRUCTIONS:
197
- 1. Examine the plot image carefully (axes labels, units, curve shapes, legend text).
198
- 2. Use the caption AND the extracted JSON data as additional context.
199
- 3. Select the TOP 3 best-matching property names from the DATABASE PROPERTIES list above.
200
- 4. For each candidate give a confidence: high / medium / low.
201
- 5. Return ONLY valid JSON — no markdown, no explanation outside the JSON.
202
-
203
- REQUIRED JSON FORMAT:
204
- {{
205
- "best_match": {{
206
- "property_name": "<exact name from DB list>",
207
- "section": "<section from DB list>",
208
- "confidence": "high|medium|low",
209
- "reasoning": "<1-2 sentence explanation>"
210
- }},
211
- "candidates": [
212
- {{"rank": 1, "property_name": "...", "section": "...", "confidence": "..."}},
213
- {{"rank": 2, "property_name": "...", "section": "...", "confidence": "..."}},
214
- {{"rank": 3, "property_name": "...", "section": "...", "confidence": "..."}}
215
- ]
216
- }}
217
- """
218
-
219
- img_b64, img_mime = _encode_image_bgr(image_bgr)
220
-
221
- payload = {
222
- "contents": [
223
- {
224
- "parts": [
225
- {"text": prompt},
226
- {"inlineData": {"mimeType": img_mime, "data": img_b64}},
227
- ]
228
- }
229
- ],
230
- "generationConfig": {
231
- "temperature": 0.0,
232
- "responseMimeType": "application/json",
233
- },
234
- }
235
-
236
- try:
237
- resp = requests.post(url, json=payload, timeout=120)
238
- resp.raise_for_status()
239
- raw = resp.json()
240
-
241
- parts = raw.get("candidates", [{}])[0].get("content", {}).get("parts", [])
242
- json_text = ""
243
- for p in parts:
244
- t = p.get("text", "")
245
- if t.strip().startswith("{"):
246
- json_text = t
247
- break
248
-
249
- if not json_text:
250
- return _empty_result("Gemini returned no JSON")
251
-
252
- result = json.loads(json_text)
253
-
254
- except Exception as exc:
255
- return _empty_result(str(exc))
256
-
257
- # Attach full DB row to best_match
258
- best = result.get("best_match", {})
259
- matched_prop = best.get("property_name", "")
260
- db_row = next(
261
- (r for r in db_properties if r["property_name"] == matched_prop),
262
- None,
263
- )
264
- best["db_row"] = db_row
265
-
266
- return {
267
- "property_name": matched_prop,
268
- "section": best.get("section", ""),
269
- "confidence": best.get("confidence", "low"),
270
- "reasoning": best.get("reasoning", ""),
271
- "db_row": db_row,
272
- "all_candidates": result.get("candidates", []),
273
- }
274
-
275
-
276
- def _empty_result(error: str) -> dict:
277
- return {
278
- "property_name": "",
279
- "section": "",
280
- "confidence": "low",
281
- "reasoning": f"Error: {error}",
282
- "db_row": None,
283
- "all_candidates": [],
284
- }
285
-
286
-
287
- # ---------------------------------------------------------------------------
288
- # Batch mapper (call once per PDF after extraction)
289
- # ---------------------------------------------------------------------------
290
-
291
- def batch_map_plots(
292
- image_results: list[dict], # from extract_images() in upload_backend.py
293
- extracted_json: dict, # from call_gemini_from_bytes()
294
- db_properties: list[dict], # from fetch_properties_for_material()
295
- gemini_api_key: str | None = None,
296
- progress_callback=None, # optional fn(current, total, caption)
297
- ) -> list[dict]:
298
- """
299
- Map every extracted plot to a DB property.
300
-
301
- Returns a list parallel to image_results:
302
- [
303
- {
304
- "caption": str,
305
- "page": int,
306
- "image_data": [...], # original image_data list
307
- "mapping_result": { ...map_plot_to_property output... }
308
- },
309
- ...
310
- ]
311
- """
312
- total = len(image_results)
313
- out = []
314
-
315
- for i, item in enumerate(image_results):
316
- caption = item.get("caption", "")
317
- page = item.get("page", 0)
318
-
319
- if progress_callback:
320
- progress_callback(i, total, caption)
321
-
322
- # Use first sub-image for mapping (usually there's only one per caption)
323
- img_list = item.get("image_data", [])
324
- if not img_list:
325
- out.append({**item, "mapping_result": _empty_result("No image data")})
326
- continue
327
-
328
- bgr = img_list[0].get("array")
329
- if bgr is None:
330
- out.append({**item, "mapping_result": _empty_result("Missing array")})
331
- continue
332
-
333
- result = map_plot_to_property(
334
- image_bgr=bgr,
335
- caption=caption,
336
- extracted_json=extracted_json,
337
- db_properties=db_properties,
338
- gemini_api_key=gemini_api_key,
339
- )
340
-
341
- out.append({
342
- "caption": caption,
343
- "page": page,
344
- "image_data": img_list,
345
- "mapping_result": result,
346
- })
347
-
348
- return out
349
-
350
- def save_plot_image_to_db(
351
- material_abbr: str,
352
- property_name: str,
353
- image_bgr,
354
- material_class: str,
355
- execute_query_fn,
356
- ) -> bool:
357
- """Save plot image as BYTEA into the matching property row in PostgreSQL."""
358
-
359
- table_map = {
360
- "Polymer": "Polymers",
361
- "Fiber": "Fibers",
362
- "Composite": "Composites_materials",
363
- }
364
- table = table_map.get(material_class)
365
- if not table:
366
- return False
367
-
368
- _, buffer = cv2.imencode(".png", image_bgr)
369
- image_bytes = buffer.tobytes()
370
-
371
- query = f"""
372
- UPDATE "{table}"
373
- SET image = :image
374
- WHERE LOWER(material_abbreviation) = LOWER(:abbr)
375
- AND LOWER(property_name) = LOWER(:prop)
376
- """
377
- rows_updated = execute_query_fn(
378
- query,
379
- {
380
- "image": image_bytes,
381
- "abbr": material_abbr,
382
- "prop": property_name,
383
- }
384
- )
385
- return rows_updated > 0