ocr-stream 2.3.3__tar.gz → 2.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ocr_stream
3
- Version: 2.3.3
3
+ Version: 2.4
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.3.3
15
+ Requires-Dist: convert_stream>=2.4
16
16
  Requires-Dist: soup_files>=1.4
17
17
 
18
18
  # ocrlib
@@ -1,38 +1,75 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import annotations
3
3
  from io import BytesIO
4
+ import threading
4
5
  from ocr_stream.modules import *
5
6
  from ocr_stream.bin_tess import BinTesseract, get_path_tesseract_sys
6
7
  from ocr_stream.ocr import TesseractOcr, TextRecognized
7
8
  from ocr_stream.recognize import RecognizeImage, RecognizePdf
8
- from convert_stream import DocumentPdf
9
- from convert_stream.image import ImageObject
9
+ from convert_stream import DocumentPdf, PageDocumentPdf
10
+ from convert_stream.image import ImageObject, CollectionImages
10
11
  from convert_stream.mod_types import (
11
- ColumnsTable, ColumnBody, HeadCell, HeadValues, create_void_table, create_void_map
12
+ ColumnsTable, ColumnBody, HeadCell, HeadValues, DictTextTable
12
13
  )
14
+
13
15
  from convert_stream.text import FindText
14
- from convert_stream.pdf import ConvertPdfToImages
16
+ from convert_stream.pdf import ConvertPdfToImages, CollectionPagePdf
15
17
  import pandas as pd
16
18
  from soup_files import (
17
- JsonConvert, ProgressBarAdapter, CreatePbar, Directory, File, InputFiles, LibraryDocs
19
+ JsonConvert, ProgressBarAdapter, ABCProgressBar, CreatePbar, Directory, File, InputFiles, LibraryDocs,
20
+ ProgressBarSimple
18
21
  )
19
22
 
20
23
 
24
+ class VoidPbar(ABCProgressBar):
25
+
26
+ def __init__(self):
27
+ super().__init__()
28
+
29
+ def set_percent(self, percent: float):
30
+ pass
31
+
32
+ def set_text(self, text: str):
33
+ pass
34
+
35
+
36
+ class ProgressVoid(ProgressBarAdapter):
37
+
38
+ def __init__(self, progress_bar: ABCProgressBar = VoidPbar()):
39
+ super().__init__(progress_bar)
40
+
41
+ def update(self, percent: float, status: str = "-"):
42
+ pass
43
+
44
+ def update_percent(self, percent: float = 0):
45
+ pass
46
+
47
+ def update_text(self, text: str = "-"):
48
+ pass
49
+
50
+
51
+ def read_image(img: ImageObject, rec: RecognizeImage = RecognizeImage()) -> TextRecognized:
52
+ _txt = rec.image_recognize(img)
53
+ _txt.metadata = img.metadata
54
+ return _txt
55
+
56
+
21
57
  def read_images(
22
58
  images: list[ImageObject], *,
59
+ rec: RecognizeImage = RecognizeImage(),
23
60
  pbar: ProgressBarAdapter = CreatePbar().get()
24
61
  ) -> DocumentPdf:
25
62
  pbar.start()
26
63
  print()
27
64
  max_num = len(images)
28
65
  pages = []
29
- rec = RecognizeImage()
66
+
30
67
  for idx, image in enumerate(images):
31
68
  pbar.update(
32
69
  ((idx + 1) / max_num) * 100,
33
70
  f'[OCR] {idx + 1} / {max_num}',
34
71
  )
35
- out = rec.image_recognize(image).to_page_pdf()
72
+ out = read_image(image, rec=rec).to_page_pdf()
36
73
  pages.append(out)
37
74
  tmp_doc = DocumentPdf.create_from_pages(pages)
38
75
  print()
@@ -47,18 +84,31 @@ def read_document(
47
84
  ) -> DocumentPdf:
48
85
  pbar.start()
49
86
  print()
50
- convert = ConvertPdfToImages.create(document)
51
- convert.set_pbar(pbar)
52
- images = convert.to_images(dpi=dpi)
87
+ _metadata = document.metadata
88
+ recognized_items: list[TextRecognized] = []
89
+ pages_pdf: list[PageDocumentPdf] = document.to_pages()
90
+ total = len(pages_pdf)
91
+ void_pbar = ProgressVoid()
92
+ for num, page in enumerate(pages_pdf):
93
+ pbar.update(
94
+ ((num + 1) / total) * 100,
95
+ f'[OCR PDF] {num + 1}/{total} {page.metadata.name}',
96
+ )
97
+ convert: ConvertPdfToImages = ConvertPdfToImages(DocumentPdf.create_from_pages([page]))
98
+ convert.set_pbar(void_pbar)
99
+ images: list[ImageObject] = convert.to_images(dpi=dpi)
100
+ for im in images:
101
+ recognized_items.append(read_image(im))
53
102
  print()
54
- return read_images(images, pbar=pbar)
103
+ final_pages: list[PageDocumentPdf] = [x.to_page_pdf() for x in recognized_items]
104
+ final_doc: DocumentPdf = DocumentPdf.create_from_pages(final_pages)
105
+ final_doc.metadata = _metadata
106
+ return final_doc
55
107
 
56
108
 
57
109
  class SearchableTextImage(object):
58
110
  """
59
111
 
60
- create_void_map() -> dict[str, ColumnBody]:
61
-
62
112
  return {
63
113
  ColumnsTable.KEY.value: ColumnBody(ColumnsTable.KEY.value, ListString([])),
64
114
  ColumnsTable.NUM_PAGE.value: ColumnBody(ColumnsTable.NUM_PAGE.value, ListString([])),
@@ -72,7 +122,7 @@ class SearchableTextImage(object):
72
122
  """
73
123
  def __init__(self, silent: bool = False):
74
124
  self.silent = silent
75
- self.elements: dict[str, ColumnBody] = create_void_map()
125
+ self.elements: DictTextTable = DictTextTable.create_void_dict()
76
126
 
77
127
  def __repr__(self):
78
128
  return f'SearchableTextPdf: {self.elements}'
@@ -149,7 +199,7 @@ class SearchableTextImage(object):
149
199
  return pd.DataFrame.from_dict(self.elements)
150
200
 
151
201
  def to_file_json(self, file: File):
152
- """Exporta os dados da busca para um arquivo .JSON"""
202
+ """Exporta os dados da busca para um arquivo JSON"""
153
203
  dt = JsonConvert.from_dict(self.elements).to_json_data()
154
204
  dt.to_file(file)
155
205
 
@@ -186,10 +236,10 @@ class ImageFinder(object):
186
236
  elif isinstance(img, BytesIO):
187
237
  img = ImageObject(img)
188
238
 
189
- if img.metadata['file_path'] is not None:
190
- self.docs_collection[img.metadata['file_path']] = img
239
+ if not img.metadata.file_path.is_empty:
240
+ self.docs_collection[img.metadata.file_path] = img
191
241
  else:
192
- self.docs_collection[img.metadata['name']] = img
242
+ self.docs_collection[img.metadata.name] = img
193
243
 
194
244
  def add_images(self, images: list[File] | list[ImageObject] | list[bytes] | list[BytesIO]) -> None:
195
245
  for f in images:
@@ -224,7 +274,7 @@ class ImageFinder(object):
224
274
  current_img: ImageObject = self.docs_collection[file_key]
225
275
  txt_in_img = self.recognize.image_to_string(current_img)
226
276
  if (txt_in_img == '') or (txt_in_img is None):
227
- self.pbar.update_text(f'ERRO: {n+1} {current_img.metadata["name"]}')
277
+ self.pbar.update_text(f'ERRO: {n+1} {current_img.metadata.name}')
228
278
  continue
229
279
  try:
230
280
  fd = FindText(txt_in_img, separator=separator)
@@ -235,20 +285,15 @@ class ImageFinder(object):
235
285
  except Exception as err:
236
286
  print(f'{__class__.__name__} {err}')
237
287
  else:
238
- file_path = current_img.metadata["file_path"]
239
- file_name = current_img.metadata["name"]
240
- dir_path = current_img.metadata["dir_path"]
241
- file_type = current_img.metadata["extension"]
242
-
243
288
  new_line: dict[str, str] = {
244
289
  ColumnsTable.KEY.value: f'{idx}',
245
290
  ColumnsTable.NUM_PAGE.value: 'nan',
246
291
  ColumnsTable.NUM_LINE.value: f'{idx + 1}',
247
292
  ColumnsTable.TEXT.value: math_text,
248
- ColumnsTable.FILE_NAME.value: file_name if file_path is not None else 'nan',
249
- ColumnsTable.FILETYPE.value: file_type if file_path is not None else 'nan',
250
- ColumnsTable.FILE_PATH.value: file_path if file_path is not None else 'nan',
251
- ColumnsTable.DIR.value: dir_path if file_path is not None else 'nan',
293
+ ColumnsTable.FILE_NAME.value: current_img.metadata.file_path,
294
+ ColumnsTable.FILETYPE.value: current_img.metadata.extension,
295
+ ColumnsTable.FILE_PATH.value: current_img.metadata.dir_path,
296
+ ColumnsTable.DIR.value: current_img.metadata.dir_path,
252
297
  }
253
298
  _searchable.add_line(new_line)
254
299
  return _searchable
@@ -280,7 +325,7 @@ class ImageFinder(object):
280
325
  current_img: ImageObject = self.docs_collection[file_key]
281
326
  txt_in_img = self.recognize.image_to_string(current_img)
282
327
  if (txt_in_img == '') or (txt_in_img is None):
283
- self.pbar.update_text(f'ERRO: {n + 1} {current_img.metadata["name"]}')
328
+ self.pbar.update_text(f'ERRO: {n + 1} {current_img.metadata.name}')
284
329
  continue
285
330
  try:
286
331
  fd = FindText(txt_in_img, separator=separator)
@@ -291,20 +336,15 @@ class ImageFinder(object):
291
336
  except Exception as err:
292
337
  print(f'{__class__.__name__} {err}')
293
338
  else:
294
- file_path = current_img.metadata["file_path"]
295
- file_name = current_img.metadata["name"]
296
- dir_path = current_img.metadata["dir_path"]
297
- file_type = current_img.metadata["extension"]
298
-
299
339
  new_line: dict[str, str] = {
300
340
  ColumnsTable.KEY.value: f'{idx}',
301
341
  ColumnsTable.NUM_PAGE.value: 'nan',
302
342
  ColumnsTable.NUM_LINE.value: f'{idx + 1}',
303
343
  ColumnsTable.TEXT.value: math_text,
304
- ColumnsTable.FILE_NAME.value: file_name if file_path is not None else 'nan',
305
- ColumnsTable.FILETYPE.value: file_type if file_path is not None else 'nan',
306
- ColumnsTable.FILE_PATH.value: file_path if file_path is not None else 'nan',
307
- ColumnsTable.DIR.value: dir_path if file_path is not None else 'nan',
344
+ ColumnsTable.FILE_NAME.value: current_img.metadata.file_path,
345
+ ColumnsTable.FILETYPE.value: current_img.metadata.extension,
346
+ ColumnsTable.FILE_PATH.value: current_img.metadata.dir_path,
347
+ ColumnsTable.DIR.value: current_img.metadata.dir_path,
308
348
  }
309
349
  _searchable.add_line(new_line)
310
350
  return _searchable
@@ -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
- #pyocr.tesseract = object
33
34
 
34
- if MOD_PYTESSERACT and MOD_PYOCR:
35
- ModuleOcr: TypeAlias = Union[pyocr, pytesseract]
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,6 +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']
49
-
@@ -10,19 +10,13 @@ from soup_files import File
10
10
  from convert_stream import DocumentPdf, PageDocumentPdf
11
11
  from convert_stream.mod_types.modules import DEFAULT_LIB_IMAGE, LibImage
12
12
  from convert_stream.image import ImageObject
13
- from convert_stream.mod_types import get_void_metadata
13
+ from convert_stream.mod_types import MetaDataItem, MetaDataFile
14
14
  from convert_stream.mod_types.table_types import ColumnBody
15
15
  from ocr_stream.modules import (
16
- LibOcr, DEFAULT_LIB_OCR, pytesseract, Output, pyocr,
16
+ LibOcr, DEFAULT_LIB_OCR, pytesseract, Output, pyocr, easyocr
17
17
  )
18
18
  from ocr_stream.bin_tess import BinTesseract
19
19
 
20
- try:
21
- import pyocr.tesseract
22
- except Exception as err:
23
- print(err)
24
- pyocr = None
25
-
26
20
 
27
21
  class TextRecognized(object):
28
22
  """
@@ -31,8 +25,9 @@ class TextRecognized(object):
31
25
  """
32
26
 
33
27
  def __init__(self, bytes_recognized: bytes):
34
- self.metadata: dict[str, str | None] = get_void_metadata()
28
+ self.metadata: MetaDataFile = MetaDataFile()
35
29
  self.bytes_recognized: bytes = bytes_recognized
30
+
36
31
  self.list_bad_char: list[str] = [
37
32
  ':', ',', ';', '$', '=',
38
33
  '!', '}', '{', '(', ')',
@@ -51,7 +46,10 @@ class TextRecognized(object):
51
46
  return doc.to_pages()[0]
52
47
 
53
48
  def to_document(self) -> DocumentPdf:
54
- return DocumentPdf(BytesIO(self.bytes_recognized))
49
+ tmp_doc = DocumentPdf(BytesIO(self.bytes_recognized))
50
+ tmp_doc.metadata.file_path = self.metadata.file_path
51
+ tmp_doc.metadata.dir_path = self.metadata.dir_path
52
+ return tmp_doc
55
53
 
56
54
  def to_dict(self, separator: str = '\n') -> dict[str, ColumnBody]:
57
55
  return self.to_page_pdf().to_dict(separator=separator)
@@ -138,6 +136,78 @@ class IPytesseract(ABCOcrTesseract):
138
136
  return TextRecognized(bt)
139
137
 
140
138
 
139
+ # ======================================================================#
140
+ # Modulo easyocr
141
+ # ======================================================================#
142
+ class IEasyOcr(ABCOcrTesseract):
143
+
144
+ def __init__(self, bin_tess: BinTesseract, *, lang: str = 'pt', tess_data_dir: str = None):
145
+ super().__init__(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
146
+ self.reader: easyocr.Reader = easyocr.Reader([self.lang], gpu=True)
147
+
148
+ def to_string(self, img: Union[File, ImageObject]) -> str:
149
+ if isinstance(img, File):
150
+ img = ImageObject(img)
151
+ result = self.reader.readtext(img.to_opencv())
152
+ text = '\n'.join([res[1] for res in result])
153
+ return text
154
+
155
+ def to_reconize(self, img: Union[File, ImageObject]) -> TextRecognized:
156
+ import cv2
157
+ import numpy as np
158
+ from PIL import Image
159
+ from io import BytesIO
160
+ from reportlab.pdfgen import canvas
161
+ from reportlab.lib.pagesizes import A4
162
+ from reportlab.lib.utils import ImageReader
163
+
164
+ # 1️⃣ Converter o objeto de entrada para imagem OpenCV
165
+ if isinstance(img, File):
166
+ img = ImageObject(img)
167
+ img_cv = img.to_opencv()
168
+ img_h, img_w = img_cv.shape[:2]
169
+
170
+ # 2️⃣ Reconhecer o texto com EasyOCR
171
+ results = self.reader.readtext(img_cv)
172
+
173
+ # 3️⃣ Converter a imagem para PDF (imagem de fundo)
174
+ buffer = BytesIO()
175
+ pdf = canvas.Canvas(buffer, pagesize=(img_w, img_h))
176
+
177
+ # Desenha a imagem original como fundo
178
+ img_pil = Image.fromarray(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB))
179
+ pdf.drawImage(ImageReader(img_pil), 0, 0, width=img_w, height=img_h)
180
+
181
+ # 4️⃣ Adiciona o texto OCR como camada "invisível" sobre a imagem
182
+ pdf.setFont("Helvetica", 8)
183
+ pdf.setFillColorRGB(1, 1, 1, alpha=0) # texto invisível
184
+
185
+ for (bbox, text, conf) in results:
186
+ if not text.strip():
187
+ continue
188
+ # Coordenadas do bounding box (EasyOCR retorna 4 pontos)
189
+ (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
190
+
191
+ # Média da posição vertical
192
+ avg_y = (y1 + y2 + y3 + y4) / 4
193
+
194
+ # Posição invertida (PDF tem origem no canto inferior esquerdo)
195
+ y_pdf = img_h - avg_y
196
+
197
+ # Largura estimada
198
+ text_width = abs(x2 - x1)
199
+ pdf.drawString(x1, y_pdf, text)
200
+
201
+ pdf.showPage()
202
+ pdf.save()
203
+ buffer.seek(0)
204
+
205
+ # 5️⃣ Retorna como TextRecognized (bytes do PDF com texto embutido)
206
+ text_rec = TextRecognized(buffer.read())
207
+ text_rec.metadata = img.metadata if hasattr(img, "metadata") else MetaDataFile()
208
+ return text_rec
209
+
210
+
141
211
  # ======================================================================#
142
212
  # Modulo OCR pyocr
143
213
  # ======================================================================#
@@ -202,6 +272,10 @@ class TesseractOcr(object):
202
272
  md = IPytesseract(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
203
273
  elif lib_ocr == LibOcr.PYOCR:
204
274
  md = IPyOcr(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
275
+ elif lib_ocr == LibOcr.EASYOCR:
276
+ if lang is None:
277
+ lang = 'pt'
278
+ md = IEasyOcr(bin_tess, lang=lang, tess_data_dir=tess_data_dir)
205
279
  else:
206
280
  raise ValueError(f'{lib_ocr} is not supported')
207
281
  return cls(md)
@@ -123,4 +123,3 @@ class RecognizePdf(object):
123
123
  recognized_doc = self.recognize_document(file_doc, dpi=dpi)
124
124
  del file_doc
125
125
  return recognized_doc
126
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ocr_stream
3
- Version: 2.3.3
3
+ Version: 2.4
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.3.3
15
+ Requires-Dist: convert_stream>=2.4
16
16
  Requires-Dist: soup_files>=1.4
17
17
 
18
18
  # ocrlib
@@ -5,5 +5,5 @@ pandas
5
5
  requests>=2.32.4
6
6
  opencv-python
7
7
  pytesseract
8
- convert_stream>=2.3.3
8
+ convert_stream>=2.4
9
9
  soup_files>=1.4
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ocr_stream"
7
- version = "2.3.3"
7
+ version = "2.4"
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.3.3",
24
+ "convert_stream>=2.4",
25
25
  "soup_files>=1.4",
26
26
  ]
27
27
 
File without changes
File without changes