DHEIVER commited on
Commit
86f1dcc
·
verified ·
1 Parent(s): 01cdb9f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +345 -143
app.py CHANGED
@@ -7,19 +7,67 @@ from typing import Tuple, Dict, List
7
  import torch
8
  import torch.nn.functional as F
9
  import torchvision.transforms as transforms
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  @dataclass
12
  class IrisZone:
 
13
  name: str
14
  inner_ratio: float
15
  outer_ratio: float
16
  color: Tuple[int, int, int]
17
  angle_start: float = 0
18
  angle_end: float = 360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  class IrisAnalyzer:
 
21
  def __init__(self):
22
- # Define zonas com cores distintas e ângulos específicos
23
  self.zones = [
24
  IrisZone("Zona Cerebral/Neural", 0.85, 1.0, (255, 0, 0)),
25
  IrisZone("Zona Digestiva", 0.7, 0.85, (0, 255, 0)),
@@ -29,166 +77,320 @@ class IrisAnalyzer:
29
  IrisZone("Zona Endócrina", 0.15, 0.25, (0, 255, 255)),
30
  IrisZone("Zona Pupilar", 0, 0.15, (128, 128, 128))
31
  ]
32
-
 
 
33
  def preprocess_image(self, img: np.ndarray) -> np.ndarray:
34
- """Pré-processa a imagem para melhorar a detecção"""
35
- # Converter para escala de cinza
36
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
37
-
38
- # Aplicar equalização de histograma adaptativo
39
- clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
40
- enhanced = clahe.apply(gray)
41
-
42
- # Redução de ruído
43
- denoised = cv2.fastNlMeansDenoising(enhanced)
44
-
45
- return denoised
 
 
 
 
 
 
 
 
46
 
47
  def detect_pupil(self, img: np.ndarray) -> Tuple[int, int, int]:
48
- """Detecta a pupila usando técnicas avançadas"""
49
- preprocessed = self.preprocess_image(img)
50
-
51
- # Threshold adaptativo
52
- _, thresh = cv2.threshold(preprocessed, 30, 255,
53
- cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
54
-
55
- # Operações morfológicas
56
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
57
- morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
58
-
59
- # Encontrar contornos
60
- contours, _ = cv2.findContours(morph, cv2.RETR_EXTERNAL,
61
- cv2.CHAIN_APPROX_SIMPLE)
62
-
63
- if not contours:
64
- return None
65
-
66
- # Encontrar o contorno mais circular
67
- max_circularity = 0
68
- best_contour = None
69
-
70
- for contour in contours:
71
- area = cv2.contourArea(contour)
72
- perimeter = cv2.arcLength(contour, True)
73
 
74
- if perimeter == 0:
75
- continue
 
 
 
 
 
 
 
 
 
76
 
77
- circularity = 4 * np.pi * area / (perimeter * perimeter)
 
 
 
 
 
 
 
78
 
79
- if circularity > max_circularity:
80
- max_circularity = circularity
81
- best_contour = contour
82
-
83
- if best_contour is None:
84
- return None
 
 
 
85
 
86
- # Ajustar círculo
87
- (x, y), radius = cv2.minEnclosingCircle(best_contour)
88
- return (int(x), int(y), int(radius))
89
 
90
  def analyze_zone(self, img: np.ndarray, mask: np.ndarray) -> Dict:
91
- """Analisa características da zona usando a máscara"""
92
- # Extrair características da região
93
- mean_color = cv2.mean(img, mask=mask)
94
-
95
- # Calcular textura usando GLCM
96
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
97
- zone_pixels = cv2.bitwise_and(gray, gray, mask=mask)
98
-
99
- # Análise de textura básica
100
- if np.sum(mask) > 0:
101
- std_dev = np.std(zone_pixels[mask > 0])
102
- mean_intensity = np.mean(zone_pixels[mask > 0])
103
- else:
104
- std_dev = 0
105
- mean_intensity = 0
106
-
107
- return {
108
- 'mean_intensity': mean_intensity,
109
- 'std_dev': std_dev,
110
- 'color_variation': mean_color[:3]
111
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  def interpret_zone(self, analysis: Dict) -> str:
114
- """Interpreta os resultados da análise"""
115
- intensity = analysis['mean_intensity']
116
- variation = analysis['std_dev']
117
-
118
- if intensity < 85:
119
- base_condition = "Possível área de atenção"
120
- elif intensity < 170:
121
- base_condition = "Área moderada"
122
- else:
123
- base_condition = "Área normal"
124
-
125
- detail = f"(Intensidade: {intensity:.1f}, Variação: {variation:.1f})"
126
- return f"{base_condition} {detail}"
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  def analyze_iris(self, img: np.ndarray) -> Tuple[np.ndarray, Dict]:
129
  """Análise principal da íris"""
130
- # Criar cópia para desenho
131
- output_img = img.copy()
132
- results = {}
133
-
134
- # Detectar pupila
135
- pupil = self.detect_pupil(img)
136
- if pupil is None:
137
- return img, {"Erro": "Não foi possível detectar a pupila"}
138
 
139
- x, y, pupil_radius = pupil
140
-
141
- # Estimar raio da íris (aproximadamente 4x o raio da pupila)
142
- iris_radius = pupil_radius * 4
143
-
144
- # Desenhar círculo da pupila
145
- cv2.circle(output_img, (x, y), pupil_radius, (0, 0, 0), 2)
146
-
147
- # Analisar cada zona
148
- for zone in self.zones:
149
- inner_r = int(iris_radius * zone.inner_ratio)
150
- outer_r = int(iris_radius * zone.outer_ratio)
151
-
152
- # Criar máscara para a zona
153
- mask = np.zeros(img.shape[:2], dtype=np.uint8)
154
- cv2.circle(mask, (x, y), outer_r, 255, -1)
155
- cv2.circle(mask, (x, y), inner_r, 0, -1)
156
-
157
- # Desenhar círculos da zona
158
- cv2.circle(output_img, (x, y), outer_r, zone.color, 2)
159
-
160
- # Analisar zona
161
- analysis = self.analyze_zone(img, mask)
162
- interpretation = self.interpret_zone(analysis)
163
- results[zone.name] = interpretation
164
-
165
- # Adicionar texto
166
- text_x = x - iris_radius
167
- text_y = y + outer_r
168
- cv2.putText(output_img, zone.name, (text_x, text_y),
169
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, zone.color, 1)
170
-
171
- return output_img, results
 
 
 
 
 
 
 
 
 
 
172
 
173
  def process_image(img):
174
- analyzer = IrisAnalyzer()
175
- return analyzer.analyze_iris(np.array(img))
 
 
 
 
 
176
 
177
  # Interface Gradio
178
- iface = gr.Interface(
179
- fn=process_image,
180
- inputs=gr.Image(),
181
- outputs=[
182
- gr.Image(label="Análise Visual"),
183
- gr.JSON(label="Resultados da Análise por Zona")
184
- ],
185
- title="Analisador Avançado de Íris (Baseado na Teoria de Jensen)",
186
- description="""AVISO: Esta é uma demonstração educacional baseada na teoria de iridologia.
187
- Esta não é uma ferramenta diagnóstica validada cientificamente e não deve ser usada para
188
- decisões médicas. Consulte sempre um profissional de saúde qualificado para diagnósticos.""",
189
- examples=[],
190
- theme="default"
191
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  if __name__ == "__main__":
194
- iface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import torch
8
  import torch.nn.functional as F
9
  import torchvision.transforms as transforms
10
+ import logging
11
+ import os
12
+ from datetime import datetime
13
+
14
+ # Configuração de logging
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(levelname)s - %(message)s',
18
+ handlers=[
19
+ logging.FileHandler('iridology_analyzer.log'),
20
+ logging.StreamHandler()
21
+ ]
22
+ )
23
 
24
  @dataclass
25
  class IrisZone:
26
+ """Classe para definir as características de uma zona da íris"""
27
  name: str
28
  inner_ratio: float
29
  outer_ratio: float
30
  color: Tuple[int, int, int]
31
  angle_start: float = 0
32
  angle_end: float = 360
33
+
34
+ def __post_init__(self):
35
+ """Validação dos parâmetros da zona"""
36
+ if not 0 <= self.inner_ratio <= 1:
37
+ raise ValueError("inner_ratio deve estar entre 0 e 1")
38
+ if not 0 <= self.outer_ratio <= 1:
39
+ raise ValueError("outer_ratio deve estar entre 0 e 1")
40
+ if self.inner_ratio >= self.outer_ratio:
41
+ raise ValueError("inner_ratio deve ser menor que outer_ratio")
42
+
43
+ class ImageProcessor:
44
+ """Classe para processamento básico de imagens"""
45
+ @staticmethod
46
+ def enhance_image(img: np.ndarray) -> np.ndarray:
47
+ """Melhora a qualidade da imagem"""
48
+ # Converter para LAB
49
+ lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB)
50
+ l, a, b = cv2.split(lab)
51
+
52
+ # Aplicar CLAHE no canal L
53
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
54
+ l = clahe.apply(l)
55
+
56
+ # Merge canais
57
+ lab = cv2.merge((l,a,b))
58
+
59
+ # Converter de volta para RGB
60
+ enhanced = cv2.cvtColor(lab, cv2.COLOR_LAB2RGB)
61
+ return enhanced
62
+
63
+ @staticmethod
64
+ def reduce_noise(img: np.ndarray) -> np.ndarray:
65
+ """Reduz ruído na imagem"""
66
+ return cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)
67
 
68
  class IrisAnalyzer:
69
+ """Classe principal para análise da íris"""
70
  def __init__(self):
 
71
  self.zones = [
72
  IrisZone("Zona Cerebral/Neural", 0.85, 1.0, (255, 0, 0)),
73
  IrisZone("Zona Digestiva", 0.7, 0.85, (0, 255, 0)),
 
77
  IrisZone("Zona Endócrina", 0.15, 0.25, (0, 255, 255)),
78
  IrisZone("Zona Pupilar", 0, 0.15, (128, 128, 128))
79
  ]
80
+ self.image_processor = ImageProcessor()
81
+ logging.info("IrisAnalyzer inicializado com %d zonas", len(self.zones))
82
+
83
  def preprocess_image(self, img: np.ndarray) -> np.ndarray:
84
+ """Pré-processa a imagem para análise"""
85
+ try:
86
+ # Melhorar qualidade
87
+ enhanced = self.image_processor.enhance_image(img)
88
+
89
+ # Reduzir ruído
90
+ denoised = self.image_processor.reduce_noise(enhanced)
91
+
92
+ # Converter para escala de cinza
93
+ gray = cv2.cvtColor(denoised, cv2.COLOR_RGB2GRAY)
94
+
95
+ # Equalização adaptativa
96
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
97
+ preprocessed = clahe.apply(gray)
98
+
99
+ logging.info("Pré-processamento de imagem concluído com sucesso")
100
+ return preprocessed
101
+ except Exception as e:
102
+ logging.error("Erro no pré-processamento: %s", str(e))
103
+ raise
104
 
105
  def detect_pupil(self, img: np.ndarray) -> Tuple[int, int, int]:
106
+ """Detecta a pupila na imagem"""
107
+ try:
108
+ preprocessed = self.preprocess_image(img)
109
+
110
+ # Threshold adaptativo
111
+ _, thresh = cv2.threshold(preprocessed, 30, 255,
112
+ cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
113
+
114
+ # Operações morfológicas
115
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
116
+ morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
117
+
118
+ # Encontrar contornos
119
+ contours, _ = cv2.findContours(morph, cv2.RETR_EXTERNAL,
120
+ cv2.CHAIN_APPROX_SIMPLE)
 
 
 
 
 
 
 
 
 
 
121
 
122
+ if not contours:
123
+ logging.warning("Nenhum contorno encontrado na imagem")
124
+ return None
125
+
126
+ # Encontrar o contorno mais circular
127
+ max_circularity = 0
128
+ best_contour = None
129
+
130
+ for contour in contours:
131
+ area = cv2.contourArea(contour)
132
+ perimeter = cv2.arcLength(contour, True)
133
 
134
+ if perimeter == 0:
135
+ continue
136
+
137
+ circularity = 4 * np.pi * area / (perimeter * perimeter)
138
+
139
+ if circularity > max_circularity:
140
+ max_circularity = circularity
141
+ best_contour = contour
142
 
143
+ if best_contour is None:
144
+ logging.warning("Nenhum contorno circular encontrado")
145
+ return None
146
+
147
+ # Ajustar círculo
148
+ (x, y), radius = cv2.minEnclosingCircle(best_contour)
149
+ logging.info("Pupila detectada em (x=%d, y=%d) com raio=%d",
150
+ int(x), int(y), int(radius))
151
+ return (int(x), int(y), int(radius))
152
 
153
+ except Exception as e:
154
+ logging.error("Erro na detecção da pupila: %s", str(e))
155
+ raise
156
 
157
  def analyze_zone(self, img: np.ndarray, mask: np.ndarray) -> Dict:
158
+ """Analisa características de uma zona específica"""
159
+ try:
160
+ # Extrair características da região
161
+ mean_color = cv2.mean(img, mask=mask)
162
+
163
+ # Calcular textura
164
+ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
165
+ zone_pixels = cv2.bitwise_and(gray, gray, mask=mask)
166
+
167
+ if np.sum(mask) > 0:
168
+ std_dev = np.std(zone_pixels[mask > 0])
169
+ mean_intensity = np.mean(zone_pixels[mask > 0])
170
+
171
+ # Calcular características adicionais
172
+ non_zero = zone_pixels[mask > 0]
173
+ percentiles = np.percentile(non_zero, [25, 50, 75])
174
+
175
+ analysis = {
176
+ 'mean_intensity': mean_intensity,
177
+ 'std_dev': std_dev,
178
+ 'color_variation': mean_color[:3],
179
+ 'quartiles': percentiles.tolist(),
180
+ 'pixel_count': len(non_zero)
181
+ }
182
+ else:
183
+ analysis = {
184
+ 'mean_intensity': 0,
185
+ 'std_dev': 0,
186
+ 'color_variation': (0,0,0),
187
+ 'quartiles': [0,0,0],
188
+ 'pixel_count': 0
189
+ }
190
+
191
+ return analysis
192
+
193
+ except Exception as e:
194
+ logging.error("Erro na análise da zona: %s", str(e))
195
+ raise
196
 
197
  def interpret_zone(self, analysis: Dict) -> str:
198
+ """Interpreta os resultados da análise de uma zona"""
199
+ try:
200
+ intensity = analysis['mean_intensity']
201
+ variation = analysis['std_dev']
202
+
203
+ # Classificação básica
204
+ if intensity < 85:
205
+ base_condition = "Possível área de atenção"
206
+ confidence = "baixa" if variation > 30 else "média"
207
+ elif intensity < 170:
208
+ base_condition = "Área moderada"
209
+ confidence = "média" if variation > 20 else "alta"
210
+ else:
211
+ base_condition = "Área normal"
212
+ confidence = "alta" if variation < 15 else "média"
213
+
214
+ # Adicionar detalhes estatísticos
215
+ detail = (f"(Intensidade: {intensity:.1f}, "
216
+ f"Variação: {variation:.1f}, "
217
+ f"Confiança: {confidence})")
218
+
219
+ return f"{base_condition} {detail}"
220
+
221
+ except Exception as e:
222
+ logging.error("Erro na interpretação da zona: %s", str(e))
223
+ raise
224
 
225
  def analyze_iris(self, img: np.ndarray) -> Tuple[np.ndarray, Dict]:
226
  """Análise principal da íris"""
227
+ try:
228
+ # Criar cópia para desenho
229
+ output_img = img.copy()
230
+ results = {}
 
 
 
 
231
 
232
+ # Detectar pupila
233
+ pupil = self.detect_pupil(img)
234
+ if pupil is None:
235
+ return img, {"Erro": "Não foi possível detectar a pupila"}
236
+
237
+ x, y, pupil_radius = pupil
238
+
239
+ # Estimar raio da íris
240
+ iris_radius = pupil_radius * 4
241
+
242
+ # Desenhar círculo da pupila
243
+ cv2.circle(output_img, (x, y), pupil_radius, (0, 0, 0), 2)
244
+
245
+ # Analisar cada zona
246
+ for zone in self.zones:
247
+ inner_r = int(iris_radius * zone.inner_ratio)
248
+ outer_r = int(iris_radius * zone.outer_ratio)
249
+
250
+ # Criar máscara para a zona
251
+ mask = np.zeros(img.shape[:2], dtype=np.uint8)
252
+ cv2.circle(mask, (x, y), outer_r, 255, -1)
253
+ cv2.circle(mask, (x, y), inner_r, 0, -1)
254
+
255
+ # Desenhar círculos da zona
256
+ cv2.circle(output_img, (x, y), outer_r, zone.color, 2)
257
+
258
+ # Analisar zona
259
+ analysis = self.analyze_zone(img, mask)
260
+ interpretation = self.interpret_zone(analysis)
261
+ results[zone.name] = interpretation
262
+
263
+ # Adicionar texto
264
+ text_x = x - iris_radius
265
+ text_y = y + outer_r
266
+ cv2.putText(output_img, zone.name, (text_x, text_y),
267
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, zone.color, 1)
268
+
269
+ logging.info("Análise completa realizada com sucesso")
270
+ return output_img, results
271
+
272
+ except Exception as e:
273
+ logging.error("Erro na análise da íris: %s", str(e))
274
+ return img, {"Erro": f"Erro na análise: {str(e)}"}
275
 
276
  def process_image(img):
277
+ """Função principal para processar imagem via Gradio"""
278
+ try:
279
+ analyzer = IrisAnalyzer()
280
+ return analyzer.analyze_iris(np.array(img))
281
+ except Exception as e:
282
+ logging.error("Erro no processamento da imagem: %s", str(e))
283
+ return img, {"Erro": str(e)}
284
 
285
  # Interface Gradio
286
+ with gr.Blocks(theme=gr.themes.Soft()) as iface:
287
+ gr.Markdown("""
288
+ # 🔍 Analisador Avançado de Íris
289
+ ### Baseado na teoria de Jensen para análise de zonas da íris
290
+
291
+ ⚠️ **AVISO**: Esta é uma demonstração educacional. Não use para diagnósticos médicos.
292
+ """)
293
+
294
+ with gr.Row():
295
+ with gr.Column(scale=1):
296
+ input_image = gr.Image(
297
+ label="Carregue a imagem do olho",
298
+ type="numpy",
299
+ tool="select",
300
+ height=300
301
+ )
302
+
303
+ analyze_btn = gr.Button(
304
+ "📸 Analisar Imagem",
305
+ variant="primary",
306
+ scale=1
307
+ )
308
+
309
+ gr.Markdown("""
310
+ ### Como usar:
311
+ 1. Faça upload de uma imagem clara do olho
312
+ 2. Certifique-se que a íris está bem visível
313
+ 3. Clique em "Analisar Imagem"
314
+ 4. Veja os resultados à direita
315
+ """)
316
+
317
+ with gr.Column(scale=1):
318
+ output_image = gr.Image(
319
+ label="Análise Visual",
320
+ height=300
321
+ )
322
+
323
+ with gr.Accordion("📊 Resultados Detalhados", open=True):
324
+ results = gr.JSON(label="")
325
+
326
+ with gr.Row():
327
+ gr.Markdown("""
328
+ ### Legenda das Zonas:
329
+ - 🔴 Zona Cerebral/Neural
330
+ - 🟢 Zona Digestiva
331
+ - 🔵 Zona Respiratória
332
+ - 🟡 Zona Circulatória
333
+ - 🟣 Zona Linfática
334
+ - 🔰 Zona Endócrina
335
+ - ⚪ Zona Pupilar
336
+ """)
337
+
338
+ # Configurar o fluxo de análise
339
+ analyze_btn.click(
340
+ fn=process_image,
341
+ inputs=input_image,
342
+ outputs=[output_image, results],
343
+ )
344
+
345
+ # Adicionar informações extras
346
+ gr.Markdown("""
347
+ ---
348
+ ### ℹ️ Informações Importantes
349
+
350
+ Este aplicativo utiliza técnicas avançadas de processamento de imagem para:
351
+ - Detectar automaticamente a pupila
352
+ - Segmentar as zonas da íris
353
+ - Analisar padrões de cor e textura
354
+ - Gerar relatório detalhado por zona
355
+
356
+ **Recursos Técnicos:**
357
+ - Processamento de imagem adaptativo
358
+ - Detecção automática de pupila
359
+ - Análise de textura e padrões
360
+ - Sistema de logging para rastreamento
361
+ - Tratamento de erros robusto
362
+
363
+ **Lembre-se**: A iridologia é considerada uma prática alternativa e não é reconhecida
364
+ pela medicina convencional como método válido de diagnóstico.
365
+
366
+ ---
367
+ """)
368
 
369
  if __name__ == "__main__":
370
+ # Criar diretório de logs se não existir
371
+ if not os.path.exists('logs'):
372
+ os.makedirs('logs')
373
+
374
+ # Configurar nome do arquivo de log com timestamp
375
+ log_filename = f'logs/iridology_analyzer_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
376
+
377
+ # Adicionar handler de arquivo para logs
378
+ file_handler = logging.FileHandler(log_filename)
379
+ file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
380
+ logging.getLogger().addHandler(file_handler)
381
+
382
+ # Iniciar a interface
383
+ try:
384
+ iface.launch(
385
+ share=True, # Permite compartilhamento via link
386
+ server_name="0.0.0.0", # Permite acesso externo
387
+ server_port=7860, # Porta padrão do Gradio
388
+ enable_queue=True, # Habilita fila para múltiplos usuários
389
+ auth=None, # Sem autenticação
390
+ max_threads=4, # Limite de threads
391
+ show_error=True # Mostra erros detalhados
392
+ )
393
+ logging.info("Aplicação iniciada com sucesso")
394
+ except Exception as e:
395
+ logging.error(f"Erro ao iniciar a aplicação: {str(e)}")
396
+ raise