super-document-processor 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ import hashlib
2
+ import base64
3
+ import logging
4
+ import threading
5
+ import concurrent.futures
6
+
7
+ logger = logging.getLogger("super_document_processor.pdf_images")
8
+
9
+ try:
10
+ from super_document_processor.utils.vlm import generate_image_description
11
+ except ImportError:
12
+ from utils.vlm import generate_image_description
13
+
14
+
15
+ def process_pdf_full_page_image(page, page_num, descriptions, model=None):
16
+ """Helper function to process the entire page as an image."""
17
+ pix = page.get_pixmap(dpi=200)
18
+ full_page_bytes = pix.tobytes("png")
19
+ raw_description = generate_image_description(full_page_bytes, model=model)
20
+ labeled_description = f"[Image description for full page {page_num + 1}]:\n{raw_description}"
21
+ descriptions.append((0, labeled_description))
22
+ return descriptions, True
23
+
24
+ def process_image(seen_image_hashes, image_data_store, image_page_map, page, page_num, doc, lock=None, model=None):
25
+ if lock is None:
26
+ lock = threading.Lock()
27
+ descriptions = []
28
+ need_full_page_ocr = False
29
+
30
+ text = page.get_text().strip()
31
+ page_text_detected = bool(text)
32
+
33
+ # 1st Condition: If vector drawings exist and no text detected
34
+ drawings = page.get_drawings()
35
+ logger.debug(f"Vector paths found on page {page_num + 1}: {len(drawings)}")
36
+
37
+ if len(drawings) > 0 and page_text_detected == False:
38
+ logger.debug(f"Processing full page image for description on page {page_num + 1}")
39
+ return process_pdf_full_page_image(page, page_num, descriptions, model=model)
40
+
41
+ # 2nd Condition: Otherwise, process individual images on the page
42
+ images = page.get_images(full=True)
43
+ logger.debug(f"Found {len(images)} images on page {page_num + 1}")
44
+
45
+ if len(images) > 5:
46
+ logger.debug(f"More than 5 images on page {page_num + 1}, processing full page image")
47
+ return process_pdf_full_page_image(page, page_num, descriptions, model=model)
48
+
49
+ # 3rd Condition: Otherwise, process each image individually (in parallel)
50
+ def process_single_image(img_index, img_info):
51
+ xref = img_info[0]
52
+ base_image = doc.extract_image(xref)
53
+ img_bytes = base_image["image"]
54
+
55
+ # Hash image bytes
56
+ img_hash = hashlib.sha256(img_bytes).hexdigest()
57
+
58
+ with lock:
59
+ if (page_num + 1) not in image_page_map[img_hash]:
60
+ image_page_map[img_hash].append(page_num + 1)
61
+ if img_hash not in image_data_store:
62
+ image_data_store[img_hash] = base64.b64encode(img_bytes).decode("utf-8")
63
+ description = seen_image_hashes.get(img_hash)
64
+
65
+ if description is None:
66
+ logger.debug(f"Generating LLM description for image {img_index + 1} on page {page_num + 1}")
67
+ description = generate_image_description(img_bytes, model=model)
68
+ with lock:
69
+ seen_image_hashes[img_hash] = description
70
+ else:
71
+ logger.debug(f"Reusing description for duplicate image on page {page_num + 1}")
72
+
73
+ labeled_description = f"[Image description for image {img_index + 1} on page {page_num + 1}]:\n{description}\n[Image description {img_index+1} end]"
74
+
75
+ image_rects = page.get_image_rects(img_info)
76
+ if not image_rects:
77
+ logger.debug(f"No bbox for image {img_index + 1} on page {page_num + 1}, skipping")
78
+ return None
79
+ img_bbox = image_rects[0]
80
+ return (img_bbox.y0, labeled_description)
81
+
82
+ workers = int(os.environ.get("MAX_WORKERS", 3))
83
+ with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
84
+ futures = [executor.submit(process_single_image, img_index, img_info) for img_index, img_info in enumerate(images)]
85
+ for future in concurrent.futures.as_completed(futures):
86
+ result = future.result()
87
+ if result:
88
+ descriptions.append(result)
89
+
90
+ return descriptions, need_full_page_ocr