Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
-
#
|
|
|
2 |
|
3 |
import fitz
|
4 |
import re
|
@@ -9,58 +10,53 @@ import pytesseract
|
|
9 |
from PIL import Image, ImageEnhance, ImageFilter
|
10 |
import io
|
11 |
|
12 |
-
# Faixas de referência
|
13 |
faixas = {
|
14 |
-
"HB": (
|
15 |
"K+": (3.5, 5.5), "NA+": (135, 145), "UREIA": (10, 50), "CR": (0.6, 1.3),
|
16 |
"TGO": (0, 40), "TGP": (0, 40), "ALB": (3.5, 5.0), "INR": (0.8, 1.2),
|
17 |
"TAP": (10, 14), "TTP": (25, 35)
|
18 |
}
|
19 |
|
20 |
def classificar(nome, valor):
|
21 |
-
"""
|
22 |
-
Retorna valor com setas se fora da faixa.
|
23 |
-
"""
|
24 |
try:
|
25 |
-
raw = valor.replace("
|
26 |
val = float(raw)
|
27 |
if nome in faixas:
|
28 |
-
|
29 |
-
if val <
|
30 |
-
|
31 |
-
if val > max_v:
|
32 |
-
return f"{valor} ↑"
|
33 |
return valor
|
34 |
except:
|
35 |
return valor
|
36 |
|
37 |
-
|
38 |
-
|
39 |
img = img.convert('L')
|
40 |
img = ImageEnhance.Contrast(img).enhance(2)
|
41 |
-
|
42 |
-
return img
|
43 |
|
44 |
-
# Extrai texto nativo + OCR das páginas do PDF
|
45 |
def extrair_texto_pdf(pdf_file):
|
46 |
-
|
|
|
47 |
ocr_imgs = []
|
48 |
with fitz.open(pdf_file.name) as doc:
|
49 |
for page in doc:
|
50 |
-
|
51 |
pix = page.get_pixmap(dpi=300)
|
52 |
img = Image.open(io.BytesIO(pix.tobytes("png")))
|
53 |
ocr_imgs.append(melhorar_imagem(img))
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
# Padrões regex para extração de cada exame, incluindo EAS
|
62 |
exames = {
|
63 |
-
|
|
|
64 |
"B": r"bas[óo]filos.*?([\d.,]+)\s?%",
|
65 |
"SS": r"segmentados.*?([\d.,]+)\s?%",
|
66 |
"EOS": r"eosin[óo]filos.*?([\d.,]+)\s?%",
|
@@ -68,105 +64,108 @@ exames = {
|
|
68 |
"MONO": r"mon[óo]citos.*?([\d.,]+)\s?%",
|
69 |
"HB": r"hemoglobina.*?([\d.,]+)\s?g/dl",
|
70 |
"HT": r"hemat[óo]crito.*?([\d.,]+)\s?%",
|
71 |
-
"PLT": r"plaquetas.*?([\d.,]+)
|
|
|
72 |
"AMIL": r"amilase.*?([\d.,]+)\s?u/l",
|
|
|
|
|
|
|
73 |
"ÁC UR": r"[áa]cido ur[íi]co.*?([\d.,]+)\s?mg/dl",
|
74 |
"BT": r"bilirrubina total.*?([\d.,]+)\s?mg/dl",
|
75 |
"BD": r"bilirrubina direta.*?([\d.,]+)\s?mg/dl",
|
76 |
"BI": r"bilirrubina indireta.*?([\d.,]+)\s?mg/dl",
|
77 |
-
"CAI": r"c[áa]lcio ioniza(?:do)
|
78 |
"CA TOTAL":r"c[áa]lcio total.*?([\d.,]+)\s?mg/dl",
|
79 |
"CL-": r"cloro.*?([\d.,]+)\s?mmol/l",
|
80 |
-
"
|
81 |
-
"UREIA": r"ureia.*?([\d.,]+)\s?mg/dl",
|
82 |
-
"FAL": r"fosfatase alcalina.*?([\d.,]+)\s?u/l",
|
83 |
"FÓS": r"f[oó]sforo.*?([\d.,]+)\s?mg/dl",
|
|
|
|
|
|
|
|
|
|
|
84 |
"GGT": r"ggt.*?([\d.,]+)\s?u/l",
|
85 |
-
"
|
86 |
-
"LIP": r"lipase.*?([\d.,]+)\s?u/l",
|
87 |
-
"MG++": r"magn[eé]sio.*?([\d.,]+)\s?mg/dl",
|
88 |
-
"PCR": r"pcr.*?\bresultado\b\s*([\d]+,[\d]+)",
|
89 |
-
"K+": r"pot[áa]ssio.*?([\d.,]+)\s?mmol/l",
|
90 |
-
"NA+": r"s[óo]dio.*?([\d.,]+)\s?mmol/l", "PTN": r"prote[íi]na total.*?([\d.,]+)\s?g/dl",
|
91 |
"ALB": r"albumina.*?([\d.,]+)\s?g/dl",
|
|
|
92 |
"GLOB": r"globulina.*?([\d.,]+)\s?g/dl",
|
93 |
"RELAÇÃO": r"rela[cç][ãa]o\s+a\/g.*?([\d.,]+)",
|
94 |
-
|
95 |
-
"
|
96 |
-
"
|
97 |
-
"INR": r"I\s*N\s*R\s+([\d]+,[\d]+)",
|
98 |
"TTP": r"ttpa.*?([\d.,]+)\s?seg",
|
99 |
-
|
100 |
-
"
|
|
|
101 |
"CKMB": r"ck[- ]?mb.*?([\d.,]+)\s?u/l",
|
102 |
-
"CPK": r"cpk
|
103 |
-
"TROPO": r"troponina.*?([<>]?[\d.,]+)\s?ng/ml",
|
104 |
-
|
105 |
-
|
106 |
-
"
|
107 |
-
"
|
108 |
-
"
|
|
|
109 |
}
|
110 |
|
111 |
-
# Ordem de
|
112 |
ordem = [
|
113 |
"LEUCO","B","SS","EOS","LINF","MONO",
|
114 |
-
"HB","HT","PLT","AMIL","
|
115 |
-
"CAI","CA TOTAL","CL-","
|
116 |
-
"
|
117 |
-
"
|
118 |
-
"
|
119 |
-
"CKMB","CPK","TROPO"
|
120 |
]
|
121 |
|
122 |
def extrair_exames_formatado(pdf_file):
|
123 |
if not pdf_file:
|
124 |
return "Nenhum arquivo enviado.", None
|
125 |
-
|
126 |
-
|
|
|
127 |
resultados = {}
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
|
|
139 |
eas_chaves = ["LEUC ESTERASE","LEUCO EAS","HEMA EAS","BACTERIAS"]
|
140 |
partes_eas = [f"{k}: {resultados[k]}" for k in eas_chaves if k in resultados]
|
141 |
texto_eas = ""
|
142 |
if partes_eas:
|
143 |
-
texto_eas = "EAS
|
144 |
|
145 |
-
#
|
146 |
partes_main = [f"{r}: {resultados[r]}" for r in ordem if r in resultados]
|
147 |
texto_main = " / ".join(partes_main)
|
148 |
|
149 |
-
#
|
150 |
-
texto_final = "
|
151 |
|
152 |
-
#
|
153 |
-
df = pd.DataFrame(
|
154 |
-
[(k, resultados[k]) for k in resultados],
|
155 |
-
columns=["Exame","Valor"]
|
156 |
-
)
|
157 |
temp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
158 |
df.to_csv(temp.name, index=False)
|
159 |
|
160 |
return texto_final, temp.name
|
161 |
|
162 |
-
#
|
163 |
with gr.Blocks() as demo:
|
164 |
-
gr.Markdown("## 🧪 Extrator Avançado com OCR
|
165 |
-
|
166 |
btn = gr.Button("🔍 Extrair Exames")
|
167 |
-
out_txt = gr.Textbox(label="📋
|
168 |
-
dl = gr.File(label="📥 CSV")
|
169 |
-
btn.click(extrair_exames_formatado, inputs=
|
170 |
|
171 |
-
if __name__ ==
|
172 |
demo.launch()
|
|
|
|
1 |
+
# Instalações necessárias
|
2 |
+
!pip install pdfplumber gradio pandas pytesseract --quiet
|
3 |
|
4 |
import fitz
|
5 |
import re
|
|
|
10 |
from PIL import Image, ImageEnhance, ImageFilter
|
11 |
import io
|
12 |
|
13 |
+
# Faixas de referência para classificação
|
14 |
faixas = {
|
15 |
+
"HB": (11.5, 16.5), "HT": (36, 50), "LEUCO": (4000, 11000), "PLT": (150000, 450000),
|
16 |
"K+": (3.5, 5.5), "NA+": (135, 145), "UREIA": (10, 50), "CR": (0.6, 1.3),
|
17 |
"TGO": (0, 40), "TGP": (0, 40), "ALB": (3.5, 5.0), "INR": (0.8, 1.2),
|
18 |
"TAP": (10, 14), "TTP": (25, 35)
|
19 |
}
|
20 |
|
21 |
def classificar(nome, valor):
|
22 |
+
"""Adiciona setas se valor numérico estiver fora da faixa de referência."""
|
|
|
|
|
23 |
try:
|
24 |
+
raw = valor.replace(">", "").replace("<", "").strip()
|
25 |
val = float(raw)
|
26 |
if nome in faixas:
|
27 |
+
mn, mx = faixas[nome]
|
28 |
+
if val < mn: return f"{valor} ↓"
|
29 |
+
if val > mx: return f"{valor} ↑"
|
|
|
|
|
30 |
return valor
|
31 |
except:
|
32 |
return valor
|
33 |
|
34 |
+
def melhorar_imagem(img: Image.Image) -> Image.Image:
|
35 |
+
"""Aumenta contraste e nitidez para OCR."""
|
36 |
img = img.convert('L')
|
37 |
img = ImageEnhance.Contrast(img).enhance(2)
|
38 |
+
return img.filter(ImageFilter.SHARPEN)
|
|
|
39 |
|
|
|
40 |
def extrair_texto_pdf(pdf_file):
|
41 |
+
"""Extrai texto nativo e via OCR de cada página."""
|
42 |
+
texto_nativo = []
|
43 |
ocr_imgs = []
|
44 |
with fitz.open(pdf_file.name) as doc:
|
45 |
for page in doc:
|
46 |
+
texto_nativo.append(page.get_text())
|
47 |
pix = page.get_pixmap(dpi=300)
|
48 |
img = Image.open(io.BytesIO(pix.tobytes("png")))
|
49 |
ocr_imgs.append(melhorar_imagem(img))
|
50 |
+
tn = " ".join(texto_nativo)
|
51 |
+
tn = re.sub(r'\s+', ' ', tn)
|
52 |
+
tocr = " ".join(pytesseract.image_to_string(im) for im in ocr_imgs)
|
53 |
+
tocr = re.sub(r'\s+', ' ', tocr)
|
54 |
+
return tn, tocr
|
55 |
+
|
56 |
+
# Padrões regex (case-insensitive) para todos os exames, incluindo Troponina Qualitativa
|
|
|
57 |
exames = {
|
58 |
+
# Hemograma e diferenciais
|
59 |
+
"LEUCO": r"leuc[óo]citos.*?([\d.,]+)\s?/u?l",
|
60 |
"B": r"bas[óo]filos.*?([\d.,]+)\s?%",
|
61 |
"SS": r"segmentados.*?([\d.,]+)\s?%",
|
62 |
"EOS": r"eosin[óo]filos.*?([\d.,]+)\s?%",
|
|
|
64 |
"MONO": r"mon[óo]citos.*?([\d.,]+)\s?%",
|
65 |
"HB": r"hemoglobina.*?([\d.,]+)\s?g/dl",
|
66 |
"HT": r"hemat[óo]crito.*?([\d.,]+)\s?%",
|
67 |
+
"PLT": r"plaquetas.*?([\d.,]+).?/u?l",
|
68 |
+
# Bioquímica
|
69 |
"AMIL": r"amilase.*?([\d.,]+)\s?u/l",
|
70 |
+
"LIP": r"lipase.*?([\d.,]+)\s?u/l",
|
71 |
+
"GLI": r"glicose.*?([\d.,]+)\s?mg/dl",
|
72 |
+
"LACTATO": r"lactato.*?([\d.,]+)\s?mmol/l",
|
73 |
"ÁC UR": r"[áa]cido ur[íi]co.*?([\d.,]+)\s?mg/dl",
|
74 |
"BT": r"bilirrubina total.*?([\d.,]+)\s?mg/dl",
|
75 |
"BD": r"bilirrubina direta.*?([\d.,]+)\s?mg/dl",
|
76 |
"BI": r"bilirrubina indireta.*?([\d.,]+)\s?mg/dl",
|
77 |
+
"CAI": r"c[áa]lcio ioniza(?:do)?.*?([\d.,]+)\s?mmol/l",
|
78 |
"CA TOTAL":r"c[áa]lcio total.*?([\d.,]+)\s?mg/dl",
|
79 |
"CL-": r"cloro.*?([\d.,]+)\s?mmol/l",
|
80 |
+
"MG++": r"magn[ée]sio.*?([\d.,]+)\s?mg/dl",
|
|
|
|
|
81 |
"FÓS": r"f[oó]sforo.*?([\d.,]+)\s?mg/dl",
|
82 |
+
"UREIA": r"ureia.*?([\d.,]+)\s?mg/dl",
|
83 |
+
"CR": r"creatinina.*?([\d.,]+)\s?mg/dl",
|
84 |
+
# Hepática e proteínas
|
85 |
+
"TGO": r"tgo.*?([\d.,]+)\s?u/l",
|
86 |
+
"TGP": r"tgp.*?([\d.,]+)\s?u/l",
|
87 |
"GGT": r"ggt.*?([\d.,]+)\s?u/l",
|
88 |
+
"FAL": r"fosfatase alcalina.*?([\d.,]+)\s?u/l",
|
|
|
|
|
|
|
|
|
|
|
89 |
"ALB": r"albumina.*?([\d.,]+)\s?g/dl",
|
90 |
+
"PTN TOTAL":r"prote[ií]na total.*?([\d.,]+)\s?g/dl",
|
91 |
"GLOB": r"globulina.*?([\d.,]+)\s?g/dl",
|
92 |
"RELAÇÃO": r"rela[cç][ãa]o\s+a\/g.*?([\d.,]+)",
|
93 |
+
# Coagulação
|
94 |
+
"TAP": r"tempo de protrombina.*?resultado\s*([\d.,]+)",
|
95 |
+
"INR": r"inr\s*([\d.,]+)",
|
|
|
96 |
"TTP": r"ttpa.*?([\d.,]+)\s?seg",
|
97 |
+
# Inflamatório
|
98 |
+
"PCR": r"pcr.*?resultado\s*([\d.,]+)",
|
99 |
+
# Cardíacos
|
100 |
"CKMB": r"ck[- ]?mb.*?([\d.,]+)\s?u/l",
|
101 |
+
"CPK": r"cpk.*?resultado\s*([\d.,]+)",
|
102 |
+
"TROPO": r"troponina\s*(?!qual).*?([<>]?[\d.,]+)\s?ng/ml",
|
103 |
+
"TROPONINA QUAL": r"troponina qualitativa.*?resultado\s*([A-Za-z]+)",
|
104 |
+
# EAS (urina)
|
105 |
+
"LEUC ESTERASE": r"leuc[óo]cito esterase.*?([A-Za-z\+\-]+)",
|
106 |
+
"LEUCO EAS": r"leuc[óo]citos?.*?([\d]+\s*[-\/]\s*\d+)",
|
107 |
+
"HEMA EAS": r"hem[áa]cias?.*?([\d]+\s*[-\/]\s*\d+)",
|
108 |
+
"BACTERIAS": r"bact[ée]rias?.*?([A-Za-z]+)"
|
109 |
}
|
110 |
|
111 |
+
# Ordem preferencial de exibição (numéricos e qualitativos)
|
112 |
ordem = [
|
113 |
"LEUCO","B","SS","EOS","LINF","MONO",
|
114 |
+
"HB","HT","PLT","AMIL","LIP","GLI","LACTATO",
|
115 |
+
"ÁC UR","BT","BD","BI","CAI","CA TOTAL","CL-","MG++","FÓS","UREIA","CR",
|
116 |
+
"TGO","TGP","GGT","FAL","ALB","PTN TOTAL","GLOB","RELAÇÃO",
|
117 |
+
"TAP","INR","TTP","PCR","DIMERO D",
|
118 |
+
"CKMB","CPK","TROPO","TROPONINA QUAL"
|
|
|
119 |
]
|
120 |
|
121 |
def extrair_exames_formatado(pdf_file):
|
122 |
if not pdf_file:
|
123 |
return "Nenhum arquivo enviado.", None
|
124 |
+
# extrai texto
|
125 |
+
tn, tocr = extrair_texto_pdf(pdf_file)
|
126 |
+
textos = tn + " " + tocr
|
127 |
resultados = {}
|
128 |
+
|
129 |
+
# varre todos os padrões
|
130 |
+
for nome, pat in exames.items():
|
131 |
+
m = re.search(pat, textos, re.IGNORECASE)
|
132 |
+
if m:
|
133 |
+
val = m.group(1).strip().replace(",", ".")
|
134 |
+
# normaliza QUAL como uppercase
|
135 |
+
if nome == "TROPONINA QUAL":
|
136 |
+
val = val.upper()
|
137 |
+
resultados[nome] = classificar(nome, val)
|
138 |
+
|
139 |
+
# monta string de EAS
|
140 |
eas_chaves = ["LEUC ESTERASE","LEUCO EAS","HEMA EAS","BACTERIAS"]
|
141 |
partes_eas = [f"{k}: {resultados[k]}" for k in eas_chaves if k in resultados]
|
142 |
texto_eas = ""
|
143 |
if partes_eas:
|
144 |
+
texto_eas = "🟤 EAS (Urinálise) → " + " / ".join(partes_eas)
|
145 |
|
146 |
+
# monta string principal
|
147 |
partes_main = [f"{r}: {resultados[r]}" for r in ordem if r in resultados]
|
148 |
texto_main = " / ".join(partes_main)
|
149 |
|
150 |
+
# concatena só as partes não vazias
|
151 |
+
texto_final = "\n".join([t for t in (texto_eas, texto_main) if t])
|
152 |
|
153 |
+
# gera CSV
|
154 |
+
df = pd.DataFrame([[k, resultados[k]] for k in resultados], columns=["Exame","Valor"])
|
|
|
|
|
|
|
155 |
temp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
156 |
df.to_csv(temp.name, index=False)
|
157 |
|
158 |
return texto_final, temp.name
|
159 |
|
160 |
+
# interface Gradio
|
161 |
with gr.Blocks() as demo:
|
162 |
+
gr.Markdown("## 🧪 Extrator Avançado com OCR + EAS + Troponina Qualitativa")
|
163 |
+
pdf_input = gr.File(label="📄 PDF de exames", file_types=[".pdf"])
|
164 |
btn = gr.Button("🔍 Extrair Exames")
|
165 |
+
out_txt = gr.Textbox(label="📋 Resultados", lines=8)
|
166 |
+
dl = gr.File(label="📥 Baixar CSV")
|
167 |
+
btn.click(extrair_exames_formatado, inputs=pdf_input, outputs=[out_txt, dl])
|
168 |
|
169 |
+
if __name__ == "__main__":
|
170 |
demo.launch()
|
171 |
+
|