Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -6,52 +6,43 @@ import tempfile
|
|
6 |
import pytesseract
|
7 |
from PIL import Image, ImageEnhance, ImageFilter
|
8 |
import io
|
|
|
9 |
|
10 |
-
# 🎯 Faixas de referência (valores de referência mínimos e máximos)
|
11 |
faixas = {
|
12 |
-
"LEUCO": (4000, 11000),
|
13 |
-
"
|
14 |
-
"
|
15 |
-
"
|
16 |
-
"
|
17 |
-
"
|
18 |
-
"
|
19 |
-
"
|
20 |
-
"
|
21 |
-
"PCR": (0, 5), "K+": (3.5, 5.1), "NA+": (135, 145),
|
22 |
-
"PTN": (6.0, 8.3), "ALB": (3.5, 5.0), "GLOB": (2.3, 3.5),
|
23 |
-
"RELAÇÃO": (1.0, 2.2),
|
24 |
-
"TGO": (0, 40), "TGP": (0, 40),
|
25 |
-
"TAP": (10, 14), "INR": (0.8, 1.2), "TTP": (25, 35),
|
26 |
-
"DIMERO D": (0, 500), "LAC": (0.5, 2.2),
|
27 |
-
"CKMB": (0, 25), "CPK": (20, 200),
|
28 |
-
"TROPONINA": (0, 0.5)
|
29 |
}
|
30 |
|
31 |
def classificar(nome, valor):
|
32 |
try:
|
33 |
-
v = float(valor.replace("
|
34 |
if nome in faixas:
|
35 |
lo, hi = faixas[nome]
|
36 |
-
if v < lo:
|
37 |
-
|
38 |
-
if v > hi:
|
39 |
-
return f"{valor} ↑"
|
40 |
return valor
|
41 |
except:
|
42 |
return valor
|
43 |
|
44 |
-
# Ajustes para melhorar OCR
|
45 |
def melhorar_imagem(img: Image.Image) -> Image.Image:
|
46 |
img = img.convert("L")
|
47 |
img = ImageEnhance.Contrast(img).enhance(2)
|
48 |
return img.filter(ImageFilter.SHARPEN)
|
49 |
|
50 |
-
|
|
|
|
|
51 |
def extrair_texto_pdf(pdf_input):
|
52 |
if isinstance(pdf_input, dict):
|
53 |
pdf_path = pdf_input.get("name") or pdf_input.get("file_path")
|
54 |
-
elif hasattr(pdf_input, "name")
|
55 |
pdf_path = pdf_input.name
|
56 |
else:
|
57 |
pdf_path = str(pdf_input)
|
@@ -59,67 +50,57 @@ def extrair_texto_pdf(pdf_input):
|
|
59 |
texto_nativo, ocr_imgs = [], []
|
60 |
with fitz.open(pdf_path) as doc:
|
61 |
for page in doc:
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
|
|
|
|
|
|
66 |
tn = re.sub(r"\s+", " ", "".join(texto_nativo))
|
67 |
-
tocr =
|
|
|
|
|
|
|
|
|
68 |
return tn, tocr
|
69 |
|
70 |
-
# Padrões de extração com word boundaries e unidades obrigatórias
|
71 |
exames = {
|
72 |
"LEUCO": r"\bleuc[óo]citos\b.*?([\d.,]+)\s*/u?l",
|
73 |
-
"B":
|
74 |
-
"SS":
|
75 |
-
"EOS":
|
76 |
"LINF": r"\blinf[oó]citos\b.*?([\d.,]+)\s?%",
|
77 |
"MONO": r"\bmon[óo]citos\b.*?([\d.,]+)\s?%",
|
78 |
-
"HB":
|
79 |
-
"HT":
|
80 |
-
"PLT":
|
81 |
"AMIL": r"\bamilase\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
82 |
-
"BT":
|
83 |
-
"BD":
|
84 |
-
"BI":
|
85 |
-
"CR":
|
86 |
-
"UREIA":r"\bureia\b.*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
87 |
-
"FAL":
|
88 |
-
"GGT":
|
89 |
-
"TGO":
|
90 |
-
"TGP":
|
91 |
-
"GLI":
|
92 |
-
"LIP":
|
93 |
"MG++": r"\bmagn[eé]sio\b.*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
94 |
-
"TAP":
|
95 |
-
"INR":
|
96 |
-
"TTP":
|
97 |
"DIMERO D": r"\bd[ií]mero d\b.*?resultado[:\s]*([\d.,]+)",
|
98 |
-
"PCR":
|
99 |
-
"CKMB":
|
100 |
-
"CPK":
|
101 |
-
"TROPONINA": r"troponina(?! qualitativa).*?resultado[:\s]*([><\d.,]+)(?=\s*ng
|
102 |
-
"TROPONINA QUAL": r"troponina qualitativa.*?resultado[:\s]*(positivo|negativo)"
|
103 |
-
"PROTEINA UR": r"\bprote[ií]na\b.*?\b(ausente|positivo|negativo)",
|
104 |
-
"GLI UR": r"\bglicose\b.*?\b(ausente|positivo|negativo)",
|
105 |
-
"CETONAS UR": r"\bcorpos cet[oô]nicos\b.*?\b(ausente|positivo|negativo)",
|
106 |
-
"SANGUE UR": r"\bsangue\b.*?\b(ausente|positivo|negativo)",
|
107 |
-
"LEUC ESTERASE": r"\bleuc[óo]citos? esterase\b.*?\b(ausente|positivo|negativo)",
|
108 |
-
"NITRITO UR": r"\bnitrito\b.*?\b(ausente|positivo|negativo)",
|
109 |
-
"LEUCO EAS": r"\bleuc[óo]citos?\b\s*([\d]+[-\/–][\d]+)",
|
110 |
-
"HEMA EAS": r"\bhem[áa]cias?\b\s*([\d]+[-\/–][\d]+)",
|
111 |
-
"BACTERIAS UR": r"\bbact[ée]rias?\b.*?\b(raras|ausentes|positivas|negativas)"
|
112 |
}
|
|
|
113 |
|
114 |
-
|
115 |
-
ordem = [
|
116 |
-
"LEUCO","B","SS","EOS","LINF","MONO",
|
117 |
-
"HB","HT","PLT","AMIL","BT","BD","BI",
|
118 |
-
"CR","UREIA","FAL","GGT","TGO","TGP","GLI","LIP","MG++",
|
119 |
-
"PCR","CKMB","CPK","TROPONINA","TROPONINA QUAL",
|
120 |
-
"TAP","INR","TTP","DIMERO D",
|
121 |
-
"PROTEINA UR","GLI UR","CETONAS UR","SANGUE UR","LEUC ESTERASE","NITRITO UR","LEUCO EAS","HEMA EAS","BACTERIAS UR"
|
122 |
-
]
|
123 |
|
124 |
def extrair_exames_formatado(pdf_file):
|
125 |
if not pdf_file:
|
@@ -127,8 +108,8 @@ def extrair_exames_formatado(pdf_file):
|
|
127 |
tn, tocr = extrair_texto_pdf(pdf_file)
|
128 |
texto = (tn + " " + tocr).lower()
|
129 |
resultados = {}
|
130 |
-
for nome, pat in
|
131 |
-
m =
|
132 |
if not m:
|
133 |
continue
|
134 |
raw = m.group(1).strip().upper()
|
@@ -137,22 +118,16 @@ def extrair_exames_formatado(pdf_file):
|
|
137 |
else:
|
138 |
resultados[nome] = classificar(nome, raw.replace(",", "."))
|
139 |
|
140 |
-
|
141 |
-
|
142 |
-
line_eas = f"🟤 EAS → {' / '.join(eas_fields)}" if eas_fields else ""
|
143 |
-
line_main = ' / '.join(main_fields)
|
144 |
-
final = '\n'.join([l for l in (line_eas, line_main) if l])
|
145 |
-
|
146 |
-
# Gera CSV
|
147 |
df = pd.DataFrame([[k, resultados[k]] for k in resultados], columns=["Exame", "Valor"])
|
148 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
149 |
df.to_csv(tmp.name, index=False)
|
150 |
return final, tmp.name
|
151 |
|
152 |
-
# Interface Gradio
|
153 |
demo = gr.Blocks()
|
154 |
with demo:
|
155 |
-
gr.Markdown("## 🧪 Extrator
|
156 |
pdf_input = gr.File(file_types=[".pdf"], label="📄 PDF de exames")
|
157 |
btn = gr.Button("🔍 Extrair")
|
158 |
out_txt = gr.Textbox(lines=15, label="📋 Resultados")
|
|
|
6 |
import pytesseract
|
7 |
from PIL import Image, ImageEnhance, ImageFilter
|
8 |
import io
|
9 |
+
from concurrent.futures import ThreadPoolExecutor
|
10 |
|
|
|
11 |
faixas = {
|
12 |
+
"LEUCO": (4000, 11000), "B": (0, 1), "SS": (45, 59), "EOS": (1, 6),
|
13 |
+
"LINF": (30, 50), "MONO": (1, 8), "HB": (12, 17), "HT": (36, 50), "PLT": (150, 450),
|
14 |
+
"AMIL": (25, 125), "ÁC UR": (3.5, 7.2), "BT": (0.3, 1.2), "BD": (0.0, 0.3), "BI": (0.1, 0.8),
|
15 |
+
"CAI": (1.1, 1.3), "CA TOTAL": (8.5, 10.2), "CL-": (98, 107), "CR": (0.6, 1.3), "UREIA": (17, 49),
|
16 |
+
"FAL": (44, 147), "FÓS": (2.5, 4.5), "GGT": (8, 61), "GLI": (70, 99), "LIP": (10, 140),
|
17 |
+
"MG++": (1.7, 2.2), "PCR": (0, 5), "K+": (3.5, 5.1), "NA+": (135, 145), "PTN": (6.0, 8.3),
|
18 |
+
"ALB": (3.5, 5.0), "GLOB": (2.3, 3.5), "RELAÇÃO": (1.0, 2.2), "TGO": (0, 40), "TGP": (0, 40),
|
19 |
+
"TAP": (10, 14), "INR": (0.8, 1.2), "TTP": (25, 35), "DIMERO D": (0, 500), "LAC": (0.5, 2.2),
|
20 |
+
"CKMB": (0, 25), "CPK": (20, 200), "TROPONINA": (0, 0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
}
|
22 |
|
23 |
def classificar(nome, valor):
|
24 |
try:
|
25 |
+
v = float(valor.replace("<", "").replace(">", "").strip())
|
26 |
if nome in faixas:
|
27 |
lo, hi = faixas[nome]
|
28 |
+
if v < lo: return f"{valor} ↓"
|
29 |
+
if v > hi: return f"{valor} ↑"
|
|
|
|
|
30 |
return valor
|
31 |
except:
|
32 |
return valor
|
33 |
|
|
|
34 |
def melhorar_imagem(img: Image.Image) -> Image.Image:
|
35 |
img = img.convert("L")
|
36 |
img = ImageEnhance.Contrast(img).enhance(2)
|
37 |
return img.filter(ImageFilter.SHARPEN)
|
38 |
|
39 |
+
def ocr_image(img):
|
40 |
+
return pytesseract.image_to_string(img)
|
41 |
+
|
42 |
def extrair_texto_pdf(pdf_input):
|
43 |
if isinstance(pdf_input, dict):
|
44 |
pdf_path = pdf_input.get("name") or pdf_input.get("file_path")
|
45 |
+
elif hasattr(pdf_input, "name"):
|
46 |
pdf_path = pdf_input.name
|
47 |
else:
|
48 |
pdf_path = str(pdf_input)
|
|
|
50 |
texto_nativo, ocr_imgs = [], []
|
51 |
with fitz.open(pdf_path) as doc:
|
52 |
for page in doc:
|
53 |
+
t = page.get_text()
|
54 |
+
texto_nativo.append(t)
|
55 |
+
if not t.strip():
|
56 |
+
pix = page.get_pixmap(dpi=200)
|
57 |
+
img = Image.open(io.BytesIO(pix.tobytes("png")))
|
58 |
+
ocr_imgs.append(melhorar_imagem(img))
|
59 |
+
|
60 |
tn = re.sub(r"\s+", " ", "".join(texto_nativo))
|
61 |
+
tocr = ""
|
62 |
+
if ocr_imgs:
|
63 |
+
with ThreadPoolExecutor() as executor:
|
64 |
+
tocr = " ".join(executor.map(ocr_image, ocr_imgs))
|
65 |
+
tocr = re.sub(r"\s+", " ", tocr)
|
66 |
return tn, tocr
|
67 |
|
|
|
68 |
exames = {
|
69 |
"LEUCO": r"\bleuc[óo]citos\b.*?([\d.,]+)\s*/u?l",
|
70 |
+
"B": r"\bbastonetes\b.*?([\d.,]+)\s?%",
|
71 |
+
"SS": r"\bsegmentados\b.*?([\d.,]+)\s?%",
|
72 |
+
"EOS": r"\beosin[óo]filos\b.*?([\d.,]+)\s?%",
|
73 |
"LINF": r"\blinf[oó]citos\b.*?([\d.,]+)\s?%",
|
74 |
"MONO": r"\bmon[óo]citos\b.*?([\d.,]+)\s?%",
|
75 |
+
"HB": r"\bhemoglobina\b.*?([\d.,]+)\s?g/dl",
|
76 |
+
"HT": r"\bhemat[óo]crito\b.*?([\d.,]+)\s?%",
|
77 |
+
"PLT": r"\bplaquetas\b.*?([\d.,]+)\s*/u?l",
|
78 |
"AMIL": r"\bamilase\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
79 |
+
"BT": r"\bbilirrubina total\b.*?([\d.,]+)\s?mg/dl",
|
80 |
+
"BD": r"\bbilirrubina direta\b.*?([\d.,]+)\s?mg/dl",
|
81 |
+
"BI": r"\bbilirrubina indireta\b.*?([\d.,]+)\s?mg/dl",
|
82 |
+
"CR": r"\bcreatinina\b.*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
83 |
+
"UREIA": r"\bureia\b.*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
84 |
+
"FAL": r"\bfosfatase alcalina\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
85 |
+
"GGT": r"\bggt\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
86 |
+
"TGO": r"\btgo\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
87 |
+
"TGP": r"\btgp\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
88 |
+
"GLI": r"\bglicose\b(?! qualitativa).*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
89 |
+
"LIP": r"\blipase\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
90 |
"MG++": r"\bmagn[eé]sio\b.*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
91 |
+
"TAP": r"\btempo de protrombina\b.*?resultado[:\s]*([\d.,]+)",
|
92 |
+
"INR": r"\binr\b.*?([\d.,]+)",
|
93 |
+
"TTP": r"\bttpa\b.*?resultado[:\s]*([\d.,]+)",
|
94 |
"DIMERO D": r"\bd[ií]mero d\b.*?resultado[:\s]*([\d.,]+)",
|
95 |
+
"PCR": r"\bpcr\b.*?resultado[:\s]*([\d.,]+)\s?mg/dl",
|
96 |
+
"CKMB": r"\bck[- ]?mb\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
97 |
+
"CPK": r"\bcpk\b.*?resultado[:\s]*([\d.,]+)\s?u/l",
|
98 |
+
"TROPONINA": r"troponina(?! qualitativa).*?resultado[:\s]*([><\d.,]+)(?=\s*ng/m[lL])",
|
99 |
+
"TROPONINA QUAL": r"troponina qualitativa.*?resultado[:\s]*(positivo|negativo)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
}
|
101 |
+
regex_compilado = {k: re.compile(v, re.IGNORECASE) for k, v in exames.items()}
|
102 |
|
103 |
+
ordem = list(exames.keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
|
105 |
def extrair_exames_formatado(pdf_file):
|
106 |
if not pdf_file:
|
|
|
108 |
tn, tocr = extrair_texto_pdf(pdf_file)
|
109 |
texto = (tn + " " + tocr).lower()
|
110 |
resultados = {}
|
111 |
+
for nome, pat in regex_compilado.items():
|
112 |
+
m = pat.search(texto)
|
113 |
if not m:
|
114 |
continue
|
115 |
raw = m.group(1).strip().upper()
|
|
|
118 |
else:
|
119 |
resultados[nome] = classificar(nome, raw.replace(",", "."))
|
120 |
|
121 |
+
linhas = [f"{nome}: {resultados[nome]}" for nome in ordem if nome in resultados]
|
122 |
+
final = " / ".join(linhas)
|
|
|
|
|
|
|
|
|
|
|
123 |
df = pd.DataFrame([[k, resultados[k]] for k in resultados], columns=["Exame", "Valor"])
|
124 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
125 |
df.to_csv(tmp.name, index=False)
|
126 |
return final, tmp.name
|
127 |
|
|
|
128 |
demo = gr.Blocks()
|
129 |
with demo:
|
130 |
+
gr.Markdown("## 🧪 Extrator Rápido com OCR seletivo")
|
131 |
pdf_input = gr.File(file_types=[".pdf"], label="📄 PDF de exames")
|
132 |
btn = gr.Button("🔍 Extrair")
|
133 |
out_txt = gr.Textbox(lines=15, label="📋 Resultados")
|