Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,13 +5,11 @@ from PIL import Image
|
|
5 |
from dataclasses import dataclass
|
6 |
from typing import Tuple, Dict, List
|
7 |
import torch
|
8 |
-
import torch.nn.functional as F
|
9 |
import torchvision.transforms as transforms
|
10 |
import logging
|
11 |
from datetime import datetime
|
12 |
-
import json
|
13 |
|
14 |
-
# Configuração
|
15 |
logging.basicConfig(
|
16 |
level=logging.INFO,
|
17 |
format='%(asctime)s - %(levelname)s - %(message)s'
|
@@ -19,115 +17,14 @@ logging.basicConfig(
|
|
19 |
|
20 |
@dataclass
|
21 |
class IrisZone:
|
22 |
-
"""
|
23 |
name: str
|
24 |
inner_ratio: float
|
25 |
outer_ratio: float
|
26 |
color: Tuple[int, int, int]
|
27 |
description: str
|
28 |
-
|
29 |
-
|
30 |
-
def __post_init__(self):
|
31 |
-
if not 0 <= self.inner_ratio <= 1:
|
32 |
-
raise ValueError("inner_ratio deve estar entre 0 e 1")
|
33 |
-
if not 0 <= self.outer_ratio <= 1:
|
34 |
-
raise ValueError("outer_ratio deve estar entre 0 e 1")
|
35 |
-
if self.inner_ratio >= self.outer_ratio:
|
36 |
-
raise ValueError("inner_ratio deve ser menor que outer_ratio")
|
37 |
-
|
38 |
-
class IrisAnalysis:
|
39 |
-
"""Classe para análise e interpretação dos resultados"""
|
40 |
-
|
41 |
-
@staticmethod
|
42 |
-
def get_zone_interpretation(zone_name: str, intensity: float, variation: float) -> Dict:
|
43 |
-
"""Interpretação detalhada para cada zona"""
|
44 |
-
base_interpretations = {
|
45 |
-
"Zona Cerebral/Neural": {
|
46 |
-
"aspectos": "Sistema nervoso central e periférico",
|
47 |
-
"sistemas": ["Cérebro", "Medula espinhal", "Nervos"],
|
48 |
-
"baixa": {
|
49 |
-
"indicacao": "Possível fadiga neural",
|
50 |
-
"sugestoes": [
|
51 |
-
"Considerar avaliação do sono",
|
52 |
-
"Verificar níveis de estresse",
|
53 |
-
"Avaliar demanda cognitiva"
|
54 |
-
]
|
55 |
-
},
|
56 |
-
"média": {
|
57 |
-
"indicacao": "Condição neural moderada",
|
58 |
-
"sugestoes": [
|
59 |
-
"Manter boa higiene do sono",
|
60 |
-
"Praticar atividades mentais"
|
61 |
-
]
|
62 |
-
},
|
63 |
-
"alta": {
|
64 |
-
"indicacao": "Boa vitalidade neural",
|
65 |
-
"sugestoes": [
|
66 |
-
"Manter práticas saudáveis",
|
67 |
-
"Continuar estimulação cognitiva"
|
68 |
-
]
|
69 |
-
}
|
70 |
-
},
|
71 |
-
"Zona Digestiva": {
|
72 |
-
"aspectos": "Sistema digestivo completo",
|
73 |
-
"sistemas": ["Estômago", "Intestinos", "Fígado", "Pâncreas"],
|
74 |
-
"baixa": {
|
75 |
-
"indicacao": "Possível sensibilidade digestiva",
|
76 |
-
"sugestoes": [
|
77 |
-
"Avaliar hábitos alimentares",
|
78 |
-
"Considerar diário alimentar",
|
79 |
-
"Observar reações a alimentos"
|
80 |
-
]
|
81 |
-
},
|
82 |
-
"média": {
|
83 |
-
"indicacao": "Sistema digestivo em equilíbrio moderado",
|
84 |
-
"sugestoes": [
|
85 |
-
"Manter alimentação balanceada",
|
86 |
-
"Observar horários das refeições"
|
87 |
-
]
|
88 |
-
},
|
89 |
-
"alta": {
|
90 |
-
"indicacao": "Boa condição digestiva",
|
91 |
-
"sugestoes": [
|
92 |
-
"Manter dieta equilibrada",
|
93 |
-
"Continuar bons hábitos"
|
94 |
-
]
|
95 |
-
}
|
96 |
-
},
|
97 |
-
# [Definições similares para outras zonas...]
|
98 |
-
}
|
99 |
-
|
100 |
-
# Determinar nível baseado na intensidade
|
101 |
-
if intensity < 85:
|
102 |
-
nivel = "baixa"
|
103 |
-
confianca = "reduzida" if variation > 30 else "moderada"
|
104 |
-
elif intensity < 170:
|
105 |
-
nivel = "média"
|
106 |
-
confianca = "moderada" if variation > 20 else "alta"
|
107 |
-
else:
|
108 |
-
nivel = "alta"
|
109 |
-
confianca = "alta" if variation < 15 else "moderada"
|
110 |
-
|
111 |
-
zone_info = base_interpretations.get(zone_name, {})
|
112 |
-
|
113 |
-
return {
|
114 |
-
"nome": zone_name,
|
115 |
-
"aspectos_analisados": zone_info.get("aspectos", ""),
|
116 |
-
"sistemas_relacionados": zone_info.get("sistemas", []),
|
117 |
-
"interpretacao": zone_info.get(nivel, {}).get("indicacao", "Sem interpretação disponível"),
|
118 |
-
"sugestoes": zone_info.get(nivel, {}).get("sugestoes", []),
|
119 |
-
"metricas": {
|
120 |
-
"intensidade": intensity,
|
121 |
-
"variacao": variation,
|
122 |
-
"nivel_geral": nivel,
|
123 |
-
"confianca_analise": confianca,
|
124 |
-
},
|
125 |
-
"indicadores": {
|
126 |
-
"intensidade_valor": f"{intensity:.1f}/255",
|
127 |
-
"variacao_valor": f"{variation:.1f}%",
|
128 |
-
"homogeneidade": "Baixa" if variation > 30 else "Média" if variation > 15 else "Alta"
|
129 |
-
}
|
130 |
-
}
|
131 |
|
132 |
class IrisAnalyzer:
|
133 |
"""Classe principal para análise da íris"""
|
@@ -139,15 +36,167 @@ class IrisAnalyzer:
|
|
139 |
outer_ratio=1.0,
|
140 |
color=(255, 0, 0),
|
141 |
description="Sistema nervoso central e periférico",
|
142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
),
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
]
|
146 |
|
147 |
def preprocess_image(self, img: np.ndarray) -> np.ndarray:
|
148 |
-
"""Pré-
|
149 |
try:
|
150 |
-
# Converter para LAB
|
151 |
lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB)
|
152 |
l, a, b = cv2.split(lab)
|
153 |
|
@@ -168,13 +217,12 @@ class IrisAnalyzer:
|
|
168 |
return img
|
169 |
|
170 |
def detect_pupil(self, img: np.ndarray) -> Tuple[int, int, int]:
|
171 |
-
"""
|
172 |
try:
|
173 |
-
# Pré-processar imagem
|
174 |
processed = self.preprocess_image(img)
|
175 |
gray = cv2.cvtColor(processed, cv2.COLOR_RGB2GRAY)
|
176 |
|
177 |
-
#
|
178 |
blur = cv2.GaussianBlur(gray, (5,5), 0)
|
179 |
_, thresh = cv2.threshold(blur, 30, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
180 |
|
@@ -189,7 +237,7 @@ class IrisAnalyzer:
|
|
189 |
return None
|
190 |
|
191 |
# Encontrar o contorno mais circular
|
192 |
-
|
193 |
best_contour = None
|
194 |
|
195 |
for contour in contours:
|
@@ -197,16 +245,15 @@ class IrisAnalyzer:
|
|
197 |
perimeter = cv2.arcLength(contour, True)
|
198 |
if perimeter == 0:
|
199 |
continue
|
200 |
-
|
201 |
-
circularity = 4 * np.pi * area / (perimeter * perimeter)
|
202 |
|
203 |
-
|
204 |
-
|
|
|
205 |
best_contour = contour
|
206 |
|
207 |
if best_contour is None:
|
208 |
return None
|
209 |
-
|
210 |
(x, y), radius = cv2.minEnclosingCircle(best_contour)
|
211 |
return (int(x), int(y), int(radius))
|
212 |
|
@@ -214,8 +261,8 @@ class IrisAnalyzer:
|
|
214 |
logging.error(f"Erro na detecção da pupila: {str(e)}")
|
215 |
return None
|
216 |
|
217 |
-
def analyze_zone(self, img: np.ndarray, mask: np.ndarray,
|
218 |
-
"""
|
219 |
try:
|
220 |
# Extrair características
|
221 |
mean_color = cv2.mean(img, mask=mask)
|
@@ -227,86 +274,94 @@ class IrisAnalyzer:
|
|
227 |
mean_intensity = np.mean(pixels)
|
228 |
std_dev = np.std(pixels)
|
229 |
|
230 |
-
#
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
232 |
|
233 |
-
#
|
234 |
-
|
235 |
-
zone_name, mean_intensity, std_dev
|
236 |
-
)
|
237 |
|
238 |
-
#
|
239 |
-
analysis
|
240 |
-
"
|
241 |
-
"
|
242 |
-
"
|
243 |
-
"
|
244 |
-
"
|
245 |
-
"
|
246 |
-
"
|
247 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
}
|
249 |
}
|
250 |
|
251 |
return analysis
|
252 |
|
253 |
-
return {"erro": "Zona sem pixels válidos
|
254 |
|
255 |
except Exception as e:
|
256 |
-
logging.error(f"Erro na análise da zona {
|
257 |
return {"erro": str(e)}
|
258 |
|
259 |
def analyze_iris(self, img: np.ndarray) -> Tuple[np.ndarray, Dict]:
|
260 |
-
"""
|
261 |
try:
|
262 |
output_img = img.copy()
|
263 |
-
|
264 |
"meta": {
|
265 |
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
266 |
-
"
|
267 |
},
|
268 |
-
"resumo_geral": {},
|
269 |
"zonas": {},
|
270 |
-
"
|
271 |
}
|
272 |
|
273 |
# Detectar pupila
|
274 |
pupil = self.detect_pupil(img)
|
275 |
if pupil is None:
|
276 |
return img, {"erro": "Não foi possível detectar a pupila"}
|
277 |
-
|
278 |
x, y, pupil_radius = pupil
|
279 |
iris_radius = pupil_radius * 4
|
280 |
|
281 |
-
# Desenhar
|
282 |
cv2.circle(output_img, (x, y), pupil_radius, (0, 0, 0), 2)
|
283 |
|
284 |
# Analisar cada zona
|
285 |
-
|
286 |
-
|
287 |
|
288 |
for zone in self.zones:
|
289 |
inner_r = int(iris_radius * zone.inner_ratio)
|
290 |
outer_r = int(iris_radius * zone.outer_ratio)
|
291 |
|
292 |
-
# Criar máscara
|
293 |
mask = np.zeros(img.shape[:2], dtype=np.uint8)
|
294 |
cv2.circle(mask, (x, y), outer_r, 255, -1)
|
295 |
cv2.circle(mask, (x, y), inner_r, 0, -1)
|
296 |
|
297 |
-
# Desenhar
|
298 |
cv2.circle(output_img, (x, y), outer_r, zone.color, 2)
|
299 |
|
300 |
# Analisar zona
|
301 |
-
analysis = self.analyze_zone(img, mask, zone
|
302 |
-
|
303 |
|
304 |
-
# Calcular score geral
|
305 |
if "metricas" in analysis:
|
306 |
-
if analysis["metricas"]["
|
307 |
-
|
308 |
-
elif analysis["metricas"]["
|
309 |
-
|
|
|
310 |
|
311 |
# Adicionar texto
|
312 |
text_x = x - iris_radius
|
@@ -314,142 +369,235 @@ class IrisAnalyzer:
|
|
314 |
cv2.putText(output_img, zone.name, (text_x, text_y),
|
315 |
cv2.FONT_HERSHEY_SIMPLEX, 0.5, zone.color, 1)
|
316 |
|
317 |
-
# Calcular
|
318 |
-
|
319 |
-
|
320 |
-
"
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
|
|
|
|
325 |
|
326 |
-
return output_img,
|
327 |
|
328 |
except Exception as e:
|
329 |
logging.error(f"Erro na análise da íris: {str(e)}")
|
330 |
return img, {"erro": str(e)}
|
331 |
|
332 |
-
def
|
333 |
-
"""Formata
|
334 |
-
if "erro" in
|
335 |
-
return f"Erro na análise: {
|
336 |
-
|
337 |
-
formatted = "# Relatório de Análise da Íris\n\n"
|
338 |
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
|
|
|
|
|
|
344 |
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
for zone_name, analysis in results["zonas"].items():
|
349 |
-
formatted += f"### {zone_name}\n"
|
350 |
-
formatted += f"- Aspectos Analisados: {analysis.get('aspectos_analisados', 'N/A')}\n"
|
351 |
-
formatted += f"- Interpretação: {analysis.get('interpretacao', 'N/A')}\n"
|
352 |
-
|
353 |
-
if "sugestoes" in analysis:
|
354 |
-
formatted += "- Sugestões:\n"
|
355 |
-
for sugestao in analysis["sugestoes"]:
|
356 |
-
formatted += f" * {sugestao}\n"
|
357 |
-
|
358 |
-
if "metricas" in analysis:
|
359 |
-
formatted += f"- Nível Geral: {analysis['metricas']['nivel_geral']}\n"
|
360 |
-
formatted += f"- Confiança da Análise: {analysis['metricas']['confianca_analise']}\n"
|
361 |
-
|
362 |
-
formatted += "\n"
|
363 |
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
|
371 |
def process_image(img):
|
372 |
-
"""
|
373 |
if img is None:
|
374 |
-
return None, "
|
375 |
-
|
376 |
-
analyzer = IrisAnalyzer()
|
377 |
-
output_img, results = analyzer.analyze_iris(np.array(img))
|
378 |
-
formatted_results = format_results(results)
|
379 |
|
380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
381 |
|
382 |
# Interface Gradio
|
383 |
with gr.Blocks(theme=gr.themes.Soft()) as iface:
|
384 |
gr.Markdown("""
|
385 |
# 🔍 Analisador Avançado de Íris
|
386 |
-
|
387 |
|
388 |
-
⚠️
|
389 |
""")
|
390 |
|
391 |
-
with gr.
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
404 |
gr.Markdown("""
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
410 |
""")
|
411 |
-
|
412 |
-
with gr.Column(scale=1):
|
413 |
-
output_image = gr.Image(label="Análise Visual")
|
414 |
-
results_text = gr.Markdown(label="Relatório Detalhado")
|
415 |
-
|
416 |
-
with gr.Row():
|
417 |
-
gr.Markdown("""
|
418 |
-
### Legenda das Zonas:
|
419 |
-
- 🔴 Zona Cerebral/Neural: Sistema nervoso
|
420 |
-
- 🟢 Zona Digestiva: Sistema digestivo
|
421 |
-
- 🔵 Zona Respiratória: Sistema respiratório
|
422 |
-
- 🟡 Zona Circulatória: Sistema circulatório
|
423 |
-
- 🟣 Zona Linfática: Sistema linfático
|
424 |
-
- 🔰 Zona Endócrina: Sistema hormonal
|
425 |
-
- ⚪ Zona Pupilar: Sistema nervoso autônomo
|
426 |
-
""")
|
427 |
|
|
|
428 |
analyze_btn.click(
|
429 |
fn=process_image,
|
430 |
inputs=input_image,
|
431 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
432 |
)
|
433 |
-
|
434 |
-
gr.Markdown("""
|
435 |
-
---
|
436 |
-
### 📋 Sobre a Análise
|
437 |
-
|
438 |
-
Este sistema realiza:
|
439 |
-
- Detecção automática da pupila
|
440 |
-
- Análise de 7 zonas principais da íris
|
441 |
-
- Avaliação de padrões e texturas
|
442 |
-
- Geração de relatório detalhado
|
443 |
-
|
444 |
-
**Observações Importantes:**
|
445 |
-
- A iridologia é uma prática alternativa
|
446 |
-
- Os resultados são interpretativos
|
447 |
-
- Consulte profissionais de saúde para diagnósticos
|
448 |
-
|
449 |
-
---
|
450 |
-
""")
|
451 |
|
452 |
-
# Configurações para execução
|
453 |
if __name__ == "__main__":
|
454 |
try:
|
455 |
iface.launch()
|
|
|
5 |
from dataclasses import dataclass
|
6 |
from typing import Tuple, Dict, List
|
7 |
import torch
|
|
|
8 |
import torchvision.transforms as transforms
|
9 |
import logging
|
10 |
from datetime import datetime
|
|
|
11 |
|
12 |
+
# Configuração de logging
|
13 |
logging.basicConfig(
|
14 |
level=logging.INFO,
|
15 |
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
|
17 |
|
18 |
@dataclass
|
19 |
class IrisZone:
|
20 |
+
"""Define características e atributos de uma zona da íris"""
|
21 |
name: str
|
22 |
inner_ratio: float
|
23 |
outer_ratio: float
|
24 |
color: Tuple[int, int, int]
|
25 |
description: str
|
26 |
+
related_systems: List[str]
|
27 |
+
indicators: Dict[str, List[str]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
class IrisAnalyzer:
|
30 |
"""Classe principal para análise da íris"""
|
|
|
36 |
outer_ratio=1.0,
|
37 |
color=(255, 0, 0),
|
38 |
description="Sistema nervoso central e periférico",
|
39 |
+
related_systems=["Cérebro", "Medula", "Nervos"],
|
40 |
+
indicators={
|
41 |
+
"baixa": [
|
42 |
+
"Possível fadiga mental",
|
43 |
+
"Considerar níveis de estresse",
|
44 |
+
"Avaliar qualidade do sono"
|
45 |
+
],
|
46 |
+
"media": [
|
47 |
+
"Estado neural adequado",
|
48 |
+
"Manter atividades mentais"
|
49 |
+
],
|
50 |
+
"alta": [
|
51 |
+
"Boa vitalidade neural",
|
52 |
+
"Continuar práticas saudáveis"
|
53 |
+
]
|
54 |
+
}
|
55 |
+
),
|
56 |
+
IrisZone(
|
57 |
+
name="Zona Digestiva",
|
58 |
+
inner_ratio=0.7,
|
59 |
+
outer_ratio=0.85,
|
60 |
+
color=(0, 255, 0),
|
61 |
+
description="Sistema digestivo completo",
|
62 |
+
related_systems=["Estômago", "Intestinos", "Fígado"],
|
63 |
+
indicators={
|
64 |
+
"baixa": [
|
65 |
+
"Atenção à digestão",
|
66 |
+
"Avaliar alimentação",
|
67 |
+
"Observar sensibilidades"
|
68 |
+
],
|
69 |
+
"media": [
|
70 |
+
"Digestão adequada",
|
71 |
+
"Manter dieta balanceada"
|
72 |
+
],
|
73 |
+
"alta": [
|
74 |
+
"Boa saúde digestiva",
|
75 |
+
"Manter hábitos saudáveis"
|
76 |
+
]
|
77 |
+
}
|
78 |
+
),
|
79 |
+
IrisZone(
|
80 |
+
name="Zona Respiratória",
|
81 |
+
inner_ratio=0.55,
|
82 |
+
outer_ratio=0.7,
|
83 |
+
color=(0, 0, 255),
|
84 |
+
description="Sistema respiratório",
|
85 |
+
related_systems=["Pulmões", "Brônquios", "Vias aéreas"],
|
86 |
+
indicators={
|
87 |
+
"baixa": [
|
88 |
+
"Atenção respiratória",
|
89 |
+
"Considerar exercícios",
|
90 |
+
"Avaliar ambiente"
|
91 |
+
],
|
92 |
+
"media": [
|
93 |
+
"Função respiratória adequada",
|
94 |
+
"Manter atividades aeróbicas"
|
95 |
+
],
|
96 |
+
"alta": [
|
97 |
+
"Boa capacidade respiratória",
|
98 |
+
"Continuar práticas saudáveis"
|
99 |
+
]
|
100 |
+
}
|
101 |
+
),
|
102 |
+
IrisZone(
|
103 |
+
name="Zona Circulatória",
|
104 |
+
inner_ratio=0.4,
|
105 |
+
outer_ratio=0.55,
|
106 |
+
color=(255, 255, 0),
|
107 |
+
description="Sistema cardiovascular",
|
108 |
+
related_systems=["Coração", "Vasos", "Circulação"],
|
109 |
+
indicators={
|
110 |
+
"baixa": [
|
111 |
+
"Atenção circulatória",
|
112 |
+
"Considerar exercícios",
|
113 |
+
"Avaliar rotina"
|
114 |
+
],
|
115 |
+
"media": [
|
116 |
+
"Circulação adequada",
|
117 |
+
"Manter atividade física"
|
118 |
+
],
|
119 |
+
"alta": [
|
120 |
+
"Boa saúde circulatória",
|
121 |
+
"Manter hábitos ativos"
|
122 |
+
]
|
123 |
+
}
|
124 |
+
),
|
125 |
+
IrisZone(
|
126 |
+
name="Zona Linfática",
|
127 |
+
inner_ratio=0.25,
|
128 |
+
outer_ratio=0.4,
|
129 |
+
color=(255, 0, 255),
|
130 |
+
description="Sistema imunológico",
|
131 |
+
related_systems=["Linfonodos", "Imunidade", "Defesa"],
|
132 |
+
indicators={
|
133 |
+
"baixa": [
|
134 |
+
"Atenção imunológica",
|
135 |
+
"Considerar suporte",
|
136 |
+
"Avaliar estresse"
|
137 |
+
],
|
138 |
+
"media": [
|
139 |
+
"Imunidade adequada",
|
140 |
+
"Manter cuidados básicos"
|
141 |
+
],
|
142 |
+
"alta": [
|
143 |
+
"Boa resposta imune",
|
144 |
+
"Manter hábitos saudáveis"
|
145 |
+
]
|
146 |
+
}
|
147 |
+
),
|
148 |
+
IrisZone(
|
149 |
+
name="Zona Endócrina",
|
150 |
+
inner_ratio=0.15,
|
151 |
+
outer_ratio=0.25,
|
152 |
+
color=(0, 255, 255),
|
153 |
+
description="Sistema hormonal",
|
154 |
+
related_systems=["Glândulas", "Hormônios", "Metabolismo"],
|
155 |
+
indicators={
|
156 |
+
"baixa": [
|
157 |
+
"Atenção hormonal",
|
158 |
+
"Considerar ritmos",
|
159 |
+
"Avaliar rotina"
|
160 |
+
],
|
161 |
+
"media": [
|
162 |
+
"Função hormonal adequada",
|
163 |
+
"Manter equilíbrio"
|
164 |
+
],
|
165 |
+
"alta": [
|
166 |
+
"Bom equilíbrio hormonal",
|
167 |
+
"Manter hábitos regulares"
|
168 |
+
]
|
169 |
+
}
|
170 |
),
|
171 |
+
IrisZone(
|
172 |
+
name="Zona Pupilar",
|
173 |
+
inner_ratio=0,
|
174 |
+
outer_ratio=0.15,
|
175 |
+
color=(128, 128, 128),
|
176 |
+
description="Sistema nervoso autônomo",
|
177 |
+
related_systems=["SNA", "Reflexos", "Regulação"],
|
178 |
+
indicators={
|
179 |
+
"baixa": [
|
180 |
+
"Atenção autonômica",
|
181 |
+
"Considerar relaxamento",
|
182 |
+
"Avaliar estresse"
|
183 |
+
],
|
184 |
+
"media": [
|
185 |
+
"Função autonômica adequada",
|
186 |
+
"Manter equilíbrio"
|
187 |
+
],
|
188 |
+
"alta": [
|
189 |
+
"Boa regulação autonômica",
|
190 |
+
"Manter práticas saudáveis"
|
191 |
+
]
|
192 |
+
}
|
193 |
+
)
|
194 |
]
|
195 |
|
196 |
def preprocess_image(self, img: np.ndarray) -> np.ndarray:
|
197 |
+
"""Pré-processa a imagem para melhor análise"""
|
198 |
try:
|
199 |
+
# Converter para LAB
|
200 |
lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB)
|
201 |
l, a, b = cv2.split(lab)
|
202 |
|
|
|
217 |
return img
|
218 |
|
219 |
def detect_pupil(self, img: np.ndarray) -> Tuple[int, int, int]:
|
220 |
+
"""Detecta a pupila na imagem"""
|
221 |
try:
|
|
|
222 |
processed = self.preprocess_image(img)
|
223 |
gray = cv2.cvtColor(processed, cv2.COLOR_RGB2GRAY)
|
224 |
|
225 |
+
# Threshold adaptativo
|
226 |
blur = cv2.GaussianBlur(gray, (5,5), 0)
|
227 |
_, thresh = cv2.threshold(blur, 30, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
228 |
|
|
|
237 |
return None
|
238 |
|
239 |
# Encontrar o contorno mais circular
|
240 |
+
max_circularity = 0
|
241 |
best_contour = None
|
242 |
|
243 |
for contour in contours:
|
|
|
245 |
perimeter = cv2.arcLength(contour, True)
|
246 |
if perimeter == 0:
|
247 |
continue
|
|
|
|
|
248 |
|
249 |
+
circularity = 4 * np.pi * area / (perimeter * perimeter)
|
250 |
+
if circularity > max_circularity:
|
251 |
+
max_circularity = circularity
|
252 |
best_contour = contour
|
253 |
|
254 |
if best_contour is None:
|
255 |
return None
|
256 |
+
|
257 |
(x, y), radius = cv2.minEnclosingCircle(best_contour)
|
258 |
return (int(x), int(y), int(radius))
|
259 |
|
|
|
261 |
logging.error(f"Erro na detecção da pupila: {str(e)}")
|
262 |
return None
|
263 |
|
264 |
+
def analyze_zone(self, img: np.ndarray, mask: np.ndarray, zone: IrisZone) -> Dict:
|
265 |
+
"""Analisa uma zona específica da íris"""
|
266 |
try:
|
267 |
# Extrair características
|
268 |
mean_color = cv2.mean(img, mask=mask)
|
|
|
274 |
mean_intensity = np.mean(pixels)
|
275 |
std_dev = np.std(pixels)
|
276 |
|
277 |
+
# Determinar nível
|
278 |
+
if mean_intensity < 85:
|
279 |
+
nivel = "baixa"
|
280 |
+
elif mean_intensity < 170:
|
281 |
+
nivel = "media"
|
282 |
+
else:
|
283 |
+
nivel = "alta"
|
284 |
|
285 |
+
# Calcular confiança
|
286 |
+
confianca = "alta" if std_dev < 15 else "media" if std_dev < 30 else "baixa"
|
|
|
|
|
287 |
|
288 |
+
# Gerar análise
|
289 |
+
analysis = {
|
290 |
+
"nome": zone.name,
|
291 |
+
"descricao": zone.description,
|
292 |
+
"sistemas_relacionados": zone.related_systems,
|
293 |
+
"indicadores": zone.indicators[nivel],
|
294 |
+
"metricas": {
|
295 |
+
"intensidade": float(mean_intensity),
|
296 |
+
"variacao": float(std_dev),
|
297 |
+
"nivel": nivel,
|
298 |
+
"confianca": confianca
|
299 |
+
},
|
300 |
+
"cores": {
|
301 |
+
"r": float(mean_color[0]),
|
302 |
+
"g": float(mean_color[1]),
|
303 |
+
"b": float(mean_color[2])
|
304 |
}
|
305 |
}
|
306 |
|
307 |
return analysis
|
308 |
|
309 |
+
return {"erro": "Zona sem pixels válidos"}
|
310 |
|
311 |
except Exception as e:
|
312 |
+
logging.error(f"Erro na análise da zona {zone.name}: {str(e)}")
|
313 |
return {"erro": str(e)}
|
314 |
|
315 |
def analyze_iris(self, img: np.ndarray) -> Tuple[np.ndarray, Dict]:
|
316 |
+
"""Realiza a análise completa da íris"""
|
317 |
try:
|
318 |
output_img = img.copy()
|
319 |
+
results = {
|
320 |
"meta": {
|
321 |
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
322 |
+
"versao": "1.0.0"
|
323 |
},
|
|
|
324 |
"zonas": {},
|
325 |
+
"resumo": {}
|
326 |
}
|
327 |
|
328 |
# Detectar pupila
|
329 |
pupil = self.detect_pupil(img)
|
330 |
if pupil is None:
|
331 |
return img, {"erro": "Não foi possível detectar a pupila"}
|
332 |
+
|
333 |
x, y, pupil_radius = pupil
|
334 |
iris_radius = pupil_radius * 4
|
335 |
|
336 |
+
# Desenhar pupila
|
337 |
cv2.circle(output_img, (x, y), pupil_radius, (0, 0, 0), 2)
|
338 |
|
339 |
# Analisar cada zona
|
340 |
+
total_score = 0
|
341 |
+
valid_zones = 0
|
342 |
|
343 |
for zone in self.zones:
|
344 |
inner_r = int(iris_radius * zone.inner_ratio)
|
345 |
outer_r = int(iris_radius * zone.outer_ratio)
|
346 |
|
347 |
+
# Criar máscara
|
348 |
mask = np.zeros(img.shape[:2], dtype=np.uint8)
|
349 |
cv2.circle(mask, (x, y), outer_r, 255, -1)
|
350 |
cv2.circle(mask, (x, y), inner_r, 0, -1)
|
351 |
|
352 |
+
# Desenhar zona
|
353 |
cv2.circle(output_img, (x, y), outer_r, zone.color, 2)
|
354 |
|
355 |
# Analisar zona
|
356 |
+
analysis = self.analyze_zone(img, mask, zone)
|
357 |
+
results["zonas"][zone.name] = analysis
|
358 |
|
|
|
359 |
if "metricas" in analysis:
|
360 |
+
if analysis["metricas"]["nivel"] == "alta":
|
361 |
+
total_score += 1.0
|
362 |
+
elif analysis["metricas"]["nivel"] == "media":
|
363 |
+
total_score += 0.5
|
364 |
+
valid_zones += 1
|
365 |
|
366 |
# Adicionar texto
|
367 |
text_x = x - iris_radius
|
|
|
369 |
cv2.putText(output_img, zone.name, (text_x, text_y),
|
370 |
cv2.FONT_HERSHEY_SIMPLEX, 0.5, zone.color, 1)
|
371 |
|
372 |
+
# Calcular índice geral
|
373 |
+
if valid_zones > 0:
|
374 |
+
health_index = (total_score / valid_zones) * 100
|
375 |
+
results["resumo"] = {
|
376 |
+
"indice_geral": f"{health_index:.1f}%",
|
377 |
+
"interpretacao": "Excelente" if health_index > 80 else
|
378 |
+
"Bom" if health_index > 60 else
|
379 |
+
"Regular" if health_index > 40 else
|
380 |
+
"Requer atenção"
|
381 |
+
}
|
382 |
|
383 |
+
return output_img, results
|
384 |
|
385 |
except Exception as e:
|
386 |
logging.error(f"Erro na análise da íris: {str(e)}")
|
387 |
return img, {"erro": str(e)}
|
388 |
|
389 |
+
def format_zone_report(zone_name: str, analysis: Dict) -> str:
|
390 |
+
"""Formata o relatório de uma zona específica"""
|
391 |
+
if "erro" in analysis:
|
392 |
+
return f"Erro na análise de {zone_name}: {analysis['erro']}"
|
|
|
|
|
393 |
|
394 |
+
report = f"""### {zone_name}
|
395 |
+
|
396 |
+
**Descrição:** {analysis.get('descricao', 'N/A')}
|
397 |
+
|
398 |
+
**Sistemas Relacionados:**
|
399 |
+
"""
|
400 |
+
for sistema in analysis.get('sistemas_relacionados', []):
|
401 |
+
report += f"- {sistema}\n"
|
402 |
|
403 |
+
report += "\n**Indicadores:**\n"
|
404 |
+
for indicador in analysis.get('indicadores', []):
|
405 |
+
report += f"- {indicador}\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
406 |
|
407 |
+
if "metricas" in analysis:
|
408 |
+
report += f"""
|
409 |
+
**Métricas:**
|
410 |
+
- Nível: {analysis['metricas']['nivel'].title()}
|
411 |
+
- Confiança: {analysis['metricas']['confianca'].title()}
|
412 |
+
- Intensidade: {analysis['metricas']['intensidade']:.1f}
|
413 |
+
- Variação: {analysis['metricas']['variacao']:.1f}
|
414 |
+
"""
|
415 |
+
return report
|
416 |
+
|
417 |
+
def format_technical_metrics(analysis: Dict) -> str:
|
418 |
+
"""Formata as métricas técnicas de uma análise"""
|
419 |
+
metrics = """### Métricas Técnicas\n\n"""
|
420 |
|
421 |
+
if "metricas" in analysis:
|
422 |
+
metrics += f"""
|
423 |
+
- **Análise de Intensidade**
|
424 |
+
- Valor: {analysis['metricas']['intensidade']:.1f}/255
|
425 |
+
- Interpretação: {analysis['metricas']['nivel'].title()}
|
426 |
+
|
427 |
+
- **Análise de Variação**
|
428 |
+
- Valor: {analysis['metricas']['variacao']:.1f}
|
429 |
+
- Confiança: {analysis['metricas']['confianca'].title()}
|
430 |
+
|
431 |
+
- **Cores (RGB)**
|
432 |
+
- R: {analysis['cores']['r']:.1f}
|
433 |
+
- G: {analysis['cores']['g']:.1f}
|
434 |
+
- B: {analysis['cores']['b']:.1f}
|
435 |
+
"""
|
436 |
+
return metrics
|
437 |
|
438 |
def process_image(img):
|
439 |
+
"""Processa a imagem e retorna todos os resultados formatados"""
|
440 |
if img is None:
|
441 |
+
return [None] * 6, gr.Warning("Por favor, carregue uma imagem.")
|
|
|
|
|
|
|
|
|
442 |
|
443 |
+
try:
|
444 |
+
analyzer = IrisAnalyzer()
|
445 |
+
output_img, results = analyzer.analyze_iris(np.array(img))
|
446 |
+
|
447 |
+
if "erro" in results:
|
448 |
+
return [output_img] + [results["erro"]] * 5
|
449 |
+
|
450 |
+
# 1. Resumo Geral
|
451 |
+
resumo = f"""## Resumo da Análise
|
452 |
+
|
453 |
+
🎯 **Índice Geral de Saúde:** {results['resumo'].get('indice_geral', 'N/A')}
|
454 |
+
📊 **Interpretação:** {results['resumo'].get('interpretacao', 'N/A')}
|
455 |
+
🕒 **Data da Análise:** {results['meta']['timestamp']}
|
456 |
+
|
457 |
+
---"""
|
458 |
+
|
459 |
+
# 2. Análise Detalhada
|
460 |
+
analise_detalhada = "## Análise Detalhada por Zona\n\n"
|
461 |
+
for zone_name, analysis in results['zonas'].items():
|
462 |
+
analise_detalhada += format_zone_report(zone_name, analysis) + "\n---\n"
|
463 |
+
|
464 |
+
# 3. Recomendações
|
465 |
+
recomendacoes = "## Recomendações Personalizadas\n\n"
|
466 |
+
for zone_name, analysis in results['zonas'].items():
|
467 |
+
if analysis.get('indicadores'):
|
468 |
+
recomendacoes += f"### {zone_name}\n"
|
469 |
+
for idx, rec in enumerate(analysis['indicadores'], 1):
|
470 |
+
recomendacoes += f"{idx}. {rec}\n"
|
471 |
+
recomendacoes += "\n"
|
472 |
+
|
473 |
+
# 4. Métricas Técnicas
|
474 |
+
metricas = "## Métricas e Indicadores\n\n"
|
475 |
+
for zone_name, analysis in results['zonas'].items():
|
476 |
+
metricas += f"### {zone_name}\n"
|
477 |
+
metricas += format_technical_metrics(analysis) + "\n"
|
478 |
+
|
479 |
+
# 5. Observações Técnicas
|
480 |
+
observacoes = f"""## Informações Técnicas
|
481 |
+
|
482 |
+
📌 **Detalhes da Análise**
|
483 |
+
- Versão do Sistema: {results['meta']['versao']}
|
484 |
+
- Data/Hora: {results['meta']['timestamp']}
|
485 |
+
- Zonas Analisadas: {len(results['zonas'])}
|
486 |
+
|
487 |
+
⚠️ **Observações**
|
488 |
+
- Esta é uma análise educacional
|
489 |
+
- Não substitui avaliação médica
|
490 |
+
- Baseado na teoria de Jensen
|
491 |
+
"""
|
492 |
+
|
493 |
+
return output_img, resumo, analise_detalhada, recomendacoes, metricas, observacoes
|
494 |
+
|
495 |
+
except Exception as e:
|
496 |
+
logging.error(f"Erro no processamento: {str(e)}")
|
497 |
+
return [None] * 6, gr.Error(f"Erro no processamento: {str(e)}")
|
498 |
|
499 |
# Interface Gradio
|
500 |
with gr.Blocks(theme=gr.themes.Soft()) as iface:
|
501 |
gr.Markdown("""
|
502 |
# 🔍 Analisador Avançado de Íris
|
503 |
+
## Sistema Educacional de Análise Iridológica
|
504 |
|
505 |
+
⚠️ Este é um sistema para fins educacionais. Não utilize para diagnósticos médicos.
|
506 |
""")
|
507 |
|
508 |
+
with gr.Tabs() as tabs:
|
509 |
+
# Aba de Análise
|
510 |
+
with gr.Tab("📸 Análise"):
|
511 |
+
with gr.Row():
|
512 |
+
with gr.Column(scale=1):
|
513 |
+
input_image = gr.Image(
|
514 |
+
label="Upload da Imagem",
|
515 |
+
type="numpy",
|
516 |
+
sources=["upload", "clipboard"],
|
517 |
+
height=400
|
518 |
+
)
|
519 |
+
analyze_btn = gr.Button("🔍 Analisar", variant="primary", size="lg")
|
520 |
+
|
521 |
+
with gr.Column(scale=1):
|
522 |
+
output_image = gr.Image(label="Resultado da Análise", height=400)
|
523 |
+
|
524 |
+
# Aba de Resultados
|
525 |
+
with gr.Tab("📊 Resultados"):
|
526 |
+
with gr.Tabs() as results_tabs:
|
527 |
+
with gr.Tab("📝 Resumo"):
|
528 |
+
resumo_text = gr.Markdown()
|
529 |
+
|
530 |
+
with gr.Tab("🔍 Análise Detalhada"):
|
531 |
+
analise_text = gr.Markdown()
|
532 |
+
|
533 |
+
with gr.Tab("💡 Recomendações"):
|
534 |
+
recomendacoes_text = gr.Markdown()
|
535 |
+
|
536 |
+
with gr.Tab("📈 Métricas"):
|
537 |
+
metricas_text = gr.Markdown()
|
538 |
+
|
539 |
+
with gr.Tab("ℹ️ Informações"):
|
540 |
+
observacoes_text = gr.Markdown()
|
541 |
+
|
542 |
+
# Aba de Ajuda
|
543 |
+
with gr.Tab("❓ Ajuda"):
|
544 |
gr.Markdown("""
|
545 |
+
## Como Usar o Sistema
|
546 |
+
|
547 |
+
1. **Upload da Imagem**
|
548 |
+
- Use uma foto clara do olho
|
549 |
+
- Certifique-se que a íris está visível
|
550 |
+
- Evite reflexos e sombras
|
551 |
+
|
552 |
+
2. **Análise**
|
553 |
+
- Clique em "Analisar"
|
554 |
+
- Aguarde o processamento
|
555 |
+
- Revise os resultados nas abas
|
556 |
+
|
557 |
+
3. **Interpretação**
|
558 |
+
- Resumo: visão geral
|
559 |
+
- Análise: detalhes por zona
|
560 |
+
- Recomendações: sugestões
|
561 |
+
- Métricas: dados técnicos
|
562 |
+
|
563 |
+
## Zonas Analisadas
|
564 |
+
|
565 |
+
1. 🧠 **Zona Cerebral/Neural**
|
566 |
+
- Sistema nervoso
|
567 |
+
2. 🟢 **Zona Digestiva**
|
568 |
+
- Sistema digestivo
|
569 |
+
3. 🫁 **Zona Respiratória**
|
570 |
+
- Sistema respiratório
|
571 |
+
4. ❤️ **Zona Circulatória**
|
572 |
+
- Sistema cardiovascular
|
573 |
+
5. 🔵 **Zona Linfática**
|
574 |
+
- Sistema imunológico
|
575 |
+
6. 🟣 **Zona Endócrina**
|
576 |
+
- Sistema hormonal
|
577 |
+
7. ⚫ **Zona Pupilar**
|
578 |
+
- Sistema nervoso autônomo
|
579 |
+
|
580 |
+
## Observações
|
581 |
+
|
582 |
+
- Sistema educacional
|
583 |
+
- Não substitui diagnósticos
|
584 |
+
- Consulte profissionais de saúde
|
585 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
586 |
|
587 |
+
# Configurar eventos
|
588 |
analyze_btn.click(
|
589 |
fn=process_image,
|
590 |
inputs=input_image,
|
591 |
+
outputs=[
|
592 |
+
output_image,
|
593 |
+
resumo_text,
|
594 |
+
analise_text,
|
595 |
+
recomendacoes_text,
|
596 |
+
metricas_text,
|
597 |
+
observacoes_text
|
598 |
+
]
|
599 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
600 |
|
|
|
601 |
if __name__ == "__main__":
|
602 |
try:
|
603 |
iface.launch()
|