Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,35 +1,72 @@
|
|
1 |
import fitz
|
2 |
import re
|
3 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
def extrair_exames_formatado(pdf_file):
|
6 |
if pdf_file is None:
|
7 |
-
return "Nenhum arquivo enviado."
|
8 |
|
9 |
texto = ""
|
10 |
with fitz.open(pdf_file.name) as doc:
|
11 |
for page in doc:
|
12 |
texto += page.get_text()
|
13 |
|
14 |
-
# Remove quebras de linha para facilitar os regex
|
15 |
texto = texto.replace('\n', ' ').replace('\r', ' ')
|
16 |
|
17 |
-
def buscar(padrao):
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
21 |
return None
|
22 |
|
23 |
def k_format(v):
|
24 |
try:
|
25 |
-
n = float(v
|
26 |
return f"{round(n / 1000, 1)}K" if n >= 1000 else str(n)
|
27 |
except:
|
28 |
return v
|
29 |
|
30 |
-
leuco = buscar(r"leuc[óo]citos
|
31 |
-
bastonetes = buscar(r"bastonetes
|
32 |
-
segmentados = buscar(r"segmentados
|
33 |
leuco_str = ""
|
34 |
if leuco:
|
35 |
leuco_str = f"LEUCO {k_format(leuco)}"
|
@@ -38,76 +75,62 @@ def extrair_exames_formatado(pdf_file):
|
|
38 |
if segmentados:
|
39 |
leuco_str += f" + {segmentados}% SS"
|
40 |
|
41 |
-
|
42 |
-
"
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
},
|
53 |
-
"
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
},
|
66 |
-
"
|
67 |
-
"HB": buscar(r"hemoglobina[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
68 |
-
"HT": buscar(r"hemat[óo]crito[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
69 |
-
"PLT": buscar(r"plaquetas[^:\d]{0,10}[:=]?\s*([\d.,]+)")
|
70 |
-
},
|
71 |
-
"🔵 Coagulação": {
|
72 |
-
"TAP": buscar(r"TP\s*[:=]?\s*([\d.,]+)\s*seg"), # Tempo de Protrombina
|
73 |
-
"INR": buscar(r"INR\s*[:=]?\s*([\d.,]+)"),
|
74 |
-
"TTP": buscar(r"TTPA.*?tempo[^:\d]{0,10}[:=]?\s*([\d.,]+)\s*seg"),
|
75 |
-
"RELAÇÃO": buscar(r"relaç[aã]o\s*paciente.*?[:=]?\s*([\d.,]+)"),
|
76 |
-
"D-DÍMERO": buscar(r"d[íi]mero[ -]?d.*?[:=]?\s*([<>]?\s*[\d.,]+)")
|
77 |
-
},
|
78 |
-
"🟢 Metabólico": {
|
79 |
-
"GLI": buscar(r"(glicose)[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
80 |
-
"LIP": buscar(r"lipase[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
81 |
-
"AMIL": buscar(r"amilase[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
82 |
-
"AC UR": buscar(r"[áa]cido[ \n]+[úu]rico[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
83 |
-
"LAC": buscar(r"lactato[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
84 |
-
"PCR": buscar(r"PCR[^:\d]{0,10}[:=]?\s*([\d.,]+)\s*mg")
|
85 |
-
},
|
86 |
-
"❤️ Cardíaco": {
|
87 |
-
"CPK": buscar(r"creatinofosfoquinase.*?[:=]?\s*([\d.,]+)"),
|
88 |
-
"CKMB": buscar(r"CKMB(?:\s*massa)?[^:\d]{0,10}[:=]?\s*([\d.,]+)"),
|
89 |
-
"TROPO": buscar(r"troponina.*?[:=]?\s*([<>]?\s*[\d.,]+)")
|
90 |
-
}
|
91 |
}
|
92 |
|
93 |
-
|
94 |
if leuco_str:
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
-
return
|
103 |
|
104 |
-
# Interface Gradio
|
105 |
with gr.Blocks() as demo:
|
106 |
-
gr.Markdown("## 🧪 Extrator de Exames PDF
|
107 |
pdf_file = gr.File(label="📄 PDF de exames", file_types=[".pdf"])
|
108 |
-
|
109 |
-
|
|
|
110 |
|
111 |
-
extract_button.click(fn=extrair_exames_formatado, inputs=pdf_file, outputs=output_text)
|
112 |
|
113 |
demo.launch()
|
|
|
1 |
import fitz
|
2 |
import re
|
3 |
import gradio as gr
|
4 |
+
import pandas as pd
|
5 |
+
import tempfile
|
6 |
+
|
7 |
+
# Faixas de referência básicas para classificação (valores em mg/dL, g/dL, etc.)
|
8 |
+
faixas = {
|
9 |
+
"HB": (12, 17),
|
10 |
+
"HT": (36, 50),
|
11 |
+
"GLI": (70, 99),
|
12 |
+
"UREIA": (10, 50),
|
13 |
+
"CR": (0.6, 1.3),
|
14 |
+
"K+": (3.5, 5.5),
|
15 |
+
"NA+": (135, 145),
|
16 |
+
"TGO": (0, 40),
|
17 |
+
"TGP": (0, 40),
|
18 |
+
"ALB": (3.5, 5.0),
|
19 |
+
"INR": (0.8, 1.2),
|
20 |
+
"TAP": (10, 14),
|
21 |
+
"TTP": (25, 35),
|
22 |
+
"LAC": (0.5, 2.2),
|
23 |
+
"PLT": (150000, 450000),
|
24 |
+
"LEUCO": (4000, 11000)
|
25 |
+
}
|
26 |
+
|
27 |
+
def classificar(nome, valor):
|
28 |
+
try:
|
29 |
+
val = float(valor.replace("K", "000").replace(">", "").replace("<", "").strip())
|
30 |
+
if nome in faixas:
|
31 |
+
min_v, max_v = faixas[nome]
|
32 |
+
if val < min_v:
|
33 |
+
return f"{valor} ↓"
|
34 |
+
elif val > max_v:
|
35 |
+
return f"{valor} ↑"
|
36 |
+
return valor
|
37 |
+
except:
|
38 |
+
return valor
|
39 |
|
40 |
def extrair_exames_formatado(pdf_file):
|
41 |
if pdf_file is None:
|
42 |
+
return "Nenhum arquivo enviado.", None
|
43 |
|
44 |
texto = ""
|
45 |
with fitz.open(pdf_file.name) as doc:
|
46 |
for page in doc:
|
47 |
texto += page.get_text()
|
48 |
|
|
|
49 |
texto = texto.replace('\n', ' ').replace('\r', ' ')
|
50 |
|
51 |
+
def buscar(padrao, excluir_protocolo=True):
|
52 |
+
matches = re.findall(padrao, texto, re.IGNORECASE)
|
53 |
+
for match in matches:
|
54 |
+
val = match.strip().replace(",", ".")
|
55 |
+
if excluir_protocolo and len(val.replace(".", "").replace(">", "").replace("<", "")) > 5:
|
56 |
+
continue # ignora IDs longos como 2500267046
|
57 |
+
return val
|
58 |
return None
|
59 |
|
60 |
def k_format(v):
|
61 |
try:
|
62 |
+
n = float(v)
|
63 |
return f"{round(n / 1000, 1)}K" if n >= 1000 else str(n)
|
64 |
except:
|
65 |
return v
|
66 |
|
67 |
+
leuco = buscar(r"leuc[óo]citos[^:\d]{0,10}[:=]?\s*(\d{3,5})")
|
68 |
+
bastonetes = buscar(r"bastonetes[^:\d]{0,10}[:=]?\s*(\d+)\s*%")
|
69 |
+
segmentados = buscar(r"segmentados[^:\d]{0,10}[:=]?\s*(\d+)\s*%")
|
70 |
leuco_str = ""
|
71 |
if leuco:
|
72 |
leuco_str = f"LEUCO {k_format(leuco)}"
|
|
|
75 |
if segmentados:
|
76 |
leuco_str += f" + {segmentados}% SS"
|
77 |
|
78 |
+
campos = {
|
79 |
+
"UREIA": r"ureia[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
80 |
+
"CR": r"creatinina[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
81 |
+
"K+": r"(?:pot[áa]ssio|k\+)[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
82 |
+
"NA+": r"(?:s[óo]dio|na\+)[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
83 |
+
"CL-": r"(?:cl[óo]ro)[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
84 |
+
"CAI": r"ioniz[áa]vel[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
85 |
+
"CA TOTAL": r"c[áa]lcio total[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
86 |
+
"MG++": r"magn[ée]sio[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
87 |
+
"FÓS": r"f[óo]sforo[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
88 |
+
"GLI": r"glicose[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
89 |
+
"HB": r"hemoglobina[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
90 |
+
"HT": r"hemat[óo]crito[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
91 |
+
"PLT": r"plaquetas[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
92 |
+
"INR": r"INR[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
93 |
+
"TAP": r"\bTP[^:\d]{0,10}[:=]?\s*([\d.,]+)\s*seg",
|
94 |
+
"TTP": r"TTPA[^:\d]{0,10}[:=]?\s*([\d.,]+)\s*seg",
|
95 |
+
"RELAÇÃO": r"relaç[aã]o.*?(?:paciente|a\/g)[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
96 |
+
"LAC": r"lactato[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
97 |
+
"TGO": r"\bTGO[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
98 |
+
"TGP": r"\bTGP[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
99 |
+
"ALB": r"albumina[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
100 |
+
"PCR": r"PCR[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
101 |
+
"CPK": r"creatinofosfoquinase.*?[:=]?\s*([\d.,]+)",
|
102 |
+
"CKMB": r"CKMB(?:\s*massa)?[^:\d]{0,10}[:=]?\s*([\d.,]+)",
|
103 |
+
"TROPO": r"troponina.*?[:=]?\s*([<>]?\s*[\d.,]+)",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
}
|
105 |
|
106 |
+
resultados = []
|
107 |
if leuco_str:
|
108 |
+
resultados.append(("LEUCO", leuco_str))
|
109 |
+
|
110 |
+
for rotulo, padrao in campos.items():
|
111 |
+
val = buscar(padrao)
|
112 |
+
if val:
|
113 |
+
val = classificar(rotulo, val)
|
114 |
+
resultados.append((rotulo, val))
|
115 |
+
else:
|
116 |
+
resultados.append((rotulo, "—"))
|
117 |
+
|
118 |
+
df = pd.DataFrame(resultados, columns=["Exame", "Valor"])
|
119 |
+
texto_final = "\n".join([f"{r[0]}: {r[1]}" for r in resultados])
|
120 |
+
|
121 |
+
# Exportação CSV temporária
|
122 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
123 |
+
df.to_csv(temp_file.name, index=False)
|
124 |
|
125 |
+
return texto_final, temp_file.name
|
126 |
|
|
|
127 |
with gr.Blocks() as demo:
|
128 |
+
gr.Markdown("## 🧪 Extrator Inteligente de Exames Laboratoriais - PDF para Diagnóstico")
|
129 |
pdf_file = gr.File(label="📄 PDF de exames", file_types=[".pdf"])
|
130 |
+
extract_button = gr.Button("🔍 Extrair Exames")
|
131 |
+
output_text = gr.Textbox(label="📋 Exames extraídos e classificados", lines=25)
|
132 |
+
download_button = gr.File(label="📥 Baixar CSV")
|
133 |
|
134 |
+
extract_button.click(fn=extrair_exames_formatado, inputs=pdf_file, outputs=[output_text, download_button])
|
135 |
|
136 |
demo.launch()
|