Skip to content

Commit 7d85b5f

Browse files
:sparkes: add support for full text ocr extra (#258)
1 parent 17c4207 commit 7d85b5f

File tree

35 files changed

+183
-60
lines changed

35 files changed

+183
-60
lines changed

mindee/client.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def parse(
7777
page_options: Optional[PageOptions] = None,
7878
cropper: bool = False,
7979
endpoint: Optional[Endpoint] = None,
80+
full_text: bool = False,
8081
) -> PredictResponse:
8182
"""
8283
Call prediction API on the document and parse the results.
@@ -89,6 +90,7 @@ def parse(
8990
9091
:param include_words: Whether to include the full text for each page.
9192
This performs a full OCR operation on the server and will increase response time.
93+
Only available on financial document APIs.
9294
9395
:param close_file: Whether to ``close()`` the file after parsing it.
9496
Set to ``False`` if you need to access the file after this operation.
@@ -101,6 +103,7 @@ def parse(
101103
This performs a cropping operation on the server and will increase response time.
102104
103105
:param endpoint: For custom endpoints, an endpoint has to be given.
106+
:param full_text: Whether to include the full OCR text response in compatible APIs.
104107
"""
105108
if input_source is None:
106109
raise MindeeClientError("No input document provided.")
@@ -118,7 +121,13 @@ def parse(
118121
page_options.page_indexes,
119122
)
120123
return self._make_request(
121-
product_class, input_source, endpoint, include_words, close_file, cropper
124+
product_class,
125+
input_source,
126+
endpoint,
127+
include_words,
128+
close_file,
129+
cropper,
130+
full_text,
122131
)
123132

124133
def enqueue(
@@ -130,6 +139,7 @@ def enqueue(
130139
page_options: Optional[PageOptions] = None,
131140
cropper: bool = False,
132141
endpoint: Optional[Endpoint] = None,
142+
full_text: bool = False,
133143
) -> AsyncPredictResponse:
134144
"""
135145
Enqueues a document to an asynchronous endpoint.
@@ -154,6 +164,8 @@ def enqueue(
154164
This performs a cropping operation on the server and will increase response time.
155165
156166
:param endpoint: For custom endpoints, an endpoint has to be given.
167+
168+
:param full_text: Whether to include the full OCR text response in compatible APIs.
157169
"""
158170
if input_source is None:
159171
raise MindeeClientError("No input document provided.")
@@ -177,6 +189,7 @@ def enqueue(
177189
include_words,
178190
close_file,
179191
cropper,
192+
full_text,
180193
)
181194

182195
def load_prediction(
@@ -246,6 +259,7 @@ def enqueue_and_parse(
246259
initial_delay_sec: float = 4,
247260
delay_sec: float = 2,
248261
max_retries: int = 30,
262+
full_text: bool = False,
249263
) -> AsyncPredictResponse:
250264
"""
251265
Enqueues to an asynchronous endpoint and automatically polls for a response.
@@ -274,6 +288,8 @@ def enqueue_and_parse(
274288
:param delay_sec: Delay between each polling attempts This should not be shorter than 2 seconds.
275289
276290
:param max_retries: Total amount of polling attempts.
291+
292+
:param full_text: Whether to include the full OCR text response in compatible APIs.
277293
"""
278294
self._validate_async_params(initial_delay_sec, delay_sec, max_retries)
279295
if not endpoint:
@@ -286,6 +302,7 @@ def enqueue_and_parse(
286302
page_options,
287303
cropper,
288304
endpoint,
305+
full_text,
289306
)
290307
logger.debug(
291308
"Successfully enqueued document with job id: %s", queue_result.job.id
@@ -352,9 +369,10 @@ def _make_request(
352369
include_words: bool,
353370
close_file: bool,
354371
cropper: bool,
372+
full_text: bool,
355373
) -> PredictResponse:
356374
response = endpoint.predict_req_post(
357-
input_source, include_words, close_file, cropper
375+
input_source, include_words, close_file, cropper, full_text
358376
)
359377

360378
dict_response = response.json()
@@ -376,14 +394,15 @@ def _predict_async(
376394
include_words: bool = False,
377395
close_file: bool = True,
378396
cropper: bool = False,
397+
full_text: bool = False,
379398
) -> AsyncPredictResponse:
380399
"""Sends a document to the queue, and sends back an asynchronous predict response."""
381400
if input_source is None:
382401
raise MindeeClientError("No input document provided")
383402
if not endpoint:
384403
endpoint = self._initialize_ots_endpoint(product_class)
385404
response = endpoint.predict_async_req_post(
386-
input_source, include_words, close_file, cropper
405+
input_source, include_words, close_file, cropper, full_text
387406
)
388407

389408
dict_response = response.json()

mindee/mindee_http/endpoint.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def predict_req_post(
3434
include_words: bool = False,
3535
close_file: bool = True,
3636
cropper: bool = False,
37+
full_text: bool = False,
3738
) -> requests.Response:
3839
"""
3940
Make a request to POST a document for prediction.
@@ -42,10 +43,11 @@ def predict_req_post(
4243
:param include_words: Include raw OCR words in the response
4344
:param close_file: Whether to `close()` the file after parsing it.
4445
:param cropper: Including Mindee cropping results.
46+
:param full_text: Whether to include the full OCR text response in compatible APIs.
4547
:return: requests response
4648
"""
4749
return self._custom_request(
48-
"predict", input_source, include_words, close_file, cropper
50+
"predict", input_source, include_words, close_file, cropper, full_text
4951
)
5052

5153
def predict_async_req_post(
@@ -54,6 +56,7 @@ def predict_async_req_post(
5456
include_words: bool = False,
5557
close_file: bool = True,
5658
cropper: bool = False,
59+
full_text: bool = False,
5760
) -> requests.Response:
5861
"""
5962
Make an asynchronous request to POST a document for prediction.
@@ -62,10 +65,11 @@ def predict_async_req_post(
6265
:param include_words: Include raw OCR words in the response
6366
:param close_file: Whether to `close()` the file after parsing it.
6467
:param cropper: Including Mindee cropping results.
68+
:param full_text: Whether to include the full OCR text response in compatible APIs.
6569
:return: requests response
6670
"""
6771
return self._custom_request(
68-
"predict_async", input_source, include_words, close_file, cropper
72+
"predict_async", input_source, include_words, close_file, cropper, full_text
6973
)
7074

7175
def _custom_request(
@@ -75,11 +79,15 @@ def _custom_request(
7579
include_words: bool = False,
7680
close_file: bool = True,
7781
cropper: bool = False,
82+
full_text: bool = False,
7883
):
7984
data = {}
8085
if include_words:
8186
data["include_mvision"] = "true"
8287

88+
if full_text:
89+
data["full_text_ocr"] = "true"
90+
8391
params = {}
8492
if cropper:
8593
params["cropper"] = "true"
@@ -111,11 +119,6 @@ def document_queue_req_get(self, queue_id: str) -> requests.Response:
111119
Sends a request matching a given queue_id. Returns either a Job or a Document.
112120
113121
:param queue_id: queue_id received from the API
114-
:param include_words: Whether to include the full text for each page.
115-
This performs a full OCR operation on the server and will increase response time.
116-
:param cropper: Whether to include cropper results for each page.
117-
This performs a cropping operation on the server and will increase response time.
118-
119122
"""
120123
return requests.get(
121124
f"{self.settings.url_root}/documents/queue/{queue_id}",

mindee/parsing/common/document.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Document(Generic[TypePrediction, TypePage]):
2929
"""Result of the base inference"""
3030
id: str
3131
"""Id of the document as sent back by the server"""
32-
extras: Optional[Extras]
32+
extras: Extras
3333
"""Potential Extras fields sent back along the prediction"""
3434
ocr: Optional[Ocr]
3535
"""Potential raw text results read by the OCR (limited feature)"""
@@ -47,6 +47,7 @@ def __init__(
4747
self.ocr = Ocr(raw_response["ocr"])
4848
if "extras" in raw_response and raw_response["extras"]:
4949
self.extras = Extras(raw_response["extras"])
50+
self._inject_full_text_ocr(raw_response)
5051
self.inference = inference_type(raw_response["inference"])
5152
self.n_pages = raw_response["n_pages"]
5253

@@ -57,3 +58,26 @@ def __str__(self) -> str:
5758
f":Filename: {self.filename}\n\n"
5859
f"{self.inference}"
5960
)
61+
62+
def _inject_full_text_ocr(self, raw_prediction: StringDict) -> None:
63+
pages = raw_prediction.get("inference", {}).get("pages", [])
64+
65+
if (
66+
not pages
67+
or "extras" not in pages[0]
68+
or "full_text_ocr" not in pages[0]["extras"]
69+
):
70+
return
71+
72+
full_text_content = "\n".join(
73+
page["extras"]["full_text_ocr"]["content"]
74+
for page in pages
75+
if "extras" in page and "full_text_ocr" in page["extras"]
76+
)
77+
78+
artificial_text_obj = {"content": full_text_content}
79+
80+
if not hasattr(self, "extras"):
81+
self.extras = Extras({"full_text_ocr": artificial_text_obj})
82+
else:
83+
self.extras.add_artificial_extra({"full_text_ocr": artificial_text_obj})

mindee/parsing/common/extras/extras.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional
22

33
from mindee.parsing.common.extras.cropper_extra import CropperExtra
4+
from mindee.parsing.common.extras.full_text_ocr_extra import FullTextOcrExtra
45
from mindee.parsing.common.string_dict import StringDict
56

67

@@ -12,12 +13,15 @@ class Extras:
1213
"""
1314

1415
cropper: Optional[CropperExtra]
16+
full_text_ocr: Optional[FullTextOcrExtra]
1517

1618
def __init__(self, raw_prediction: StringDict) -> None:
1719
if "cropper" in raw_prediction and raw_prediction["cropper"]:
1820
self.cropper = CropperExtra(raw_prediction["cropper"])
21+
if "full_text_ocr" in raw_prediction and raw_prediction["full_text_ocr"]:
22+
self.full_text_ocr = FullTextOcrExtra(raw_prediction["full_text_ocr"])
1923
for key, extra in raw_prediction.items():
20-
if key != "cropper":
24+
if key not in ["cropper", "full_text_ocr"]:
2125
setattr(self, key, extra)
2226

2327
def __str__(self) -> str:
@@ -26,3 +30,12 @@ def __str__(self) -> str:
2630
if not attr.startswith("__"):
2731
out_str += f":{attr}: {getattr(self, attr)}\n"
2832
return out_str
33+
34+
def add_artificial_extra(self, raw_prediction: StringDict):
35+
"""
36+
Adds artificial extra data for reconstructed extras. Currently only used for full_text_ocr.
37+
38+
:param raw_prediction: Raw prediction used by the document.
39+
"""
40+
if "full_text_ocr" in raw_prediction and raw_prediction["full_text_ocr"]:
41+
self.full_text_ocr = FullTextOcrExtra(raw_prediction["full_text_ocr"])
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Optional
2+
3+
from mindee.parsing.common.string_dict import StringDict
4+
5+
6+
class FullTextOcrExtra:
7+
"""Full Text OCR result."""
8+
9+
content: Optional[str]
10+
language: Optional[str]
11+
12+
def __init__(self, raw_prediction: StringDict) -> None:
13+
if raw_prediction and "content" in raw_prediction:
14+
self.content = raw_prediction["content"]
15+
16+
if raw_prediction and "language" in raw_prediction:
17+
self.language = raw_prediction["language"]
18+
19+
def __str__(self) -> str:
20+
return self.content if self.content else ""

mindee/product/barcode_reader/barcode_reader_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(BarcodeReaderV1Document, page))

mindee/product/cropper/cropper_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def __init__(self, raw_prediction: StringDict):
3333
self.pages = []
3434
for page in raw_prediction["pages"]:
3535
try:
36-
page_production = page["prediction"]
36+
page_prediction = page["prediction"]
3737
except KeyError:
3838
continue
39-
if page_production:
39+
if page_prediction:
4040
self.pages.append(Page(CropperV1Page, page))

mindee/product/eu/driver_license/driver_license_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def __init__(self, raw_prediction: StringDict):
3333
self.pages = []
3434
for page in raw_prediction["pages"]:
3535
try:
36-
page_production = page["prediction"]
36+
page_prediction = page["prediction"]
3737
except KeyError:
3838
continue
39-
if page_production:
39+
if page_prediction:
4040
self.pages.append(Page(DriverLicenseV1Page, page))

mindee/product/eu/license_plate/license_plate_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(LicensePlateV1Document, page))

mindee/product/financial_document/financial_document_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(FinancialDocumentV1Document, page))

mindee/product/fr/bank_account_details/bank_account_details_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(BankAccountDetailsV1Document, page))

mindee/product/fr/bank_account_details/bank_account_details_v2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(BankAccountDetailsV2Document, page))

mindee/product/fr/carte_grise/carte_grise_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(CarteGriseV1Document, page))

mindee/product/fr/carte_vitale/carte_vitale_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __init__(self, raw_prediction: StringDict):
3030
self.pages = []
3131
for page in raw_prediction["pages"]:
3232
try:
33-
page_production = page["prediction"]
33+
page_prediction = page["prediction"]
3434
except KeyError:
3535
continue
36-
if page_production:
36+
if page_prediction:
3737
self.pages.append(Page(CarteVitaleV1Document, page))

0 commit comments

Comments
 (0)