Skip to content

Commit 59cbecf

Browse files
authored
✨ Add real bounding boxes (#86)
1 parent 45519fb commit 59cbecf

File tree

8 files changed

+110
-21
lines changed

8 files changed

+110
-21
lines changed

mindee/fields/amount.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __init__(
1919
:param amount_prediction: Amount prediction object from HTTP response
2020
:param value_key: Key to use in the amount_prediction dict
2121
:param reconstructed: Bool for reconstructed object (not extracted in the API)
22-
:param page_n: Page number for multi pages pdf
22+
:param page_n: Page number for multi-page PDF
2323
"""
2424
super().__init__(
2525
amount_prediction,

mindee/fields/base.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
from typing import Any, Dict, List, Optional, TypeVar
22

3+
from mindee.geometry import Polygon, get_bbox_as_polygon
4+
5+
TypePrediction = Dict[str, Any]
6+
37

48
class Field:
59
value: Optional[Any] = None
610
"""Raw field value"""
711
confidence: float = 0.0
812
"""Confidence score"""
9-
bbox: List[List[float]] = []
13+
bbox: Polygon = []
1014
"""Bounding box coordinates containing the field"""
15+
polygon: Polygon = []
16+
"""coordinates of the field"""
1117

1218
def __init__(
1319
self,
14-
abstract_prediction: Dict[str, Any],
20+
abstract_prediction: TypePrediction,
1521
value_key: str = "value",
1622
reconstructed: bool = False,
1723
page_n: Optional[int] = None,
@@ -25,26 +31,28 @@ def __init__(
2531
:param page_n: Page number for multi-page PDF
2632
"""
2733
self.page_n = page_n
34+
self.reconstructed = reconstructed
2835

2936
if (
3037
value_key not in abstract_prediction
3138
or abstract_prediction[value_key] == "N/A"
3239
):
33-
self.value = None
34-
self.confidence = 0.0
35-
self.bbox = []
36-
else:
37-
self.value = abstract_prediction[value_key]
38-
try:
39-
self.confidence = float(abstract_prediction["confidence"])
40-
except (KeyError, TypeError):
41-
self.confidence = 0.0
42-
try:
43-
self.bbox = abstract_prediction["polygon"]
44-
except KeyError:
45-
self.bbox = []
40+
return
4641

47-
self.reconstructed = reconstructed
42+
self.value = abstract_prediction[value_key]
43+
try:
44+
self.confidence = float(abstract_prediction["confidence"])
45+
except (KeyError, TypeError):
46+
pass
47+
self._set_bbox(abstract_prediction)
48+
49+
def _set_bbox(self, abstract_prediction: TypePrediction) -> None:
50+
try:
51+
self.polygon = abstract_prediction["polygon"]
52+
except KeyError:
53+
pass
54+
if self.polygon:
55+
self.bbox = get_bbox_as_polygon(self.polygon)
4856

4957
def __eq__(self, other: Any) -> bool:
5058
if not isinstance(other, Field):

mindee/fields/date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(
2828
:param date_prediction: Date prediction object from HTTP response
2929
:param value_key: Key to use in the date_prediction dict
3030
:param reconstructed: Bool for reconstructed object (not extracted in the API)
31-
:param page_n: Page number for multi pages pdf
31+
:param page_n: Page number for multi-page PDF
3232
"""
3333
super().__init__(
3434
date_prediction,

mindee/fields/locale.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(
2424
:param locale_prediction: Locale prediction object from HTTP response
2525
:param value_key: Key to use in the locale_prediction dict
2626
:param reconstructed: Bool for reconstructed object (not extracted in the API)
27-
:param page_n: Page number for multi pages pdf
27+
:param page_n: Page number for multi-page PDF
2828
"""
2929
super().__init__(
3030
locale_prediction,

mindee/fields/orientation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(
1818
:param orientation_prediction: Orientation prediction object from HTTP response
1919
:param value_key: Key to use in the orientation_prediction dict
2020
:param reconstructed: Bool for reconstructed object (not extracted in the API)
21-
:param page_n: Page number for multi pages pdf
21+
:param page_n: Page number for multi-page PDF
2222
"""
2323
super().__init__(
2424
orientation_prediction,

mindee/fields/payment_details.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(
3636
payment_details_prediction dict
3737
:param swift_key: Key to use for getting the SWIFT in the payment_details_prediction dict
3838
:param reconstructed: Bool for reconstructed object (not extracted in the API)
39-
:param page_n: Page number for multi pages pdf
39+
:param page_n: Page number for multi-page PDF
4040
"""
4141
super().__init__(
4242
payment_details_prediction,

mindee/geometry.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Pure Python geometry functions for working with polygons."""
2+
3+
from typing import Sequence, Tuple
4+
5+
Point = Tuple[float, float]
6+
Polygon = Sequence[Point]
7+
BoundingBox = Tuple[float, float, float, float]
8+
Quadrilateral = Tuple[Point, Point, Point, Point]
9+
10+
11+
def get_bbox_as_polygon(polygon: Polygon) -> Quadrilateral:
12+
"""
13+
Given a sequence of points, calculate a polygon that encompasses all points.
14+
15+
:param polygon: Sequence of ``Point``
16+
:return: Quadrilateral
17+
"""
18+
x_min, y_min, x_max, y_max = get_bbox(polygon)
19+
return (x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)
20+
21+
22+
def get_bbox(polygon: Polygon) -> BoundingBox:
23+
"""
24+
Given a list of points, calculate a bounding box that encompasses all points.
25+
26+
:param polygon: Sequence of ``Point``
27+
:return: BoundingBox
28+
"""
29+
y_min = min(v[1] for v in polygon)
30+
y_max = max(v[1] for v in polygon)
31+
x_min = min(v[0] for v in polygon)
32+
x_max = max(v[0] for v in polygon)
33+
return x_min, y_min, x_max, y_max

tests/test_geometry.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pytest
2+
3+
from mindee import geometry
4+
5+
6+
@pytest.fixture
7+
def polygon_a():
8+
"""90° rectangle, overlaps polygon_b"""
9+
return [(0.123, 0.53), (0.175, 0.53), (0.175, 0.546), (0.123, 0.546)]
10+
11+
12+
@pytest.fixture
13+
def polygon_b():
14+
"""90° rectangle, overlaps polygon_a"""
15+
return [(0.124, 0.535), (0.190, 0.535), (0.190, 0.546), (0.124, 0.546)]
16+
17+
18+
@pytest.fixture
19+
def polygon_c():
20+
"""not 90° rectangle, doesn't overlap any polygons"""
21+
return [(0.205, 0.407), (0.379, 0.407), (0.381, 0.43), (0.207, 0.43)]
22+
23+
24+
def test_bbox(polygon_a, polygon_b, polygon_c):
25+
assert geometry.get_bbox(polygon_a) == (0.123, 0.53, 0.175, 0.546)
26+
assert geometry.get_bbox(polygon_b) == (0.124, 0.535, 0.19, 0.546)
27+
assert geometry.get_bbox(polygon_c) == (0.205, 0.407, 0.381, 0.43)
28+
29+
30+
def test_bbox_polygon(polygon_a, polygon_b, polygon_c):
31+
assert geometry.get_bbox_as_polygon(polygon_a) == (
32+
(0.123, 0.53),
33+
(0.175, 0.53),
34+
(0.175, 0.546),
35+
(0.123, 0.546),
36+
)
37+
assert geometry.get_bbox_as_polygon(polygon_b) == (
38+
(0.124, 0.535),
39+
(0.19, 0.535),
40+
(0.19, 0.546),
41+
(0.124, 0.546),
42+
)
43+
assert geometry.get_bbox_as_polygon(polygon_c) == (
44+
(0.205, 0.407),
45+
(0.381, 0.407),
46+
(0.381, 0.43),
47+
(0.205, 0.43),
48+
)

0 commit comments

Comments
 (0)