Skip to content

Commit b2eafd7

Browse files
committed
bbox update
1 parent 1ae3254 commit b2eafd7

File tree

6 files changed

+307
-328
lines changed

6 files changed

+307
-328
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: MIT-0
3+
4+
"""
5+
Shared utilities for geometry data conversion.
6+
7+
Consolidates duplicate geometry conversion logic from service.py and granular_service.py.
8+
"""
9+
10+
import os
11+
from typing import Any
12+
13+
from aws_lambda_powertools import Logger
14+
15+
from idp_common.assessment.models import Geometry
16+
17+
logger = Logger(service="assessment", level=os.getenv("LOG_LEVEL", "INFO"))
18+
19+
20+
def process_assessment_geometry(
21+
attr_assessment: dict[str, Any], attr_name: str = ""
22+
) -> dict[str, Any]:
23+
"""
24+
Process and standardize geometry data in assessment response.
25+
26+
Args:
27+
attr_assessment: Assessment dict with potential bbox/page fields
28+
attr_name: Field name for logging
29+
30+
Returns:
31+
Enhanced assessment with standardized geometry
32+
"""
33+
enhanced = attr_assessment.copy()
34+
35+
# Check for bbox data
36+
if "bbox" in attr_assessment and "page" in attr_assessment:
37+
try:
38+
bbox_coords = attr_assessment["bbox"]
39+
page_num = attr_assessment["page"]
40+
41+
if isinstance(bbox_coords, list) and len(bbox_coords) == 4:
42+
# Create Geometry object and convert to UI format
43+
geometry = Geometry.from_bbox_list(bbox_coords, page_num)
44+
enhanced["geometry"] = [geometry.to_ui_format()]
45+
46+
logger.debug(
47+
f"Converted bbox for {attr_name}: {bbox_coords} -> geometry"
48+
)
49+
else:
50+
logger.warning(f"Invalid bbox format for {attr_name}: {bbox_coords}")
51+
except Exception as e:
52+
logger.warning(f"Failed to process bbox for {attr_name}: {e}")
53+
elif "bbox" in attr_assessment and "page" not in attr_assessment:
54+
logger.warning(
55+
f"Found bbox without page for {attr_name} - removing incomplete bbox data"
56+
)
57+
elif "page" in attr_assessment and "bbox" not in attr_assessment:
58+
logger.warning(
59+
f"Found page without bbox for {attr_name} - removing incomplete page data"
60+
)
61+
62+
# Remove raw bbox/page data
63+
enhanced.pop("bbox", None)
64+
enhanced.pop("page", None)
65+
66+
return enhanced
67+
68+
69+
def extract_geometry_from_nested_dict(
70+
data: dict[str, Any], path: list[str] | None = None
71+
) -> dict[str, Any]:
72+
"""
73+
Recursively process geometry data in nested assessment structures.
74+
75+
Args:
76+
data: Assessment data dictionary (may contain nested dicts/lists)
77+
path: Current path for logging
78+
79+
Returns:
80+
Enhanced data with processed geometry
81+
"""
82+
if path is None:
83+
path = []
84+
85+
if not isinstance(data, dict):
86+
return data
87+
88+
result = {}
89+
90+
for key, value in data.items():
91+
current_path = path + [key]
92+
93+
if isinstance(value, dict):
94+
# Check if this looks like an assessment entry
95+
if "confidence" in value or "bbox" in value:
96+
# Process this assessment
97+
result[key] = process_assessment_geometry(value, ".".join(current_path))
98+
else:
99+
# Recurse into nested dict
100+
result[key] = extract_geometry_from_nested_dict(value, current_path)
101+
102+
elif isinstance(value, list):
103+
# Process each item in list
104+
processed_list = []
105+
for i, item in enumerate(value):
106+
if isinstance(item, dict):
107+
processed_list.append(
108+
extract_geometry_from_nested_dict(item, current_path + [str(i)])
109+
)
110+
else:
111+
processed_list.append(item)
112+
result[key] = processed_list
113+
else:
114+
result[key] = value
115+
116+
return result

lib/idp_common_pkg/idp_common/assessment/granular_service.py

Lines changed: 1 addition & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -657,148 +657,7 @@ def _get_text_confidence_data(self, page) -> str:
657657
raise
658658
return ""
659659

660-
def _convert_bbox_to_geometry(
661-
self, bbox_coords: list[float], page_num: int
662-
) -> dict[str, Any]:
663-
"""
664-
Convert [x1,y1,x2,y2] coordinates to geometry format.
665-
666-
Args:
667-
bbox_coords: list of 4 coordinates [x1, y1, x2, y2] in 0-1000 scale
668-
page_num: Page number where the bounding box appears
669-
670-
Returns:
671-
dictionary in geometry format compatible with pattern-1 UI
672-
"""
673-
if len(bbox_coords) != 4:
674-
raise ValueError(f"Expected 4 coordinates, got {len(bbox_coords)}")
675-
676-
x1, y1, x2, y2 = bbox_coords
677-
678-
# Ensure coordinates are in correct order
679-
x1, x2 = min(x1, x2), max(x1, x2)
680-
y1, y2 = min(y1, y2), max(y1, y2)
681-
682-
# Convert from normalized 0-1000 scale to 0-1
683-
left = x1 / 1000.0
684-
top = y1 / 1000.0
685-
width = (x2 - x1) / 1000.0
686-
height = (y2 - y1) / 1000.0
687-
688-
return {
689-
"boundingBox": {"top": top, "left": left, "width": width, "height": height},
690-
"page": page_num,
691-
}
692-
693-
def _process_single_assessment_geometry(
694-
self, attr_assessment: dict[str, Any], attr_name: str = ""
695-
) -> dict[str, Any]:
696-
"""
697-
Process geometry data for a single assessment (with confidence key).
698-
699-
Args:
700-
attr_assessment: Single assessment dictionary with confidence data
701-
attr_name: Name of attribute for logging
702-
703-
Returns:
704-
Enhanced assessment with geometry converted to proper format
705-
"""
706-
enhanced_attr = attr_assessment.copy()
707-
708-
# Check if this assessment includes bbox data
709-
if "bbox" in attr_assessment or "page" in attr_assessment:
710-
# Both bbox and page are required for valid geometry
711-
if "bbox" in attr_assessment and "page" in attr_assessment:
712-
try:
713-
bbox_coords = attr_assessment["bbox"]
714-
page_num = attr_assessment["page"]
715-
716-
# Validate bbox coordinates
717-
if isinstance(bbox_coords, list) and len(bbox_coords) == 4:
718-
# Convert to geometry format
719-
geometry = self._convert_bbox_to_geometry(bbox_coords, page_num)
720-
enhanced_attr["geometry"] = [geometry]
721-
722-
logger.debug(
723-
f"Converted bounding box for {attr_name}: {bbox_coords} -> geometry format"
724-
)
725-
else:
726-
logger.warning(
727-
f"Invalid bounding box format for {attr_name}: {bbox_coords}"
728-
)
729-
except Exception as e:
730-
logger.warning(
731-
f"Failed to process bounding box for {attr_name}: {str(e)}"
732-
)
733-
raise
734-
else:
735-
# If only one of bbox/page exists, log a warning about incomplete data
736-
if "bbox" in attr_assessment and "page" not in attr_assessment:
737-
logger.warning(
738-
f"Found bbox without page for {attr_name} - removing incomplete bbox data"
739-
)
740-
elif "page" in attr_assessment and "bbox" not in attr_assessment:
741-
logger.warning(
742-
f"Found page without bbox for {attr_name} - removing incomplete page data"
743-
)
744-
745-
# Always remove raw bbox/page data from output (whether processed or incomplete)
746-
enhanced_attr.pop("bbox", None)
747-
enhanced_attr.pop("page", None)
748-
749-
return enhanced_attr
750-
751-
def _extract_geometry_from_assessment(
752-
self, assessment_data: dict[str, Any]
753-
) -> dict[str, Any]:
754-
"""
755-
Extract geometry data from assessment response and convert to proper format.
756-
Now supports recursive processing of nested group attributes.
757-
758-
Args:
759-
assessment_data: Dictionary containing assessment results from LLM
760-
761-
Returns:
762-
Enhanced assessment data with geometry information converted to proper format
763-
"""
764-
enhanced_assessment = {}
765-
766-
for attr_name, attr_assessment in assessment_data.items():
767-
if isinstance(attr_assessment, dict):
768-
# Check if this is a direct confidence assessment
769-
if "confidence" in attr_assessment:
770-
# This is a direct assessment - process its geometry
771-
enhanced_assessment[attr_name] = (
772-
self._process_single_assessment_geometry(
773-
attr_assessment, attr_name
774-
)
775-
)
776-
else:
777-
# This is a group attribute (no direct confidence) - recursively process nested attributes
778-
logger.debug(f"Processing group attribute: {attr_name}")
779-
enhanced_assessment[attr_name] = (
780-
self._extract_geometry_from_assessment(attr_assessment)
781-
)
782-
783-
elif isinstance(attr_assessment, list):
784-
# Handle list attributes - process each item recursively
785-
enhanced_list = []
786-
for i, item_assessment in enumerate(attr_assessment):
787-
if isinstance(item_assessment, dict):
788-
# Recursively process each list item
789-
enhanced_item = self._extract_geometry_from_assessment(
790-
item_assessment
791-
)
792-
enhanced_list.append(enhanced_item)
793-
else:
794-
# Non-dict items pass through unchanged
795-
enhanced_list.append(item_assessment)
796-
enhanced_assessment[attr_name] = enhanced_list
797-
else:
798-
# Other types pass through unchanged
799-
enhanced_assessment[attr_name] = attr_assessment
800-
801-
return enhanced_assessment
660+
# Geometry processing uses shared utilities from geometry_utils module
802661

803662
def process_document_section(self, document: Document, section_id: str) -> Document:
804663
"""

0 commit comments

Comments
 (0)