convert-stream 2.2.7__tar.gz → 2.3.1__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.
- {convert_stream-2.2.7 → convert_stream-2.3.1}/PKG-INFO +1 -1
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/__init__.py +10 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/doc_stream.py +51 -18
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/enum_libs/modules.py +30 -19
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/image/img_object.py +92 -85
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/pdf/pdf_document.py +35 -6
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/pdf/pdf_page.py +1 -1
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream.egg-info/PKG-INFO +1 -1
- {convert_stream-2.2.7 → convert_stream-2.3.1}/pyproject.toml +1 -1
- {convert_stream-2.2.7 → convert_stream-2.3.1}/README.md +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/__main__.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/_version.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/enum_libs/__init__.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/enum_libs/enums.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/image/__init__.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/pdf/__init__.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/pdf/image_to_pdf.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/pdf/pdf_to_image.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/sheets/__init__.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/sheets/load.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/sheets/parse.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/sheets/save.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/table_files.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/text/__init__.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/text/date.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/text/string.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream/text/terminal.py +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream.egg-info/SOURCES.txt +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream.egg-info/dependency_links.txt +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream.egg-info/requires.txt +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/convert_stream.egg-info/top_level.txt +0 -0
- {convert_stream-2.2.7 → convert_stream-2.3.1}/setup.cfg +0 -0
|
@@ -88,3 +88,13 @@ def read_image(image: Union[str, File, bytes, BytesIO]) -> PdfStream:
|
|
|
88
88
|
return _pdf_stream
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def merge(stream: list[PdfStream]) -> PdfStream:
|
|
92
|
+
if len(stream) == 0:
|
|
93
|
+
raise ValueError()
|
|
94
|
+
if len(stream) == 1:
|
|
95
|
+
return stream[0]
|
|
96
|
+
else:
|
|
97
|
+
s = stream[0]
|
|
98
|
+
for pdf_stream in stream[1:]:
|
|
99
|
+
s.add_pages(pdf_stream.to_document().to_pages())
|
|
100
|
+
return s
|
|
@@ -8,7 +8,6 @@ from convert_stream.enum_libs.modules import (
|
|
|
8
8
|
DEFAULT_LIB_PDF, DEFAULT_LIB_IMAGE, DEFAULT_LIB_PDF_TO_IMG, DEFAULT_LIB_IMAGE_TO_PDF,
|
|
9
9
|
)
|
|
10
10
|
from convert_stream.enum_libs.enums import LibPDF, LibImage, LibImageToPdf, LibPdfToImage
|
|
11
|
-
from convert_stream.text.string import FindText
|
|
12
11
|
from convert_stream.pdf.pdf_page import PageDocumentPdf
|
|
13
12
|
from convert_stream.image.img_object import ImageObject, CollectionImages
|
|
14
13
|
from convert_stream.pdf.pdf_document import DocumentPdf, CollectionPagePdf
|
|
@@ -108,22 +107,43 @@ class PdfStream(object):
|
|
|
108
107
|
def add_directory_image(self, src_dir: Directory, max_files: int = 4000) -> None:
|
|
109
108
|
self.collection_images.add_directory_images(src_dir, max_files=max_files)
|
|
110
109
|
|
|
111
|
-
def to_document(self) -> DocumentPdf:
|
|
110
|
+
def to_document(self, *, land_scape: bool = False, a4: bool = False) -> DocumentPdf:
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
Converte os documentos adicionados em PdfStream (imagens/pdfs) em
|
|
114
|
+
objeto do tipo DocumentPdf().
|
|
115
|
+
"""
|
|
112
116
|
if self.collection_pages.is_null and self.collection_images.is_empty:
|
|
113
117
|
raise ValueError('Adicione imagens e/ou documentos para prosseguir!')
|
|
118
|
+
|
|
114
119
|
_doc = None
|
|
115
|
-
if not self.collection_pages.is_null:
|
|
116
|
-
_doc = DocumentPdf.create_from_pages(self.collection_pages.pages)
|
|
117
120
|
if not self.collection_images.is_empty:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
_convert = ConvertImageToPdf(self.collection_images, lib_img_to_pdf=self.lib_img_to_pdf)
|
|
122
|
+
_doc: DocumentPdf = _convert.to_document(land_scape, a4=a4)
|
|
123
|
+
if not self.collection_pages.is_null:
|
|
121
124
|
if _doc is None:
|
|
122
|
-
_doc =
|
|
125
|
+
_doc: DocumentPdf = self.collection_pages.to_document(self.lib_pdf)
|
|
123
126
|
else:
|
|
124
|
-
_doc.add_pages(
|
|
127
|
+
_doc.add_pages(self.collection_pages.pages)
|
|
125
128
|
return _doc
|
|
126
129
|
|
|
130
|
+
def thresold(self, *, land_scape: bool = False, a4: bool = False, dpi=300) -> DocumentPdf:
|
|
131
|
+
images: list[ImageObject] = self.collection_images.images
|
|
132
|
+
if not self.collection_pages.is_null:
|
|
133
|
+
tmp_doc = DocumentPdf.create_from_pages(self.collection_pages.pages)
|
|
134
|
+
conv_pdf_to_img = ConvertPdfToImages.create(tmp_doc)
|
|
135
|
+
images.extend(conv_pdf_to_img.to_images(dpi=dpi))
|
|
136
|
+
del tmp_doc
|
|
137
|
+
if len(images) == 0:
|
|
138
|
+
raise ValueError('Adicione imagens para prosseguir!')
|
|
139
|
+
if land_scape:
|
|
140
|
+
for img in images:
|
|
141
|
+
img.set_landscape()
|
|
142
|
+
for i in images:
|
|
143
|
+
i.set_threshold_gray()
|
|
144
|
+
conv_img_to_pdf = ConvertImageToPdf(CollectionImages(images), lib_img_to_pdf=self.lib_img_to_pdf)
|
|
145
|
+
return conv_img_to_pdf.to_document(a4=a4)
|
|
146
|
+
|
|
127
147
|
def to_files_images(
|
|
128
148
|
self,
|
|
129
149
|
output_dir: Directory, *,
|
|
@@ -134,16 +154,20 @@ class PdfStream(object):
|
|
|
134
154
|
if self.collection_pages.is_null and self.collection_images.is_empty:
|
|
135
155
|
raise ValueError('Adicione imagens e/ou documentos para prosseguir!')
|
|
136
156
|
self.progress.start()
|
|
157
|
+
|
|
158
|
+
image_stream = ImageStream()
|
|
159
|
+
if not self.collection_images.is_empty:
|
|
160
|
+
image_stream.add_images(self.collection_images.images)
|
|
137
161
|
if not self.collection_pages.is_null:
|
|
138
162
|
self.progress.update_text('Processando Imagens')
|
|
139
163
|
# Criar um documento a partir das páginas
|
|
140
164
|
doc_pdf = DocumentPdf.create_from_pages(self.collection_pages.pages)
|
|
141
165
|
convert_pdf_to_img = ConvertPdfToImages.create(doc_pdf)
|
|
142
166
|
# Converter as páginas PDF em imagens e adicionar a coleção já existe de imagens.
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
_pdf_to_images: list[ImageObject] = convert_pdf_to_img.to_images(dpi=dpi)
|
|
168
|
+
image_stream.add_images(_pdf_to_images)
|
|
145
169
|
# Exportar para arquivos.
|
|
146
|
-
|
|
170
|
+
image_stream.to_files_image(output_dir, replace=replace, land_scape=land_scape)
|
|
147
171
|
self.progress.stop()
|
|
148
172
|
|
|
149
173
|
def to_files_pdf(
|
|
@@ -151,21 +175,27 @@ class PdfStream(object):
|
|
|
151
175
|
output_dir: Directory, *,
|
|
152
176
|
replace: bool = False,
|
|
153
177
|
land_scape: bool = False,
|
|
178
|
+
a4: bool = False,
|
|
179
|
+
prefix: str = None,
|
|
154
180
|
) -> None:
|
|
155
181
|
if self.collection_pages.is_null and self.collection_images.is_empty:
|
|
156
182
|
raise ValueError('Adicione imagens e/ou documentos para prosseguir!')
|
|
183
|
+
pages_document = []
|
|
157
184
|
if not self.collection_pages.is_null:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
self.collection_images.
|
|
185
|
+
pages_document.extend(self.collection_pages.pages)
|
|
186
|
+
if not self.collection_images.is_empty:
|
|
187
|
+
img_to_pdf = ConvertImageToPdf(self.collection_images, lib_img_to_pdf=self.lib_img_to_pdf)
|
|
188
|
+
tmp_document = img_to_pdf.to_document(land_scape, a4=a4)
|
|
189
|
+
pages_document.extend(tmp_document.to_pages())
|
|
190
|
+
final_collection = CollectionPagePdf(pages_document)
|
|
161
191
|
if land_scape:
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
final_collection.set_land_scape()
|
|
193
|
+
final_collection.to_files_pdf(output_dir, replace=replace, prefix=prefix)
|
|
164
194
|
|
|
165
195
|
|
|
166
196
|
class SplitPdf(object):
|
|
167
197
|
|
|
168
|
-
def __init__(self, pages: list[PageDocumentPdf], pbar: ProgressBarAdapter = ProgressBarAdapter()):
|
|
198
|
+
def __init__(self, pages: list[PageDocumentPdf] = [], pbar: ProgressBarAdapter = ProgressBarAdapter()):
|
|
169
199
|
self.pbar: ProgressBarAdapter = pbar
|
|
170
200
|
self.__collection_pages: CollectionPagePdf = CollectionPagePdf(pages)
|
|
171
201
|
self.__collection_pages.set_pbar(self.pbar)
|
|
@@ -173,6 +203,9 @@ class SplitPdf(object):
|
|
|
173
203
|
def is_empty(self) -> bool:
|
|
174
204
|
return self.__collection_pages.is_null
|
|
175
205
|
|
|
206
|
+
def add_pages_pdf(self, pages: list[PageDocumentPdf]) -> None:
|
|
207
|
+
self.__collection_pages.add_pages(pages)
|
|
208
|
+
|
|
176
209
|
def export(self, output_dir: Directory, *, replace: bool = False, prefix: str = 'pag') -> None:
|
|
177
210
|
if self.is_empty():
|
|
178
211
|
self.pbar.update_text(f'O Documento está vazio!')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from typing import Union, TypeAlias
|
|
4
|
+
from typing import Union, TypeAlias #, Any
|
|
5
5
|
from convert_stream.enum_libs.enums import *
|
|
6
6
|
|
|
7
7
|
MOD_IMG_PIL: bool = False
|
|
@@ -10,6 +10,17 @@ MOD_PYPDF: bool = False
|
|
|
10
10
|
MOD_FITZ: bool = False
|
|
11
11
|
MOD_CANVAS: bool = False
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
class FakeType(object):
|
|
15
|
+
Image = object
|
|
16
|
+
MatLike = object
|
|
17
|
+
TextPage = object
|
|
18
|
+
Page = object
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
13
24
|
#=================================================================#
|
|
14
25
|
# Módulos de Imagen, PIL e opencv
|
|
15
26
|
#=================================================================#
|
|
@@ -18,16 +29,16 @@ try:
|
|
|
18
29
|
from cv2.typing import MatLike
|
|
19
30
|
MOD_IMG_OPENCV = True
|
|
20
31
|
except ImportError:
|
|
21
|
-
cv2.typing.MatLike =
|
|
22
|
-
MatLike =
|
|
32
|
+
#cv2.typing.MatLike = FakeType
|
|
33
|
+
MatLike = FakeType
|
|
23
34
|
|
|
24
35
|
try:
|
|
25
36
|
from PIL import Image
|
|
26
37
|
from PIL import ImageOps, ImageFilter
|
|
27
38
|
MOD_IMG_PIL = True
|
|
28
39
|
except ImportError:
|
|
29
|
-
Image =
|
|
30
|
-
Image.Image =
|
|
40
|
+
Image = FakeType
|
|
41
|
+
Image.Image = FakeType
|
|
31
42
|
|
|
32
43
|
if MOD_IMG_OPENCV and MOD_IMG_PIL:
|
|
33
44
|
ModuleImage: TypeAlias = Union[MatLike, Image.Image]
|
|
@@ -40,7 +51,7 @@ elif MOD_IMG_PIL:
|
|
|
40
51
|
DEFAULT_LIB_IMAGE = LibImage.PIL
|
|
41
52
|
else:
|
|
42
53
|
DEFAULT_LIB_IMAGE = LibImage.NOT_IMPLEMENTED
|
|
43
|
-
ModuleImage =
|
|
54
|
+
ModuleImage = FakeType # fallback para runtime
|
|
44
55
|
|
|
45
56
|
#=================================================================#
|
|
46
57
|
# Módulos para PDF fitz e pypdf
|
|
@@ -54,17 +65,17 @@ except ImportError:
|
|
|
54
65
|
import pymupdf as fitz
|
|
55
66
|
MOD_FITZ = True
|
|
56
67
|
except ImportError:
|
|
57
|
-
fitz =
|
|
58
|
-
fitz.Page =
|
|
59
|
-
fitz.TextPage =
|
|
68
|
+
fitz = FakeType
|
|
69
|
+
fitz.Page = FakeType
|
|
70
|
+
fitz.TextPage = FakeType
|
|
60
71
|
|
|
61
72
|
try:
|
|
62
73
|
from pypdf import PdfWriter, PdfReader, PageObject
|
|
63
74
|
MOD_PYPDF = True
|
|
64
75
|
except ImportError:
|
|
65
|
-
PageObject =
|
|
66
|
-
PdfReader =
|
|
67
|
-
PdfWriter =
|
|
76
|
+
PageObject = FakeType
|
|
77
|
+
PdfReader = FakeType
|
|
78
|
+
PdfWriter = FakeType
|
|
68
79
|
|
|
69
80
|
|
|
70
81
|
if MOD_FITZ and MOD_PYPDF:
|
|
@@ -80,8 +91,8 @@ elif MOD_PYPDF:
|
|
|
80
91
|
ModDocPdf = Union[PdfWriter]
|
|
81
92
|
DEFAULT_LIB_PDF = LibPDF.PYPDF
|
|
82
93
|
else:
|
|
83
|
-
ModPagePdf =
|
|
84
|
-
ModDocPdf =
|
|
94
|
+
ModPagePdf = FakeType # fallback para runtime
|
|
95
|
+
ModDocPdf = FakeType
|
|
85
96
|
DEFAULT_LIB_PDF = LibPDF.NOT_IMPLEMENTED
|
|
86
97
|
|
|
87
98
|
#=================================================================#
|
|
@@ -95,10 +106,10 @@ try:
|
|
|
95
106
|
from reportlab.lib.utils import ImageReader
|
|
96
107
|
MOD_CANVAS = True
|
|
97
108
|
except ImportError:
|
|
98
|
-
canvas =
|
|
99
|
-
Canvas =
|
|
109
|
+
canvas = FakeType
|
|
110
|
+
Canvas = FakeType
|
|
100
111
|
letter = (612, 792)
|
|
101
|
-
ImageReader =
|
|
112
|
+
ImageReader = FakeType
|
|
102
113
|
|
|
103
114
|
if MOD_CANVAS and MOD_FITZ and MOD_IMG_PIL:
|
|
104
115
|
ModImageToPdf: TypeAlias = Union[canvas, fitz.Page, Image]
|
|
@@ -113,7 +124,7 @@ elif MOD_IMG_PIL:
|
|
|
113
124
|
ModImageToPdf: TypeAlias = Union[Image]
|
|
114
125
|
DEFAULT_LIB_IMAGE_TO_PDF = LibImageToPdf.IMAGE_TO_PDF_PIL
|
|
115
126
|
else:
|
|
116
|
-
ModImageToPdf =
|
|
127
|
+
ModImageToPdf = FakeType
|
|
117
128
|
DEFAULT_LIB_IMAGE_TO_PDF = LibImageToPdf.NOT_IMPLEMENTED
|
|
118
129
|
|
|
119
130
|
#=================================================================#
|
|
@@ -125,4 +136,4 @@ if MOD_FITZ:
|
|
|
125
136
|
ModPdfToImage: TypeAlias = Union[fitz]
|
|
126
137
|
else:
|
|
127
138
|
DEFAULT_LIB_PDF_TO_IMG = LibPdfToImage.NOT_IMPLEMENTED
|
|
128
|
-
ModPdfToImage =
|
|
139
|
+
ModPdfToImage = FakeType
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
Módulo para trabalhar com imagens
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
|
+
from typing import Union
|
|
7
8
|
from abc import ABC, abstractmethod
|
|
8
9
|
from hashlib import md5 as md5_hash
|
|
9
10
|
from soup_files import File, Directory, InputFiles, LibraryDocs, ProgressBarAdapter
|
|
@@ -63,11 +64,11 @@ class ABCImageObject(ABC):
|
|
|
63
64
|
pass
|
|
64
65
|
|
|
65
66
|
@abstractmethod
|
|
66
|
-
def
|
|
67
|
+
def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
67
68
|
pass
|
|
68
69
|
|
|
69
70
|
@abstractmethod
|
|
70
|
-
def
|
|
71
|
+
def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
71
72
|
pass
|
|
72
73
|
|
|
73
74
|
@abstractmethod
|
|
@@ -114,7 +115,8 @@ class ImplementOpenCv(ABCImageObject):
|
|
|
114
115
|
|
|
115
116
|
def set_landscape(self):
|
|
116
117
|
if self.is_vertical():
|
|
117
|
-
self.set_rotation(RotationAngle.ROTATION_90)
|
|
118
|
+
#self.set_rotation(RotationAngle.ROTATION_90)
|
|
119
|
+
self.module_image = cv2.rotate(self.module_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
|
118
120
|
|
|
119
121
|
def set_vertical(self):
|
|
120
122
|
if self.is_landscape():
|
|
@@ -128,36 +130,10 @@ class ImplementOpenCv(ABCImageObject):
|
|
|
128
130
|
elif angle == RotationAngle.ROTATION_270:
|
|
129
131
|
self.module_image = cv2.rotate(self.module_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
|
130
132
|
|
|
131
|
-
def set_gausian_blur(self, sigma: float = 0.
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# 2. Detecta o texto com threshold adaptativo
|
|
136
|
-
# Texto ficará branco (255) e fundo preto (0)
|
|
137
|
-
text_mask = cv2.adaptiveThreshold(
|
|
138
|
-
gray, 160,
|
|
139
|
-
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
|
140
|
-
cv2.THRESH_BINARY_INV,
|
|
141
|
-
25, 15
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
# 3. Inverte máscara (fundo branco, texto preto)
|
|
145
|
-
background_mask = cv2.bitwise_not(text_mask)
|
|
146
|
-
|
|
147
|
-
# 4. Borra apenas o fundo
|
|
148
|
-
blurred_background = cv2.GaussianBlur(gray, (0, 0), sigma)
|
|
149
|
-
|
|
150
|
-
# 5. Combina: fundo borrado + texto nítido
|
|
151
|
-
text_part = cv2.bitwise_and(gray, gray, mask=text_mask)
|
|
152
|
-
background_part = cv2.bitwise_and(blurred_background, blurred_background, mask=background_mask)
|
|
153
|
-
combined = cv2.add(text_part, background_part)
|
|
154
|
-
|
|
155
|
-
# 6. Realça contraste (texto mais escuro, fundo mais claro)
|
|
156
|
-
combined = cv2.normalize(combined, None, 0, 255, cv2.NORM_MINMAX)
|
|
157
|
-
combined = cv2.convertScaleAbs(combined, alpha=1.5, beta=20)
|
|
158
|
-
|
|
159
|
-
# 7. Salva de volta em BGR
|
|
160
|
-
self.module_image = cv2.cvtColor(combined, cv2.COLOR_GRAY2BGR)
|
|
133
|
+
def set_gausian_blur(self, sigma: float = 0.5):
|
|
134
|
+
# Aplica um filtro Gaussiano para reduzir o ruído
|
|
135
|
+
_blurred: MatLike = cv2.GaussianBlur(self.module_image, (3, 3), sigma)
|
|
136
|
+
self.module_image = _blurred
|
|
161
137
|
|
|
162
138
|
def set_background_blur(self, sigma: float):
|
|
163
139
|
# 1. Converter para escala de cinza
|
|
@@ -175,42 +151,39 @@ class ImplementOpenCv(ABCImageObject):
|
|
|
175
151
|
# 5. Converter de volta para BGR
|
|
176
152
|
self.module_image = cv2.cvtColor(norm, cv2.COLOR_GRAY2BGR)
|
|
177
153
|
|
|
178
|
-
def
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
# 2. Detecta o texto (texto=branco, fundo=preto)
|
|
189
|
-
text_mask = cv2.adaptiveThreshold(
|
|
190
|
-
gray, 200,
|
|
154
|
+
def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
155
|
+
nparr = np.frombuffer(self.to_bytes().getvalue(), np.uint8)
|
|
156
|
+
img_opencv: cv2.typing.MatLike = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
|
|
157
|
+
# Aplica um filtro Gaussiano para reduzir o ruído
|
|
158
|
+
_blurred: cv2.typing.MatLike = cv2.GaussianBlur(img_opencv, (3, 3), sigma)
|
|
159
|
+
# Aplica binarização adaptativa (texto branco, fundo preto)
|
|
160
|
+
binary: cv2.typing.MatLike = cv2.adaptiveThreshold(
|
|
161
|
+
_blurred,
|
|
162
|
+
max_value,
|
|
191
163
|
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
|
192
|
-
cv2.
|
|
193
|
-
|
|
164
|
+
cv2.THRESH_BINARY,
|
|
165
|
+
11,
|
|
166
|
+
2
|
|
194
167
|
)
|
|
168
|
+
self.module_image = cv2.bitwise_not(binary)
|
|
195
169
|
|
|
196
|
-
|
|
197
|
-
|
|
170
|
+
def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
171
|
+
nparr = np.frombuffer(self.to_bytes().getvalue(), np.uint8)
|
|
172
|
+
img_opencv: cv2.typing.MatLike = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
|
|
198
173
|
|
|
199
|
-
#
|
|
200
|
-
|
|
201
|
-
background = np.full_like(gray, gray_bg_value, dtype=np.uint8)
|
|
174
|
+
# Aplica um filtro Gaussiano para reduzir o ruído
|
|
175
|
+
_blurred: cv2.typing.MatLike = cv2.GaussianBlur(img_opencv, (3, 3), sigma)
|
|
202
176
|
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
self.module_image = cv2.cvtColor(combined, cv2.COLOR_GRAY2BGR)
|
|
177
|
+
# Aplica binarização adaptativa (texto preto, fundo branco)
|
|
178
|
+
binary = cv2.adaptiveThreshold(
|
|
179
|
+
_blurred,
|
|
180
|
+
max_value,
|
|
181
|
+
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
|
182
|
+
cv2.THRESH_BINARY_INV, # Inverte o texto para ser branco inicialmente
|
|
183
|
+
11,
|
|
184
|
+
2
|
|
185
|
+
)
|
|
186
|
+
self.module_image = cv2.bitwise_not(binary)
|
|
214
187
|
|
|
215
188
|
def to_file(self, file: File):
|
|
216
189
|
#print(f'Exportando arquivo: {file.basename()}')
|
|
@@ -317,19 +290,18 @@ class ImplementPIL(ABCImageObject):
|
|
|
317
290
|
img.paste(blurred, mask=mask)
|
|
318
291
|
self.module_image = img
|
|
319
292
|
|
|
320
|
-
def
|
|
293
|
+
def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
321
294
|
img = self.module_image.convert("RGBA")
|
|
322
295
|
gray = img.convert("L")
|
|
323
296
|
mask = gray.point(lambda p: 255 if p > 200 else 0).convert("1")
|
|
324
|
-
black_bg = Image.new("RGBA", img.size, (0, 0, 0,
|
|
297
|
+
black_bg = Image.new("RGBA", img.size, (0, 0, 0, max_value))
|
|
325
298
|
img.paste(black_bg, mask=mask)
|
|
326
299
|
self.module_image = img
|
|
327
300
|
|
|
328
|
-
def
|
|
301
|
+
def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
329
302
|
if not MOD_IMG_PIL:
|
|
330
303
|
raise RuntimeError("PIL não está disponível.")
|
|
331
|
-
|
|
332
|
-
# 1. Converte para escala de cinza
|
|
304
|
+
# 1. Converte para escala de cinza
|
|
333
305
|
gray_img = self.module_image.convert("L")
|
|
334
306
|
|
|
335
307
|
# 2. Detecta o texto usando um threshold adaptativo (via NumPy)
|
|
@@ -343,11 +315,8 @@ class ImplementPIL(ABCImageObject):
|
|
|
343
315
|
|
|
344
316
|
# 3. Máscara do fundo (inverso)
|
|
345
317
|
mask_background = 255 - mask_text
|
|
346
|
-
|
|
347
318
|
# 4. Cria fundo cinza claro
|
|
348
|
-
|
|
349
|
-
background_np = np.full_like(img_np, gray_bg_value, dtype=np.uint8)
|
|
350
|
-
|
|
319
|
+
background_np = np.full_like(img_np, max_value, dtype=np.uint8)
|
|
351
320
|
# 5. Texto escuro
|
|
352
321
|
text_dark_value = 30
|
|
353
322
|
text_np = np.full_like(img_np, text_dark_value, dtype=np.uint8)
|
|
@@ -355,7 +324,6 @@ class ImplementPIL(ABCImageObject):
|
|
|
355
324
|
# 6. Combina texto escuro com fundo cinza
|
|
356
325
|
combined_np = ((text_np * (mask_text // 255)) +
|
|
357
326
|
(background_np * (mask_background // 255))).astype(np.uint8)
|
|
358
|
-
|
|
359
327
|
# 7. Converte de volta para PIL (BGR → RGB não é necessário, pois estamos em L)
|
|
360
328
|
self.module_image = Image.fromarray(combined_np).convert("RGB")
|
|
361
329
|
|
|
@@ -387,9 +355,48 @@ class ImplementPIL(ABCImageObject):
|
|
|
387
355
|
|
|
388
356
|
class ImageObject(object):
|
|
389
357
|
|
|
390
|
-
def __init__(
|
|
391
|
-
|
|
392
|
-
|
|
358
|
+
def __init__(
|
|
359
|
+
self, img: Union[ABCImageObject, bytes, BytesIO, str, File],
|
|
360
|
+
*, lib_image: LibImage = DEFAULT_LIB_IMAGE,
|
|
361
|
+
) -> None:
|
|
362
|
+
#
|
|
363
|
+
if isinstance(img, ABCImageObject):
|
|
364
|
+
self.img_adapter: ABCImageObject = img
|
|
365
|
+
elif isinstance(img, bytes):
|
|
366
|
+
if lib_image == LibImage.OPENCV:
|
|
367
|
+
self.img_adapter = ImplementOpenCv.create_from_bytes(img)
|
|
368
|
+
elif lib_image == LibImage.PIL:
|
|
369
|
+
self.img_adapter = ImplementPIL.create_from_bytes(img)
|
|
370
|
+
else:
|
|
371
|
+
raise ValueError('Use: PIL ou OPENCV')
|
|
372
|
+
elif isinstance(img, BytesIO):
|
|
373
|
+
img.seek(0)
|
|
374
|
+
if lib_image == LibImage.OPENCV:
|
|
375
|
+
self.img_adapter = ImplementOpenCv.create_from_bytes(img.getvalue())
|
|
376
|
+
elif lib_image == LibImage.PIL:
|
|
377
|
+
self.img_adapter = ImplementPIL.create_from_bytes(img.getvalue())
|
|
378
|
+
else:
|
|
379
|
+
raise ValueError('Use: PIL ou OPENCV')
|
|
380
|
+
elif isinstance(img, File):
|
|
381
|
+
if lib_image == LibImage.OPENCV:
|
|
382
|
+
self.img_adapter = ImplementOpenCv.create_from_file(img)
|
|
383
|
+
elif lib_image == LibImage.PIL:
|
|
384
|
+
self.img_adapter = ImplementPIL.create_from_file(img)
|
|
385
|
+
else:
|
|
386
|
+
raise ValueError('Use: PIL ou OPENCV')
|
|
387
|
+
elif isinstance(img, str):
|
|
388
|
+
if lib_image == LibImage.OPENCV:
|
|
389
|
+
self.img_adapter = ImplementOpenCv.create_from_file(File(img))
|
|
390
|
+
elif lib_image == LibImage.PIL:
|
|
391
|
+
self.img_adapter = ImplementPIL.create_from_file(File(img))
|
|
392
|
+
else:
|
|
393
|
+
raise ValueError('Use: PIL ou OPENCV')
|
|
394
|
+
else:
|
|
395
|
+
raise ValueError('Use: str, bytes, BytesIO, File, OPENCV or PIL')
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def name(self) -> str:
|
|
399
|
+
return self.img_adapter.name
|
|
393
400
|
|
|
394
401
|
def to_pil(self) -> Image.Image:
|
|
395
402
|
"""
|
|
@@ -425,17 +432,17 @@ class ImageObject(object):
|
|
|
425
432
|
def set_rotation(self, angle: RotationAngle = RotationAngle.ROTATION_90) -> None:
|
|
426
433
|
self.img_adapter.set_rotation(angle)
|
|
427
434
|
|
|
428
|
-
def set_gausian_blur(self, sigma: float = 0.
|
|
435
|
+
def set_gausian_blur(self, sigma: float = 0.5):
|
|
429
436
|
self.img_adapter.set_gausian_blur(sigma)
|
|
430
437
|
|
|
431
438
|
def set_background_blur(self, sigma: float):
|
|
432
439
|
self.img_adapter.set_background_blur(sigma)
|
|
433
440
|
|
|
434
|
-
def
|
|
435
|
-
self.img_adapter.
|
|
441
|
+
def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
|
|
442
|
+
self.img_adapter.set_threshold_black(max_value=max_value, sigma=sigma)
|
|
436
443
|
|
|
437
|
-
def
|
|
438
|
-
self.img_adapter.
|
|
444
|
+
def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5):
|
|
445
|
+
self.img_adapter.set_threshold_gray(max_value=max_value, sigma=sigma)
|
|
439
446
|
|
|
440
447
|
def to_file(self, file: File):
|
|
441
448
|
self.img_adapter.to_file(file)
|
|
@@ -474,7 +481,7 @@ class CollectionImages(object):
|
|
|
474
481
|
|
|
475
482
|
def __init__(self, images: list[ImageObject] = []) -> None:
|
|
476
483
|
"""
|
|
477
|
-
|
|
484
|
+
Gerir uma lista de Imagens
|
|
478
485
|
:type images: list[ImageObject]
|
|
479
486
|
"""
|
|
480
487
|
self.images: list[ImageObject] = images
|
|
@@ -550,7 +557,7 @@ class CollectionImages(object):
|
|
|
550
557
|
img.set_landscape()
|
|
551
558
|
if gaussian_filter:
|
|
552
559
|
img.set_gausian_blur()
|
|
553
|
-
img.
|
|
560
|
+
img.set_threshold_gray()
|
|
554
561
|
try:
|
|
555
562
|
img.to_file(file_path)
|
|
556
563
|
except Exception as e:
|
|
@@ -571,7 +578,7 @@ class CollectionImages(object):
|
|
|
571
578
|
f'Aplicando GausianBlur: [{_num + 1} de {max_num}]'
|
|
572
579
|
)
|
|
573
580
|
im.set_gausian_blur(sigma)
|
|
574
|
-
im.
|
|
581
|
+
im.set_threshold_gray()
|
|
575
582
|
print()
|
|
576
583
|
self.pbar.stop()
|
|
577
584
|
|
|
@@ -27,6 +27,9 @@ class SearchableTextPdf(object):
|
|
|
27
27
|
"""
|
|
28
28
|
def __init__(self, silent: bool = False):
|
|
29
29
|
self.silent = silent
|
|
30
|
+
# Cada elemento encontrado na busca de texto, será guardado nesse objeto para
|
|
31
|
+
# posterior consulta (self.elements), cada chave aqui deve ter uma lista de
|
|
32
|
+
# tamanho igual.
|
|
30
33
|
self.elements: dict[str, list[str]] = {
|
|
31
34
|
ColumnsTable.NUM_PAGE.value: [],
|
|
32
35
|
ColumnsTable.NUM_LINHA.value: [],
|
|
@@ -117,7 +120,12 @@ class SearchableTextPdf(object):
|
|
|
117
120
|
|
|
118
121
|
|
|
119
122
|
class ABCDocument(ABC):
|
|
120
|
-
|
|
123
|
+
"""
|
|
124
|
+
Classe molde para gerir os documentos, para operações como:
|
|
125
|
+
- leitura e escrita de arquivos pdf
|
|
126
|
+
- exportar páginas ou arquivos pdf
|
|
127
|
+
- permitir a leitura de arquivos ou bytes de pdf.
|
|
128
|
+
"""
|
|
121
129
|
def __init__(self, mod_doc_pdf: ModDocPdf, name: str):
|
|
122
130
|
self.mod_doc_pdf: ModDocPdf = mod_doc_pdf
|
|
123
131
|
self.lib_pdf: LibPDF = DEFAULT_LIB_PDF
|
|
@@ -169,7 +177,9 @@ class ABCDocument(ABC):
|
|
|
169
177
|
|
|
170
178
|
|
|
171
179
|
class ImplementDocumentFitz(ABCDocument):
|
|
172
|
-
|
|
180
|
+
"""
|
|
181
|
+
Implementação usando a biblioteca fitz.
|
|
182
|
+
"""
|
|
173
183
|
def __init__(self, mod_doc_pdf: ModDocPdf, name: str):
|
|
174
184
|
super().__init__(mod_doc_pdf, name)
|
|
175
185
|
self.mod_doc_pdf: ModDocPdf = mod_doc_pdf
|
|
@@ -255,6 +265,9 @@ class ImplementDocumentFitz(ABCDocument):
|
|
|
255
265
|
|
|
256
266
|
|
|
257
267
|
class ImplementDocumentPyPdf(ABCDocument):
|
|
268
|
+
"""
|
|
269
|
+
Implementação usando a biblioteca pypdf
|
|
270
|
+
"""
|
|
258
271
|
|
|
259
272
|
def __init__(self, mod_doc_pdf: PdfWriter, name: str):
|
|
260
273
|
super().__init__(mod_doc_pdf, name)
|
|
@@ -353,13 +366,20 @@ class ImplementDocumentPyPdf(ABCDocument):
|
|
|
353
366
|
|
|
354
367
|
|
|
355
368
|
class DocumentPdf(object):
|
|
369
|
+
"""
|
|
370
|
+
Gerir um documento PDF com uma implementação de fitz ou pypdf.
|
|
371
|
+
As operações são feitas pela classe encapsulada aqui.
|
|
372
|
+
"""
|
|
356
373
|
def __init__(
|
|
357
374
|
self,
|
|
358
|
-
document: Union[
|
|
375
|
+
document: Union[
|
|
376
|
+
str, File, PdfWriter, fitz.Document, BytesIO,
|
|
377
|
+
ImplementDocumentFitz, ImplementDocumentPyPdf,
|
|
378
|
+
],
|
|
359
379
|
*,
|
|
360
380
|
lib_pdf: LibPDF = LibPDF.FITZ
|
|
361
381
|
):
|
|
362
|
-
self.doc_pdf = None
|
|
382
|
+
self.doc_pdf: ABCDocument = None
|
|
363
383
|
self.lib_pdf: LibPDF = lib_pdf
|
|
364
384
|
if isinstance(document, str):
|
|
365
385
|
if lib_pdf == LibPDF.PYPDF:
|
|
@@ -415,7 +435,6 @@ class DocumentPdf(object):
|
|
|
415
435
|
"""
|
|
416
436
|
Retorna a primeira página do documento.
|
|
417
437
|
"""
|
|
418
|
-
# O índice da primeira página em PyPDF2 é 0.
|
|
419
438
|
return self.doc_pdf.get_first_page()
|
|
420
439
|
|
|
421
440
|
def get_last_page(self) -> PageDocumentPdf:
|
|
@@ -597,7 +616,9 @@ class DocumentPdf(object):
|
|
|
597
616
|
|
|
598
617
|
|
|
599
618
|
class CollectionPagePdf(object):
|
|
600
|
-
|
|
619
|
+
"""
|
|
620
|
+
Gerir uma coleção de páginas PDF.
|
|
621
|
+
"""
|
|
601
622
|
def __init__(self, pages: list[PageDocumentPdf] = list()) -> None:
|
|
602
623
|
self.pages: list[PageDocumentPdf] = pages
|
|
603
624
|
self.pbar: ProgressBarAdapter = ProgressBarAdapter()
|
|
@@ -622,6 +643,14 @@ class CollectionPagePdf(object):
|
|
|
622
643
|
def set_pbar(self, p: ProgressBarAdapter):
|
|
623
644
|
self.pbar = p
|
|
624
645
|
|
|
646
|
+
def set_land_scape(self):
|
|
647
|
+
for page in self.pages:
|
|
648
|
+
page.set_land_scape()
|
|
649
|
+
|
|
650
|
+
def set_rotation(self, rotation: int) -> None:
|
|
651
|
+
for page in self.pages:
|
|
652
|
+
page.set_rotation(rotation)
|
|
653
|
+
|
|
625
654
|
def add_page(self, page: PageDocumentPdf) -> None:
|
|
626
655
|
page.number_page = len(self.pages) + 1
|
|
627
656
|
self.pbar.update_text(f'Adicionando página PDF {page.number_page}')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|