ocr-stream 2.4.1__tar.gz → 2.4.2__tar.gz
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.
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/PKG-INFO +2 -2
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream/__init__.py +75 -92
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream/modules/__init__.py +13 -5
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream/ocr/__init__.py +101 -12
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream/recognize.py +30 -64
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream.egg-info/PKG-INFO +2 -2
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream.egg-info/requires.txt +1 -1
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/pyproject.toml +2 -2
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/README.md +0 -0
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream/bin_tess/__init__.py +0 -0
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream.egg-info/SOURCES.txt +0 -0
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream.egg-info/dependency_links.txt +0 -0
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/ocr_stream.egg-info/top_level.txt +0 -0
- {ocr_stream-2.4.1 → ocr_stream-2.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocr_stream
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: Extrair textos de documentos digitalizados e imagens.
|
|
5
5
|
Author: Bruno Chaves
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -12,7 +12,7 @@ Requires-Dist: pandas
|
|
|
12
12
|
Requires-Dist: requests>=2.32.4
|
|
13
13
|
Requires-Dist: opencv-python
|
|
14
14
|
Requires-Dist: pytesseract
|
|
15
|
-
Requires-Dist: convert_stream>=2.4.
|
|
15
|
+
Requires-Dist: convert_stream>=2.4.3
|
|
16
16
|
Requires-Dist: soup_files>=1.4
|
|
17
17
|
|
|
18
18
|
# ocrlib
|
|
@@ -4,27 +4,31 @@ from io import BytesIO
|
|
|
4
4
|
from ocr_stream.modules import *
|
|
5
5
|
from ocr_stream.bin_tess import BinTesseract, get_path_tesseract_sys
|
|
6
6
|
from ocr_stream.ocr import TesseractOcr, TextRecognized
|
|
7
|
-
from ocr_stream.recognize import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import
|
|
7
|
+
from ocr_stream.recognize import RecognizeImage, RecognizePdf
|
|
8
|
+
from convert_stream import DocumentPdf
|
|
9
|
+
from convert_stream.image import ImageObject
|
|
10
|
+
from sheet_stream.type_utils import HeadValues, HeadCell
|
|
11
|
+
from sheet_stream import TableRow, TableDocuments, TableTextKeyWord, VoidAdapter, ColumnsTable
|
|
12
|
+
from convert_stream.mod_types import DictTextTable
|
|
13
|
+
from convert_stream.text import FindText, FindStrings
|
|
11
14
|
import pandas as pd
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
from soup_files import (
|
|
16
|
+
JsonConvert, ProgressBarAdapter, ABCProgressBar, CreatePbar,
|
|
17
|
+
Directory, File, InputFiles, LibraryDocs
|
|
18
|
+
)
|
|
15
19
|
|
|
16
20
|
|
|
17
|
-
def read_image(img:
|
|
21
|
+
def read_image(img: ImageObject, rec: RecognizeImage = RecognizeImage()) -> TextRecognized:
|
|
18
22
|
_txt = rec.image_recognize(img)
|
|
19
23
|
_txt.metadata = img.metadata
|
|
20
24
|
return _txt
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
def read_images(
|
|
24
|
-
images: list[
|
|
28
|
+
images: list[ImageObject], *,
|
|
25
29
|
rec: RecognizeImage = RecognizeImage(),
|
|
26
|
-
pbar:
|
|
27
|
-
) ->
|
|
30
|
+
pbar: ProgressBarAdapter = CreatePbar().get()
|
|
31
|
+
) -> DocumentPdf:
|
|
28
32
|
pbar.start()
|
|
29
33
|
print()
|
|
30
34
|
max_num = len(images)
|
|
@@ -33,76 +37,55 @@ def read_images(
|
|
|
33
37
|
for idx, image in enumerate(images):
|
|
34
38
|
pbar.update(
|
|
35
39
|
((idx + 1) / max_num) * 100,
|
|
36
|
-
f'[OCR
|
|
40
|
+
f'[OCR Imagem] {idx + 1} / {max_num}',
|
|
37
41
|
)
|
|
38
42
|
out = read_image(image, rec=rec).to_page_pdf()
|
|
39
43
|
pages.append(out)
|
|
40
|
-
tmp_doc =
|
|
41
|
-
print()
|
|
44
|
+
tmp_doc = DocumentPdf.create_from_pages(pages)
|
|
42
45
|
pbar.stop()
|
|
43
46
|
return tmp_doc
|
|
44
47
|
|
|
45
48
|
|
|
46
49
|
def read_document(
|
|
47
|
-
document:
|
|
48
|
-
dpi: int =
|
|
49
|
-
|
|
50
|
-
) ->
|
|
51
|
-
pbar.start()
|
|
52
|
-
|
|
53
|
-
_metadata: cs.MetaDataFile = document.metadata
|
|
54
|
-
recognized_items: list[TextRecognized] = []
|
|
55
|
-
pages_pdf: list[cs.PageDocumentPdf] = document.to_pages()
|
|
56
|
-
total = len(pages_pdf)
|
|
57
|
-
void_pbar = ProgressVoid()
|
|
58
|
-
for num, page in enumerate(pages_pdf):
|
|
59
|
-
pbar.update(
|
|
60
|
-
((num + 1) / total) * 100,
|
|
61
|
-
f'[OCR PDF] {num + 1}/{total} {page.metadata.name}',
|
|
62
|
-
)
|
|
63
|
-
convert: cs.ConvertPdfToImages = cs.ConvertPdfToImages(cs.DocumentPdf.create_from_pages([page]))
|
|
64
|
-
convert.set_pbar(void_pbar)
|
|
65
|
-
images: list[cs.ImageObject] = convert.to_images(dpi=dpi)
|
|
66
|
-
for im in images:
|
|
67
|
-
recognized_items.append(read_image(im))
|
|
68
|
-
print()
|
|
69
|
-
final_pages: list[cs.PageDocumentPdf] = [x.to_page_pdf() for x in recognized_items]
|
|
70
|
-
final_doc: cs.DocumentPdf = cs.DocumentPdf.create_from_pages(final_pages)
|
|
71
|
-
final_doc.metadata = _metadata
|
|
72
|
-
return final_doc
|
|
50
|
+
document: DocumentPdf, *,
|
|
51
|
+
dpi: int = 200,
|
|
52
|
+
rec: RecognizePdf = RecognizePdf(),
|
|
53
|
+
) -> DocumentPdf:
|
|
54
|
+
rec.pbar.start()
|
|
55
|
+
return rec.recognize_document(document, dpi=dpi)
|
|
73
56
|
|
|
74
57
|
|
|
75
58
|
class SearchableTextImage(object):
|
|
76
59
|
"""
|
|
77
60
|
|
|
78
61
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
62
|
+
ColumnsTable.KEY.value: ColumnBody(ColumnsTable.KEY.value, ListString([])),
|
|
63
|
+
ColumnsTable.NUM_PAGE.value: ColumnBody(ColumnsTable.NUM_PAGE.value, ListString([])),
|
|
64
|
+
ColumnsTable.NUM_LINE.value: ColumnBody(ColumnsTable.NUM_LINE.value, ListString([])),
|
|
65
|
+
ColumnsTable.TEXT.value: ColumnBody(ColumnsTable.TEXT.value, ListString([])),
|
|
66
|
+
ColumnsTable.FILE_NAME.value: ColumnBody(ColumnsTable.FILE_NAME.value, ListString([])),
|
|
67
|
+
ColumnsTable.FILETYPE.value: ColumnBody(ColumnsTable.FILETYPE.value, ListString([])),
|
|
68
|
+
ColumnsTable.FILE_PATH.value: ColumnBody(ColumnsTable.FILE_PATH.value, ListString([])),
|
|
69
|
+
ColumnsTable.DIR.value: ColumnBody(ColumnsTable.DIR.value, ListString([])),
|
|
87
70
|
}
|
|
88
71
|
"""
|
|
89
72
|
def __init__(self, silent: bool = False):
|
|
90
73
|
self.silent = silent
|
|
91
|
-
self.elements:
|
|
74
|
+
self.elements: DictTextTable = DictTextTable.create_void_dict()
|
|
92
75
|
|
|
93
76
|
def __repr__(self):
|
|
94
77
|
return f'SearchableTextPdf: {self.elements}'
|
|
95
78
|
|
|
96
79
|
@property
|
|
97
80
|
def length(self) -> int:
|
|
98
|
-
return len(self.elements[
|
|
81
|
+
return len(self.elements[ColumnsTable.TEXT.value])
|
|
99
82
|
|
|
100
83
|
def is_empty(self) -> bool:
|
|
101
|
-
return len(self.elements[
|
|
84
|
+
return len(self.elements[ColumnsTable.TEXT.value]) == 0
|
|
102
85
|
|
|
103
86
|
@property
|
|
104
|
-
def columns(self) ->
|
|
105
|
-
return
|
|
87
|
+
def columns(self) -> HeadValues:
|
|
88
|
+
return HeadValues([HeadCell(x) for x in list(self.elements.keys())])
|
|
106
89
|
|
|
107
90
|
@property
|
|
108
91
|
def first(self) -> dict[str, str]:
|
|
@@ -140,8 +123,8 @@ class SearchableTextImage(object):
|
|
|
140
123
|
return _current_item
|
|
141
124
|
|
|
142
125
|
def add_line(self, line: dict[str, str]) -> None:
|
|
143
|
-
cols:
|
|
144
|
-
cols_line:
|
|
126
|
+
cols: HeadValues = self.columns
|
|
127
|
+
cols_line: HeadValues = HeadValues([HeadCell(y) for y in list(line.keys())])
|
|
145
128
|
for col in cols:
|
|
146
129
|
if cols_line.contains(col, case=True, iqual=True):
|
|
147
130
|
self.elements[col].append(line[col])
|
|
@@ -156,7 +139,7 @@ class SearchableTextImage(object):
|
|
|
156
139
|
ou 'nas' em caso de erro nas = Not a String
|
|
157
140
|
"""
|
|
158
141
|
try:
|
|
159
|
-
return ' '.join(self.elements[
|
|
142
|
+
return ' '.join(self.elements[ColumnsTable.TEXT.value])
|
|
160
143
|
except Exception as e:
|
|
161
144
|
print(e)
|
|
162
145
|
return 'nas'
|
|
@@ -164,12 +147,12 @@ class SearchableTextImage(object):
|
|
|
164
147
|
def to_data_frame(self) -> pd.DataFrame:
|
|
165
148
|
return pd.DataFrame.from_dict(self.elements)
|
|
166
149
|
|
|
167
|
-
def to_file_json(self, file:
|
|
150
|
+
def to_file_json(self, file: File):
|
|
168
151
|
"""Exporta os dados da busca para um arquivo JSON"""
|
|
169
|
-
dt =
|
|
152
|
+
dt = JsonConvert.from_dict(self.elements).to_json_data()
|
|
170
153
|
dt.to_file(file)
|
|
171
154
|
|
|
172
|
-
def to_file_excel(self, file:
|
|
155
|
+
def to_file_excel(self, file: File):
|
|
173
156
|
"""Exporta os dados da busca para um arquivo .XLSX"""
|
|
174
157
|
self.to_data_frame().to_excel(file.absolute(), index=False)
|
|
175
158
|
|
|
@@ -180,9 +163,9 @@ class ImageFinder(object):
|
|
|
180
163
|
"""
|
|
181
164
|
|
|
182
165
|
def __init__(self):
|
|
183
|
-
self.docs_collection: dict[str,
|
|
166
|
+
self.docs_collection: dict[str, ImageObject] = {}
|
|
184
167
|
self.recognize: RecognizeImage = RecognizeImage()
|
|
185
|
-
self.pbar:
|
|
168
|
+
self.pbar: ProgressBarAdapter = ProgressBarAdapter()
|
|
186
169
|
|
|
187
170
|
def is_empty(self) -> bool:
|
|
188
171
|
return len(self.docs_collection) == 0
|
|
@@ -190,29 +173,29 @@ class ImageFinder(object):
|
|
|
190
173
|
def clear(self) -> None:
|
|
191
174
|
self.docs_collection.clear()
|
|
192
175
|
|
|
193
|
-
def add_image(self, img:
|
|
194
|
-
if isinstance(img,
|
|
195
|
-
img =
|
|
196
|
-
elif isinstance(img,
|
|
176
|
+
def add_image(self, img: File | bytes | BytesIO | str | ImageObject) -> None:
|
|
177
|
+
if isinstance(img, File):
|
|
178
|
+
img = ImageObject(img)
|
|
179
|
+
elif isinstance(img, ImageObject):
|
|
197
180
|
pass
|
|
198
181
|
elif isinstance(img, bytes):
|
|
199
|
-
img =
|
|
182
|
+
img = ImageObject(img)
|
|
200
183
|
elif isinstance(img, str):
|
|
201
|
-
img =
|
|
184
|
+
img = ImageObject(img)
|
|
202
185
|
elif isinstance(img, BytesIO):
|
|
203
|
-
img =
|
|
186
|
+
img = ImageObject(img)
|
|
204
187
|
|
|
205
188
|
if not img.metadata.file_path.is_empty:
|
|
206
189
|
self.docs_collection[img.metadata.file_path] = img
|
|
207
190
|
else:
|
|
208
191
|
self.docs_collection[img.metadata.name] = img
|
|
209
192
|
|
|
210
|
-
def add_images(self, images: list[
|
|
193
|
+
def add_images(self, images: list[File] | list[ImageObject] | list[bytes] | list[BytesIO]) -> None:
|
|
211
194
|
for f in images:
|
|
212
195
|
self.add_image(f)
|
|
213
196
|
|
|
214
|
-
def add_directory_images(self, dir_pdf:
|
|
215
|
-
files =
|
|
197
|
+
def add_directory_images(self, dir_pdf: Directory) -> None:
|
|
198
|
+
files = InputFiles(dir_pdf).get_files(file_type=LibraryDocs.IMAGE)
|
|
216
199
|
if len(files) > 0:
|
|
217
200
|
self.add_images(files)
|
|
218
201
|
|
|
@@ -237,13 +220,13 @@ class ImageFinder(object):
|
|
|
237
220
|
f'[PESQUISANDO]: {n+1}/{total_num}',
|
|
238
221
|
)
|
|
239
222
|
|
|
240
|
-
current_img:
|
|
223
|
+
current_img: ImageObject = self.docs_collection[file_key]
|
|
241
224
|
txt_in_img = self.recognize.image_to_string(current_img)
|
|
242
225
|
if (txt_in_img == '') or (txt_in_img is None):
|
|
243
226
|
self.pbar.update_text(f'ERRO: {n+1} {current_img.metadata.name}')
|
|
244
227
|
continue
|
|
245
228
|
try:
|
|
246
|
-
fd =
|
|
229
|
+
fd = FindText(txt_in_img, separator=separator)
|
|
247
230
|
idx = fd.find_index(text, iqual=iqual, case=case)
|
|
248
231
|
if idx is None:
|
|
249
232
|
continue
|
|
@@ -252,14 +235,14 @@ class ImageFinder(object):
|
|
|
252
235
|
print(f'{__class__.__name__} {err}')
|
|
253
236
|
else:
|
|
254
237
|
new_line: dict[str, str] = {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
238
|
+
ColumnsTable.KEY: f'{idx}',
|
|
239
|
+
ColumnsTable.NUM_PAGE: 'nan',
|
|
240
|
+
ColumnsTable.NUM_LINE: f'{idx + 1}',
|
|
241
|
+
ColumnsTable.TEXT: math_text,
|
|
242
|
+
ColumnsTable.FILE_NAME: current_img.metadata.file_path,
|
|
243
|
+
ColumnsTable.FILETYPE: current_img.metadata.extension,
|
|
244
|
+
ColumnsTable.FILE_PATH: current_img.metadata.dir_path,
|
|
245
|
+
ColumnsTable.DIR: current_img.metadata.dir_path,
|
|
263
246
|
}
|
|
264
247
|
_searchable.add_line(new_line)
|
|
265
248
|
return _searchable
|
|
@@ -288,13 +271,13 @@ class ImageFinder(object):
|
|
|
288
271
|
f'[PESQUISANDO]: {n + 1}/{total_num}',
|
|
289
272
|
)
|
|
290
273
|
|
|
291
|
-
current_img:
|
|
274
|
+
current_img: ImageObject = self.docs_collection[file_key]
|
|
292
275
|
txt_in_img = self.recognize.image_to_string(current_img)
|
|
293
276
|
if (txt_in_img == '') or (txt_in_img is None):
|
|
294
277
|
self.pbar.update_text(f'ERRO: {n + 1} {current_img.metadata.name}')
|
|
295
278
|
continue
|
|
296
279
|
try:
|
|
297
|
-
fd =
|
|
280
|
+
fd = FindText(txt_in_img, separator=separator)
|
|
298
281
|
idx = fd.find_index(text, iqual=iqual, case=case)
|
|
299
282
|
if idx is None:
|
|
300
283
|
continue
|
|
@@ -303,14 +286,14 @@ class ImageFinder(object):
|
|
|
303
286
|
print(f'{__class__.__name__} {err}')
|
|
304
287
|
else:
|
|
305
288
|
new_line: dict[str, str] = {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
289
|
+
ColumnsTable.KEY: f'{idx}',
|
|
290
|
+
ColumnsTable.NUM_PAGE: 'nan',
|
|
291
|
+
ColumnsTable.NUM_LINE: f'{idx + 1}',
|
|
292
|
+
ColumnsTable.TEXT: math_text,
|
|
293
|
+
ColumnsTable.FILE_NAME: current_img.metadata.file_path,
|
|
294
|
+
ColumnsTable.FILETYPE: current_img.metadata.extension,
|
|
295
|
+
ColumnsTable.FILE_PATH: current_img.metadata.dir_path,
|
|
296
|
+
ColumnsTable.DIR: current_img.metadata.dir_path,
|
|
314
297
|
}
|
|
315
298
|
_searchable.add_line(new_line)
|
|
316
299
|
return _searchable
|
|
@@ -318,5 +301,5 @@ class ImageFinder(object):
|
|
|
318
301
|
|
|
319
302
|
__all__ = [
|
|
320
303
|
'ImageFinder', 'SearchableTextImage', 'read_images',
|
|
321
|
-
'read_document', 'RecognizeImage', 'RecognizePdf', '
|
|
304
|
+
'read_document', 'RecognizeImage', 'RecognizePdf', 'LibOcr',
|
|
322
305
|
]
|
|
@@ -8,11 +8,13 @@ class LibOcr(Enum):
|
|
|
8
8
|
|
|
9
9
|
PYTESSERACT = "pytesseract"
|
|
10
10
|
PYOCR = "pyocr"
|
|
11
|
+
EASYOCR = "easyocr"
|
|
11
12
|
NOT_IMPLEMENTED = "not_implemented"
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
MOD_PYTESSERACT: bool = False
|
|
15
16
|
MOD_PYOCR: bool = False
|
|
17
|
+
MOD_EASYOCR: bool = False
|
|
16
18
|
|
|
17
19
|
try:
|
|
18
20
|
from pytesseract import pytesseract, Output
|
|
@@ -27,16 +29,24 @@ try:
|
|
|
27
29
|
import pyocr.tesseract
|
|
28
30
|
MOD_PYOCR = True
|
|
29
31
|
except Exception as err:
|
|
30
|
-
print(err)
|
|
32
|
+
#print(err)
|
|
31
33
|
pyocr = object
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
try:
|
|
37
|
+
import easyocr
|
|
38
|
+
MOD_EASYOCR = True
|
|
39
|
+
except Exception as err:
|
|
40
|
+
easyocr = object
|
|
41
|
+
|
|
42
|
+
if MOD_PYTESSERACT and MOD_PYOCR and MOD_EASYOCR:
|
|
43
|
+
ModuleOcr: TypeAlias = Union[esasyocr, pytesseract, pyocr]
|
|
36
44
|
DEFAULT_LIB_OCR = LibOcr.PYTESSERACT
|
|
37
45
|
elif MOD_PYTESSERACT:
|
|
38
46
|
ModuleOcr: TypeAlias = Union[pytesseract]
|
|
39
47
|
DEFAULT_LIB_OCR = LibOcr.PYTESSERACT
|
|
48
|
+
elif MOD_EASYOCR:
|
|
49
|
+
ModuleOcr: TypeAlias = Union[easyocr]
|
|
40
50
|
elif MOD_PYOCR:
|
|
41
51
|
ModuleOcr: TypeAlias = Union[pyocr]
|
|
42
52
|
DEFAULT_LIB_OCR = LibOcr.PYOCR
|
|
@@ -44,5 +54,3 @@ else:
|
|
|
44
54
|
ModuleOcr: TypeAlias = None
|
|
45
55
|
DEFAULT_LIB_OCR = LibOcr.NOT_IMPLEMENTED
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
__all__ = ['LibOcr', 'DEFAULT_LIB_OCR']
|
|
@@ -4,24 +4,23 @@ from __future__ import annotations
|
|
|
4
4
|
import os.path
|
|
5
5
|
from abc import abstractmethod, ABC
|
|
6
6
|
from typing import Union
|
|
7
|
-
from io import BytesIO
|
|
8
7
|
from pandas import DataFrame
|
|
9
8
|
from soup_files import File
|
|
10
|
-
from convert_stream import DocumentPdf, PageDocumentPdf
|
|
9
|
+
from convert_stream import DocumentPdf, PageDocumentPdf, DictTextTable
|
|
11
10
|
from convert_stream.mod_types.modules import DEFAULT_LIB_IMAGE, LibImage
|
|
12
11
|
from convert_stream.image import ImageObject
|
|
13
|
-
from
|
|
14
|
-
from convert_stream.mod_types.table_types import ColumnBody
|
|
12
|
+
from sheet_stream.type_utils import MetaDataFile
|
|
15
13
|
from ocr_stream.modules import (
|
|
16
|
-
LibOcr, DEFAULT_LIB_OCR, pytesseract,
|
|
14
|
+
LibOcr, DEFAULT_LIB_OCR, pytesseract, pyocr, easyocr
|
|
17
15
|
)
|
|
18
16
|
from ocr_stream.bin_tess import BinTesseract
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
import cv2
|
|
19
|
+
from PIL import Image
|
|
20
|
+
from io import BytesIO
|
|
21
|
+
from reportlab.pdfgen import canvas
|
|
22
|
+
#from reportlab.lib.pagesizes import A4
|
|
23
|
+
from reportlab.lib.utils import ImageReader
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
class TextRecognized(object):
|
|
@@ -33,6 +32,7 @@ class TextRecognized(object):
|
|
|
33
32
|
def __init__(self, bytes_recognized: bytes):
|
|
34
33
|
self.metadata: MetaDataFile = MetaDataFile()
|
|
35
34
|
self.bytes_recognized: bytes = bytes_recognized
|
|
35
|
+
self.__text_document: str | None = None
|
|
36
36
|
|
|
37
37
|
self.list_bad_char: list[str] = [
|
|
38
38
|
':', ',', ';', '$', '=',
|
|
@@ -44,8 +44,28 @@ class TextRecognized(object):
|
|
|
44
44
|
'%', '~', '¥', '♀',
|
|
45
45
|
]
|
|
46
46
|
|
|
47
|
+
@property
|
|
48
|
+
def is_empty(self) -> bool:
|
|
49
|
+
txt = self.to_string()
|
|
50
|
+
if (txt is None) or (txt == '') or (txt == 'nas'):
|
|
51
|
+
return True
|
|
52
|
+
return False
|
|
53
|
+
|
|
47
54
|
def to_string(self) -> str | None:
|
|
48
|
-
|
|
55
|
+
if self.__text_document is None:
|
|
56
|
+
_pages: list[PageDocumentPdf] = self.to_document().to_pages()
|
|
57
|
+
content: str = ''
|
|
58
|
+
txt_page: str
|
|
59
|
+
for page in _pages:
|
|
60
|
+
txt_page = page.to_string()
|
|
61
|
+
if (txt_page is None) or (txt_page == '') or (txt_page == 'nas'):
|
|
62
|
+
continue
|
|
63
|
+
if content == '':
|
|
64
|
+
content = txt_page
|
|
65
|
+
continue
|
|
66
|
+
content = f'{content}\n{txt_page}'
|
|
67
|
+
self.__text_document = content
|
|
68
|
+
return self.__text_document
|
|
49
69
|
|
|
50
70
|
def to_page_pdf(self) -> PageDocumentPdf:
|
|
51
71
|
doc = self.to_document()
|
|
@@ -57,7 +77,7 @@ class TextRecognized(object):
|
|
|
57
77
|
tmp_doc.metadata.dir_path = self.metadata.dir_path
|
|
58
78
|
return tmp_doc
|
|
59
79
|
|
|
60
|
-
def to_dict(self, separator: str = '\n') ->
|
|
80
|
+
def to_dict(self, separator: str = '\n') -> DictTextTable:
|
|
61
81
|
return self.to_page_pdf().to_dict(separator=separator)
|
|
62
82
|
|
|
63
83
|
def to_dataframe(self, separator='\n') -> DataFrame:
|
|
@@ -142,6 +162,71 @@ class IPytesseract(ABCOcrTesseract):
|
|
|
142
162
|
return TextRecognized(bt)
|
|
143
163
|
|
|
144
164
|
|
|
165
|
+
# ======================================================================#
|
|
166
|
+
# Modulo easyocr
|
|
167
|
+
# ======================================================================#
|
|
168
|
+
class IEasyOcr(ABCOcrTesseract):
|
|
169
|
+
|
|
170
|
+
def __init__(self, bin_tess: BinTesseract, *, lang: str = 'pt', tess_data_dir: str = None):
|
|
171
|
+
super().__init__(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
|
|
172
|
+
self.reader: easyocr.Reader = easyocr.Reader([self.lang], gpu=True)
|
|
173
|
+
|
|
174
|
+
def to_string(self, img: Union[File, ImageObject]) -> str:
|
|
175
|
+
if isinstance(img, File):
|
|
176
|
+
img = ImageObject(img)
|
|
177
|
+
result = self.reader.readtext(img.to_opencv())
|
|
178
|
+
text = '\n'.join([res[1] for res in result])
|
|
179
|
+
return text
|
|
180
|
+
|
|
181
|
+
def to_reconize(self, img: Union[File, ImageObject]) -> TextRecognized:
|
|
182
|
+
|
|
183
|
+
# 1️⃣ Converter o objeto de entrada para imagem OpenCV
|
|
184
|
+
if isinstance(img, File):
|
|
185
|
+
img = ImageObject(img)
|
|
186
|
+
img_cv = img.to_opencv()
|
|
187
|
+
img_h, img_w = img_cv.shape[:2]
|
|
188
|
+
|
|
189
|
+
# 2️⃣ Reconhecer o texto com EasyOCR
|
|
190
|
+
results: list = self.reader.readtext(img_cv)
|
|
191
|
+
|
|
192
|
+
# 3️⃣ Converter a imagem para PDF (imagem de fundo)
|
|
193
|
+
buffer = BytesIO()
|
|
194
|
+
pdf = canvas.Canvas(buffer, pagesize=(img_w, img_h))
|
|
195
|
+
|
|
196
|
+
# Desenha a imagem original como fundo
|
|
197
|
+
img_pil = Image.fromarray(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB))
|
|
198
|
+
pdf.drawImage(ImageReader(img_pil), 0, 0, width=img_w, height=img_h)
|
|
199
|
+
|
|
200
|
+
# 4️⃣ Adiciona o texto OCR como camada "invisível" sobre a imagem
|
|
201
|
+
pdf.setFont("Helvetica", 8)
|
|
202
|
+
pdf.setFillColorRGB(1, 1, 1, alpha=0) # texto invisível
|
|
203
|
+
|
|
204
|
+
for (bbox, text, conf) in results:
|
|
205
|
+
if not text.strip():
|
|
206
|
+
continue
|
|
207
|
+
# Coordenadas do bounding box (EasyOCR retorna 4 pontos)
|
|
208
|
+
(x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
|
|
209
|
+
|
|
210
|
+
# Média da posição vertical
|
|
211
|
+
avg_y = (y1 + y2 + y3 + y4) / 4
|
|
212
|
+
|
|
213
|
+
# Posição invertida (PDF tem origem no canto inferior esquerdo)
|
|
214
|
+
y_pdf = img_h - avg_y
|
|
215
|
+
|
|
216
|
+
# Largura estimada
|
|
217
|
+
text_width = abs(x2 - x1)
|
|
218
|
+
pdf.drawString(x1, y_pdf, text)
|
|
219
|
+
|
|
220
|
+
pdf.showPage()
|
|
221
|
+
pdf.save()
|
|
222
|
+
buffer.seek(0)
|
|
223
|
+
|
|
224
|
+
# 5️⃣ Retorna como TextRecognized (bytes do PDF com texto embutido)
|
|
225
|
+
text_rec = TextRecognized(buffer.read())
|
|
226
|
+
text_rec.metadata = img.metadata if hasattr(img, "metadata") else MetaDataFile()
|
|
227
|
+
return text_rec
|
|
228
|
+
|
|
229
|
+
|
|
145
230
|
# ======================================================================#
|
|
146
231
|
# Modulo OCR pyocr
|
|
147
232
|
# ======================================================================#
|
|
@@ -206,6 +291,10 @@ class TesseractOcr(object):
|
|
|
206
291
|
md = IPytesseract(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
|
|
207
292
|
elif lib_ocr == LibOcr.PYOCR:
|
|
208
293
|
md = IPyOcr(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
|
|
294
|
+
elif lib_ocr == LibOcr.EASYOCR:
|
|
295
|
+
if lang is None:
|
|
296
|
+
lang = 'pt'
|
|
297
|
+
md = IEasyOcr(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
|
|
209
298
|
else:
|
|
210
299
|
raise ValueError(f'{lib_ocr} is not supported')
|
|
211
300
|
return cls(md)
|
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from convert_stream.pdf import ConvertPdfToImages
|
|
5
|
-
from convert_stream.image import ImageObject
|
|
3
|
+
import convert_stream as cs
|
|
6
4
|
from ocr_stream.bin_tess import BinTesseract
|
|
7
5
|
from ocr_stream.modules import LibOcr, DEFAULT_LIB_OCR
|
|
8
6
|
from ocr_stream.ocr import TesseractOcr, TextRecognized
|
|
9
7
|
from pandas import DataFrame
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class VoidPbar(sp.ABCProgressBar):
|
|
14
|
-
|
|
15
|
-
def __init__(self):
|
|
16
|
-
super().__init__()
|
|
17
|
-
|
|
18
|
-
def set_percent(self, percent: float):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
def set_text(self, text: str):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ProgressVoid(sp.ProgressBarAdapter):
|
|
26
|
-
|
|
27
|
-
def __init__(self, progress_bar: sp.ABCProgressBar = VoidPbar()):
|
|
28
|
-
super().__init__(progress_bar)
|
|
29
|
-
|
|
30
|
-
def update(self, percent: float, status: str = "-"):
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
def update_percent(self, percent: float = 0):
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
def update_text(self, text: str = "-"):
|
|
37
|
-
pass
|
|
8
|
+
from soup_files import File, ProgressBarAdapter, CreatePbar
|
|
38
9
|
|
|
39
10
|
|
|
40
11
|
class RecognizeImage(object):
|
|
@@ -59,7 +30,7 @@ class RecognizeImage(object):
|
|
|
59
30
|
def bin_tesseract(self) -> BinTesseract:
|
|
60
31
|
return self.module_ocr.mod_ocr.bin_tess
|
|
61
32
|
|
|
62
|
-
def image_content_data(self, img: ImageObject |
|
|
33
|
+
def image_content_data(self, img: cs.ImageObject | File) -> DataFrame:
|
|
63
34
|
"""
|
|
64
35
|
Retorna uma tabela estruturada com os dados reconhecidos da imagem.
|
|
65
36
|
|
|
@@ -68,35 +39,35 @@ class RecognizeImage(object):
|
|
|
68
39
|
nível do OCR e confiabilidade, entre outras.
|
|
69
40
|
|
|
70
41
|
Parâmetros:
|
|
71
|
-
img (ImageObject |
|
|
72
|
-
`ImageObject` ou um arquivo `
|
|
42
|
+
img (cs.ImageObject | File): A imagem a ser processada, podendo ser um objeto
|
|
43
|
+
`cs.ImageObject` ou um arquivo `File`.
|
|
73
44
|
|
|
74
45
|
Retorna:
|
|
75
46
|
DataFrame: Uma tabela contendo os dados reconhecidos na imagem.
|
|
76
47
|
|
|
77
48
|
Lança:
|
|
78
|
-
ValueError: Se o tipo do parâmetro `img` não for `ImageObject` ou `
|
|
49
|
+
ValueError: Se o tipo do parâmetro `img` não for `cs.ImageObject` ou `File`.
|
|
79
50
|
|
|
80
|
-
@param img: Imagem a ser processada, podendo ser uma instância de `ImageObject`
|
|
81
|
-
ou um arquivo `
|
|
82
|
-
@type img: ImageObject |
|
|
51
|
+
@param img: Imagem a ser processada, podendo ser uma instância de `cs.ImageObject`
|
|
52
|
+
ou um arquivo `File`.
|
|
53
|
+
@type img: cs.ImageObject | File
|
|
83
54
|
|
|
84
55
|
@rtype: pandas.DataFrame
|
|
85
56
|
@return: Um DataFrame com os dados reconhecidos na imagem.
|
|
86
57
|
|
|
87
|
-
@raise ValueError: Caso o tipo de `img` não seja `ImageObject` nem `
|
|
58
|
+
@raise ValueError: Caso o tipo de `img` não seja `cs.ImageObject` nem `File`.
|
|
88
59
|
"""
|
|
89
60
|
return self.image_recognize(img).to_dataframe()
|
|
90
61
|
|
|
91
|
-
def image_recognize(self, img: ImageObject |
|
|
92
|
-
if isinstance(img,
|
|
93
|
-
img = ImageObject(img)
|
|
62
|
+
def image_recognize(self, img: cs.ImageObject | File, *, land_scape: bool = False) -> TextRecognized:
|
|
63
|
+
if isinstance(img, File):
|
|
64
|
+
img = cs.ImageObject(img)
|
|
94
65
|
if land_scape:
|
|
95
66
|
img.set_landscape()
|
|
96
67
|
rec: TextRecognized = self.module_ocr.to_reconize(img)
|
|
97
68
|
return rec
|
|
98
69
|
|
|
99
|
-
def image_to_string(self, img: ImageObject |
|
|
70
|
+
def image_to_string(self, img: cs.ImageObject | File) -> str:
|
|
100
71
|
return self.module_ocr.to_string(img)
|
|
101
72
|
|
|
102
73
|
|
|
@@ -108,50 +79,45 @@ class RecognizePdf(object):
|
|
|
108
79
|
lib_ocr: LibOcr = DEFAULT_LIB_OCR,
|
|
109
80
|
):
|
|
110
81
|
self.recognize_image: RecognizeImage = RecognizeImage(bin_tess, lib_ocr=lib_ocr)
|
|
111
|
-
self.pbar:
|
|
82
|
+
self.pbar: ProgressBarAdapter = CreatePbar().get()
|
|
112
83
|
|
|
113
|
-
def set_pbar(self, p:
|
|
84
|
+
def set_pbar(self, p: ProgressBarAdapter):
|
|
114
85
|
self.pbar = p
|
|
115
86
|
|
|
116
|
-
def recognize_page_pdf(self, page: PageDocumentPdf, *, dpi: int =
|
|
87
|
+
def recognize_page_pdf(self, page: cs.PageDocumentPdf, *, dpi: int = 200) -> cs.DocumentPdf:
|
|
117
88
|
"""
|
|
118
89
|
Converte a página em Imagem, reconhece o texto com OCR e
|
|
119
90
|
retorna uma nova página com o texto reconhecido.
|
|
120
91
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
img: ImageObject = _conv.to_images(dpi=dpi)[0]
|
|
92
|
+
_stream = cs.PdfStream(pbar=self.pbar, dpi=dpi)
|
|
93
|
+
_stream.add_page(page)
|
|
94
|
+
img: cs.ImageObject = _stream.convert_pdf_to_images.to_images(dpi=dpi)[0]
|
|
125
95
|
text_recognized: TextRecognized = self.recognize_image.image_recognize(img)
|
|
126
|
-
del _doc
|
|
127
|
-
del _conv
|
|
128
96
|
del img
|
|
129
|
-
text_recognized.metadata.file_path = page.metadata.file_path
|
|
130
|
-
text_recognized.metadata.dir_path = page.metadata.dir_path
|
|
131
97
|
return text_recognized.to_document()
|
|
132
98
|
|
|
133
|
-
def recognize_document(self, doc: DocumentPdf, *, dpi: int =
|
|
134
|
-
_pages: list[PageDocumentPdf] = []
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
_conv.
|
|
138
|
-
images: list[ImageObject] = _conv.to_images(dpi=dpi)
|
|
99
|
+
def recognize_document(self, doc: cs.DocumentPdf, *, dpi: int = 200) -> cs.DocumentPdf:
|
|
100
|
+
_pages: list[cs.PageDocumentPdf] = []
|
|
101
|
+
_stream = cs.PdfStream(pbar=self.pbar, dpi=dpi)
|
|
102
|
+
_stream.add_document(doc)
|
|
103
|
+
_conv = _stream.convert_pdf_to_images
|
|
104
|
+
images: list[cs.ImageObject] = _conv.to_images(dpi=dpi)
|
|
139
105
|
max_num: int = len(images)
|
|
140
106
|
for num, img in enumerate(images):
|
|
141
107
|
self.pbar.update(
|
|
142
108
|
((num + 1) / max_num) * 100,
|
|
143
|
-
f'OCR Documento [{num+1} de {max_num}]'
|
|
109
|
+
f'OCR Documento: [{num+1} de {max_num}]'
|
|
144
110
|
)
|
|
145
111
|
text_recognized = self.recognize_image.image_recognize(img)
|
|
146
112
|
_pages.append(text_recognized.to_page_pdf())
|
|
147
113
|
print()
|
|
148
114
|
self.pbar.stop()
|
|
149
|
-
new_doc = DocumentPdf.create_from_pages(_pages)
|
|
115
|
+
new_doc = cs.DocumentPdf.create_from_pages(_pages)
|
|
150
116
|
new_doc.metadata = doc.metadata
|
|
151
117
|
return new_doc
|
|
152
118
|
|
|
153
|
-
def recognize_file_pdf(self, file:
|
|
154
|
-
file_doc = DocumentPdf.create_from_file(file)
|
|
119
|
+
def recognize_file_pdf(self, file: File, *, dpi: int = 300) -> cs.DocumentPdf:
|
|
120
|
+
file_doc = cs.DocumentPdf.create_from_file(file)
|
|
155
121
|
recognized_doc = self.recognize_document(file_doc, dpi=dpi)
|
|
156
122
|
del file_doc
|
|
157
123
|
return recognized_doc
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocr_stream
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: Extrair textos de documentos digitalizados e imagens.
|
|
5
5
|
Author: Bruno Chaves
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -12,7 +12,7 @@ Requires-Dist: pandas
|
|
|
12
12
|
Requires-Dist: requests>=2.32.4
|
|
13
13
|
Requires-Dist: opencv-python
|
|
14
14
|
Requires-Dist: pytesseract
|
|
15
|
-
Requires-Dist: convert_stream>=2.4.
|
|
15
|
+
Requires-Dist: convert_stream>=2.4.3
|
|
16
16
|
Requires-Dist: soup_files>=1.4
|
|
17
17
|
|
|
18
18
|
# ocrlib
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ocr_stream"
|
|
7
|
-
version = "2.4.
|
|
7
|
+
version = "2.4.2"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Bruno Chaves"},
|
|
10
10
|
]
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"requests>=2.32.4",
|
|
22
22
|
"opencv-python",
|
|
23
23
|
"pytesseract",
|
|
24
|
-
"convert_stream>=2.4.
|
|
24
|
+
"convert_stream>=2.4.3",
|
|
25
25
|
"soup_files>=1.4",
|
|
26
26
|
]
|
|
27
27
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|