rmayormartins commited on
Commit
bdd4371
·
1 Parent(s): a2ab6d7
Files changed (2) hide show
  1. app.py +153 -314
  2. requirements.txt +1 -2
app.py CHANGED
@@ -7,384 +7,223 @@ import torch.optim as optim
7
  from torchvision import datasets, transforms, models
8
  from torch.utils.data import DataLoader, random_split
9
  from PIL import Image
10
- import matplotlib
11
- matplotlib.use('Agg') # Use non-interactive backend
12
- import matplotlib.pyplot as plt
13
- import seaborn as sns
14
- import numpy as np
15
- from sklearn.metrics import classification_report, confusion_matrix
16
  import tempfile
17
  import warnings
18
  warnings.filterwarnings("ignore")
19
 
20
- # Configuração
 
 
 
 
 
21
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
22
- print(f"🖥️ Device: {device}")
23
 
24
- # Modelos disponíveis
25
- MODELS = {
26
- 'ResNet18': models.resnet18,
27
- 'MobileNetV2': models.mobilenet_v2
28
- }
29
-
30
- # Estado global
31
- class AppState:
32
- def __init__(self):
33
- self.model = None
34
- self.train_loader = None
35
- self.val_loader = None
36
- self.test_loader = None
37
- self.dataset_path = None
38
- self.class_dirs = []
39
- self.class_labels = []
40
- self.num_classes = 2
41
-
42
- state = AppState()
43
-
44
- def setup_classes(num_classes_value):
45
- """Configura classes"""
46
- try:
47
- state.num_classes = int(num_classes_value)
48
- state.dataset_path = tempfile.mkdtemp()
49
- state.class_labels = [f'classe_{i}' for i in range(state.num_classes)]
50
-
51
- state.class_dirs = []
52
- for i in range(state.num_classes):
53
- class_dir = os.path.join(state.dataset_path, f'classe_{i}')
54
- os.makedirs(class_dir, exist_ok=True)
55
- state.class_dirs.append(class_dir)
56
-
57
- return f"✅ Criados {state.num_classes} diretórios"
58
- except Exception as e:
59
- return f"❌ Erro: {str(e)}"
60
-
61
- def set_class_labels(labels_text):
62
- """Define rótulos das classes (separados por vírgula)"""
63
- try:
64
- labels = [label.strip() for label in labels_text.split(',') if label.strip()]
65
-
66
- if len(labels) != state.num_classes:
67
- return f"❌ Forneça {state.num_classes} rótulos separados por vírgula"
68
-
69
- state.class_labels = labels
70
- return f"✅ Rótulos: {', '.join(state.class_labels)}"
71
- except Exception as e:
72
- return f"❌ Erro: {str(e)}"
73
 
74
- def upload_images(class_id, images):
75
- """Upload de imagens"""
 
 
 
 
 
 
76
  try:
77
- if not images:
78
- return "❌ Selecione imagens"
79
-
80
- class_idx = int(class_id)
81
- if class_idx >= len(state.class_dirs):
82
- return f"❌ Classe inválida"
83
 
84
- class_dir = state.class_dirs[class_idx]
85
- count = 0
 
 
 
86
 
87
- for image in images:
88
- if image is not None:
89
- shutil.copy2(image, class_dir)
90
- count += 1
91
-
92
- class_name = state.class_labels[class_idx]
93
- return f"✅ {count} imagens → {class_name}"
94
  except Exception as e:
95
  return f"❌ Erro: {str(e)}"
96
 
97
- def prepare_data(batch_size):
98
- """Prepara dados"""
 
 
99
  try:
100
- if not state.dataset_path:
101
- return "❌ Configure classes primeiro"
102
 
 
103
  transform = transforms.Compose([
104
  transforms.Resize((224, 224)),
105
  transforms.ToTensor(),
106
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
107
  ])
108
 
109
- dataset = datasets.ImageFolder(state.dataset_path, transform=transform)
 
110
 
111
- if len(dataset) < 6:
112
- return f"❌ Poucas imagens ({len(dataset)}). Mínimo: 6"
113
 
114
- # Divisão: 70% treino, 20% val, 10% teste
115
  train_size = int(0.7 * len(dataset))
116
- val_size = int(0.2 * len(dataset))
117
- test_size = len(dataset) - train_size - val_size
118
 
119
- train_dataset, val_dataset, test_dataset = random_split(
120
- dataset, [train_size, val_size, test_size],
121
- generator=torch.Generator().manual_seed(42)
122
- )
123
 
124
- state.train_loader = DataLoader(train_dataset, batch_size=int(batch_size), shuffle=True)
125
- state.val_loader = DataLoader(val_dataset, batch_size=int(batch_size), shuffle=False)
126
- state.test_loader = DataLoader(test_dataset, batch_size=int(batch_size), shuffle=False)
127
-
128
- return f"✅ Dados preparados:\n• Treino: {train_size}\n• Validação: {val_size}\n• Teste: {test_size}"
129
- except Exception as e:
130
- return f"❌ Erro: {str(e)}"
131
-
132
- def train_model(model_name, epochs, lr):
133
- """Treina modelo"""
134
- try:
135
- if state.train_loader is None:
136
- return "❌ Prepare os dados primeiro"
137
 
138
  # Carregar modelo
139
- state.model = MODELS[model_name](pretrained=True)
140
-
141
- # Adaptar camada final
142
- if hasattr(state.model, 'fc'):
143
- state.model.fc = nn.Linear(state.model.fc.in_features, state.num_classes)
144
- elif hasattr(state.model, 'classifier'):
145
- if isinstance(state.model.classifier, nn.Sequential):
146
- state.model.classifier[-1] = nn.Linear(state.model.classifier[-1].in_features, state.num_classes)
147
 
148
- state.model = state.model.to(device)
149
  criterion = nn.CrossEntropyLoss()
150
- optimizer = optim.Adam(state.model.parameters(), lr=float(lr))
151
-
152
- state.model.train()
153
- results = [f"🚀 Treinando {model_name}"]
154
 
155
- for epoch in range(int(epochs)):
156
- running_loss = 0.0
157
- correct = 0
158
- total = 0
159
-
160
- for inputs, labels in state.train_loader:
161
  inputs, labels = inputs.to(device), labels.to(device)
162
 
163
  optimizer.zero_grad()
164
- outputs = state.model(inputs)
165
  loss = criterion(outputs, labels)
166
  loss.backward()
167
  optimizer.step()
168
-
169
- running_loss += loss.item()
170
- _, predicted = torch.max(outputs, 1)
171
- total += labels.size(0)
172
- correct += (predicted == labels).sum().item()
173
-
174
- epoch_loss = running_loss / len(state.train_loader)
175
- epoch_acc = 100. * correct / total
176
- results.append(f"Época {epoch+1}: Loss={epoch_loss:.4f}, Acc={epoch_acc:.2f}%")
177
 
178
- results.append("✅ Treinamento concluído!")
179
- return "\n".join(results)
180
  except Exception as e:
181
  return f"❌ Erro: {str(e)}"
182
 
183
  def evaluate_model():
184
  """Avalia modelo"""
 
 
 
 
 
185
  try:
186
- if state.model is None or state.test_loader is None:
187
- return "❌ Modelo/dados não disponíveis"
188
-
189
- state.model.eval()
190
- all_preds = []
191
- all_labels = []
192
-
193
- with torch.no_grad():
194
- for inputs, labels in state.test_loader:
195
- inputs, labels = inputs.to(device), labels.to(device)
196
- outputs = state.model(inputs)
197
- _, preds = torch.max(outputs, 1)
198
- all_preds.extend(preds.cpu().numpy())
199
- all_labels.extend(labels.cpu().numpy())
200
-
201
- report = classification_report(
202
- all_labels, all_preds,
203
- target_names=state.class_labels,
204
- zero_division=0
205
- )
206
- return f"📊 RELATÓRIO:\n\n{report}"
207
- except Exception as e:
208
- return f"❌ Erro: {str(e)}"
209
-
210
- def generate_confusion_matrix():
211
- """Gera matriz de confusão"""
212
- try:
213
- if state.model is None or state.test_loader is None:
214
- return None
215
-
216
- state.model.eval()
217
- all_preds = []
218
- all_labels = []
219
 
220
  with torch.no_grad():
221
- for inputs, labels in state.test_loader:
222
  inputs, labels = inputs.to(device), labels.to(device)
223
- outputs = state.model(inputs)
224
- _, preds = torch.max(outputs, 1)
225
- all_preds.extend(preds.cpu().numpy())
226
- all_labels.extend(labels.cpu().numpy())
227
-
228
- cm = confusion_matrix(all_labels, all_preds)
229
-
230
- plt.figure(figsize=(8, 6))
231
- sns.heatmap(cm, annot=True, fmt="d", cmap="Blues",
232
- xticklabels=state.class_labels,
233
- yticklabels=state.class_labels)
234
- plt.xlabel('Predições')
235
- plt.ylabel('Valores Reais')
236
- plt.title('Matriz de Confusão')
237
- plt.tight_layout()
238
 
239
- temp_path = tempfile.NamedTemporaryFile(suffix='.png', delete=False).name
240
- plt.savefig(temp_path, dpi=150, bbox_inches='tight')
241
- plt.close()
242
 
243
- return temp_path
244
  except Exception as e:
245
- print(f"Erro matriz confusão: {e}")
246
- return None
247
 
248
- def predict_images(images):
249
- """Prediz imagens"""
 
 
 
 
 
 
 
 
250
  try:
251
- if state.model is None:
252
- return "❌ Treine o modelo primeiro"
253
-
254
- if not images:
255
- return "❌ Selecione imagens"
256
-
257
  transform = transforms.Compose([
258
  transforms.Resize((224, 224)),
259
  transforms.ToTensor(),
260
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
261
  ])
262
 
263
- state.model.eval()
264
- results = []
265
 
266
- for image_path in images:
267
- if image_path:
268
- image = Image.open(image_path).convert('RGB')
269
- img_tensor = transform(image).unsqueeze(0).to(device)
270
-
271
- with torch.no_grad():
272
- outputs = state.model(img_tensor)
273
- probs = torch.nn.functional.softmax(outputs[0], dim=0)
274
- _, predicted = torch.max(outputs, 1)
275
-
276
- class_id = predicted.item()
277
- confidence = probs[class_id].item() * 100
278
- class_name = state.class_labels[class_id]
279
-
280
- results.append(f"📸 {os.path.basename(image_path)}")
281
- results.append(f" 🎯 {class_name}")
282
- results.append(f" 📊 {confidence:.2f}%")
283
- results.append("-" * 30)
284
-
285
- return "\n".join(results) if results else "❌ Nenhuma predição"
286
  except Exception as e:
287
  return f"❌ Erro: {str(e)}"
288
 
289
- # Interface
290
- with gr.Blocks(title="🖼️ Classificador", theme=gr.themes.Soft()) as demo:
 
291
 
292
- gr.Markdown("""
293
- # 🖼️ Sistema de Classificação de Imagens
294
- **Instruções:** Configure → Upload → Treine → Avalie → Prediga
295
- """)
296
 
297
- with gr.Tab("1️⃣ Configuração"):
298
- gr.Markdown("### 🎯 Configurar Classes")
299
-
300
- num_classes = gr.Slider(
301
- minimum=2, maximum=5, value=2, step=1,
302
- label="Número de Classes"
303
- )
304
-
305
- setup_btn = gr.Button("🔧 Configurar", variant="primary")
306
- setup_status = gr.Textbox(label="Status", lines=2)
307
-
308
- gr.Markdown("### 🏷️ Definir Rótulos")
309
-
310
- labels_input = gr.Textbox(
311
- label="Rótulos (separados por vírgula)",
312
- placeholder="gato, cachorro",
313
- value="gato, cachorro"
314
- )
315
-
316
- labels_btn = gr.Button("🏷️ Definir Rótulos")
317
- labels_status = gr.Textbox(label="Status Rótulos")
318
-
319
- with gr.Tab("2️⃣ Upload"):
320
- gr.Markdown("### 📤 Upload de Imagens")
321
-
322
- class_selector = gr.Slider(
323
- minimum=0, maximum=1, value=0, step=1,
324
- label="Classe (0, 1, 2...)"
325
- )
326
-
327
- images_upload = gr.File(
328
- label="Imagens",
329
- file_count="multiple",
330
- file_types=["image"]
331
- )
332
-
333
- upload_btn = gr.Button("📤 Upload", variant="primary")
334
- upload_status = gr.Textbox(label="Status")
335
-
336
- with gr.Tab("3️⃣ Treinamento"):
337
- gr.Markdown("### ⚙️ Preparar Dados")
338
-
339
- batch_size = gr.Slider(1, 32, 8, step=1, label="Batch Size")
340
- prepare_btn = gr.Button("⚙️ Preparar", variant="primary")
341
- prepare_status = gr.Textbox(label="Status", lines=4)
342
-
343
- gr.Markdown("### 🚀 Treinar Modelo")
344
-
345
- with gr.Row():
346
- model_choice = gr.Radio(
347
- choices=list(MODELS.keys()),
348
- value="MobileNetV2",
349
- label="Modelo"
350
- )
351
- epochs = gr.Slider(1, 10, 3, step=1, label="Épocas")
352
- learning_rate = gr.Slider(0.0001, 0.01, 0.001, label="Learning Rate")
353
-
354
- train_btn = gr.Button("🚀 Treinar", variant="primary")
355
- train_status = gr.Textbox(label="Status Treinamento", lines=8)
356
-
357
- with gr.Tab("4️⃣ Avaliação"):
358
- gr.Markdown("### 📊 Avaliar Modelo")
359
-
360
- with gr.Row():
361
- eval_btn = gr.Button("📊 Avaliar", variant="primary")
362
- matrix_btn = gr.Button("📈 Matriz Confusão")
363
-
364
- eval_results = gr.Textbox(label="Relatório", lines=12)
365
- confusion_matrix_plot = gr.Image(label="Matriz de Confusão")
366
-
367
- with gr.Tab("5️⃣ Predição"):
368
- gr.Markdown("### 🔮 Predizer Novas Imagens")
369
-
370
- predict_images_input = gr.File(
371
- label="Imagens para Predição",
372
- file_count="multiple",
373
- file_types=["image"]
374
- )
375
-
376
- predict_btn = gr.Button("🔮 Predizer", variant="primary")
377
- predict_results = gr.Textbox(label="Resultados", lines=10)
378
 
379
  # Conectar eventos
380
- setup_btn.click(setup_classes, [num_classes], [setup_status])
381
- labels_btn.click(set_class_labels, [labels_input], [labels_status])
382
- upload_btn.click(upload_images, [class_selector, images_upload], [upload_status])
383
- prepare_btn.click(prepare_data, [batch_size], [prepare_status])
384
- train_btn.click(train_model, [model_choice, epochs, learning_rate], [train_status])
385
- eval_btn.click(evaluate_model, [], [eval_results])
386
- matrix_btn.click(generate_confusion_matrix, [], [confusion_matrix_plot])
387
- predict_btn.click(predict_images, [predict_images_input], [predict_results])
388
 
389
- if __name__ == "__main__":
390
- demo.launch()
 
7
  from torchvision import datasets, transforms, models
8
  from torch.utils.data import DataLoader, random_split
9
  from PIL import Image
 
 
 
 
 
 
10
  import tempfile
11
  import warnings
12
  warnings.filterwarnings("ignore")
13
 
14
+ # Estado global simples
15
+ model = None
16
+ train_loader = None
17
+ test_loader = None
18
+ dataset_path = None
19
+ class_names = ["classe_0", "classe_1"]
20
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
21
 
22
+ def setup_dataset():
23
+ """Cria estrutura de pastas"""
24
+ global dataset_path
25
+ dataset_path = tempfile.mkdtemp()
26
+
27
+ # Criar pastas para 2 classes
28
+ for i in range(2):
29
+ os.makedirs(os.path.join(dataset_path, f"classe_{i}"), exist_ok=True)
30
+
31
+ return f"✅ Dataset criado em: {dataset_path}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ def save_image(image, class_id):
34
+ """Salva uma imagem na classe especificada"""
35
+ if dataset_path is None:
36
+ return "❌ Execute 'Criar Dataset' primeiro"
37
+
38
+ if image is None:
39
+ return "❌ Selecione uma imagem"
40
+
41
  try:
42
+ class_dir = os.path.join(dataset_path, f"classe_{int(class_id)}")
 
 
 
 
 
43
 
44
+ # Salvar imagem
45
+ import time
46
+ filename = f"img_{int(time.time())}.jpg"
47
+ filepath = os.path.join(class_dir, filename)
48
+ image.save(filepath)
49
 
50
+ return f"✅ Imagem salva na classe {int(class_id)}"
 
 
 
 
 
 
51
  except Exception as e:
52
  return f"❌ Erro: {str(e)}"
53
 
54
+ def prepare_and_train():
55
+ """Prepara dados e treina modelo"""
56
+ global model, train_loader, test_loader
57
+
58
  try:
59
+ if dataset_path is None:
60
+ return "❌ Crie o dataset primeiro"
61
 
62
+ # Transformações
63
  transform = transforms.Compose([
64
  transforms.Resize((224, 224)),
65
  transforms.ToTensor(),
66
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
67
  ])
68
 
69
+ # Carregar dataset
70
+ dataset = datasets.ImageFolder(dataset_path, transform=transform)
71
 
72
+ if len(dataset) < 4:
73
+ return f"❌ Poucas imagens ({len(dataset)}). Adicione pelo menos 2 por classe."
74
 
75
+ # Dividir dados: 70% treino, 30% teste
76
  train_size = int(0.7 * len(dataset))
77
+ test_size = len(dataset) - train_size
 
78
 
79
+ train_dataset, test_dataset = random_split(dataset, [train_size, test_size])
 
 
 
80
 
81
+ train_loader = DataLoader(train_dataset, batch_size=4, shuffle=True)
82
+ test_loader = DataLoader(test_dataset, batch_size=4, shuffle=False)
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  # Carregar modelo
85
+ model = models.mobilenet_v2(pretrained=True)
86
+ model.classifier = nn.Sequential(
87
+ nn.Dropout(0.2),
88
+ nn.Linear(model.classifier[1].in_features, 2)
89
+ )
90
+ model = model.to(device)
 
 
91
 
92
+ # Treinar
93
  criterion = nn.CrossEntropyLoss()
94
+ optimizer = optim.Adam(model.parameters(), lr=0.001)
 
 
 
95
 
96
+ model.train()
97
+ for epoch in range(3): # Apenas 3 épocas
98
+ for inputs, labels in train_loader:
 
 
 
99
  inputs, labels = inputs.to(device), labels.to(device)
100
 
101
  optimizer.zero_grad()
102
+ outputs = model(inputs)
103
  loss = criterion(outputs, labels)
104
  loss.backward()
105
  optimizer.step()
 
 
 
 
 
 
 
 
 
106
 
107
+ return f"✅ Modelo treinado! Dataset: {train_size} treino, {test_size} teste"
108
+
109
  except Exception as e:
110
  return f"❌ Erro: {str(e)}"
111
 
112
  def evaluate_model():
113
  """Avalia modelo"""
114
+ global model, test_loader
115
+
116
+ if model is None or test_loader is None:
117
+ return "❌ Treine o modelo primeiro"
118
+
119
  try:
120
+ model.eval()
121
+ correct = 0
122
+ total = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  with torch.no_grad():
125
+ for inputs, labels in test_loader:
126
  inputs, labels = inputs.to(device), labels.to(device)
127
+ outputs = model(inputs)
128
+ _, predicted = torch.max(outputs, 1)
129
+ total += labels.size(0)
130
+ correct += (predicted == labels).sum().item()
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ accuracy = 100 * correct / total if total > 0 else 0
133
+ return f"📊 Acurácia: {accuracy:.2f}% ({correct}/{total})"
 
134
 
 
135
  except Exception as e:
136
+ return f"Erro: {str(e)}"
 
137
 
138
+ def predict_single_image(image):
139
+ """Prediz uma única imagem"""
140
+ global model
141
+
142
+ if model is None:
143
+ return "❌ Treine o modelo primeiro"
144
+
145
+ if image is None:
146
+ return "❌ Selecione uma imagem"
147
+
148
  try:
 
 
 
 
 
 
149
  transform = transforms.Compose([
150
  transforms.Resize((224, 224)),
151
  transforms.ToTensor(),
152
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
153
  ])
154
 
155
+ img_tensor = transform(image).unsqueeze(0).to(device)
 
156
 
157
+ model.eval()
158
+ with torch.no_grad():
159
+ outputs = model(img_tensor)
160
+ probs = torch.nn.functional.softmax(outputs[0], dim=0)
161
+ _, predicted = torch.max(outputs, 1)
162
+
163
+ class_id = predicted.item()
164
+ confidence = probs[class_id].item() * 100
165
+ class_name = class_names[class_id]
166
+
167
+ return f"🎯 Predição: {class_name}\n📊 Confiança: {confidence:.2f}%"
168
+
 
 
 
 
 
 
 
 
169
  except Exception as e:
170
  return f"❌ Erro: {str(e)}"
171
 
172
+ def set_class_names(name0, name1):
173
+ """Define nomes das classes"""
174
+ global class_names
175
 
176
+ if not name0.strip() or not name1.strip():
177
+ return "❌ Preencha ambos os nomes"
 
 
178
 
179
+ class_names = [name0.strip(), name1.strip()]
180
+ return f" Classes: {class_names[0]} e {class_names[1]}"
181
+
182
+ # Interface ultra-simples
183
+ with gr.Blocks(title="🖼️ Classificador Simples") as demo:
184
+
185
+ gr.Markdown("# 🖼️ Classificador de Imagens Simples")
186
+
187
+ with gr.Row():
188
+ with gr.Column():
189
+ gr.Markdown("### 1️⃣ Configurar Classes")
190
+ class_0_name = gr.Textbox(label="Nome Classe 0", value="gato")
191
+ class_1_name = gr.Textbox(label="Nome Classe 1", value="cachorro")
192
+ set_names_btn = gr.Button("🏷️ Definir Nomes")
193
+ names_status = gr.Textbox(label="Status")
194
+
195
+ gr.Markdown("### 2️⃣ Criar Dataset")
196
+ create_btn = gr.Button("🔧 Criar Dataset", variant="primary")
197
+ create_status = gr.Textbox(label="Status")
198
+
199
+ with gr.Column():
200
+ gr.Markdown("### 3️⃣ Adicionar Imagens")
201
+ upload_image = gr.Image(type="pil", label="Imagem")
202
+ class_selector = gr.Number(label="Classe (0 ou 1)", value=0, precision=0)
203
+ save_btn = gr.Button("💾 Salvar Imagem")
204
+ save_status = gr.Textbox(label="Status")
205
+
206
+ with gr.Row():
207
+ with gr.Column():
208
+ gr.Markdown("### 4️⃣ Treinar")
209
+ train_btn = gr.Button("🚀 Preparar + Treinar", variant="primary")
210
+ train_status = gr.Textbox(label="Status", lines=3)
211
+
212
+ eval_btn = gr.Button("📊 Avaliar")
213
+ eval_status = gr.Textbox(label="Resultado")
214
+
215
+ with gr.Column():
216
+ gr.Markdown("### 5️⃣ Predizer")
217
+ predict_image = gr.Image(type="pil", label="Imagem para Predição")
218
+ predict_btn = gr.Button("🔮 Predizer")
219
+ predict_result = gr.Textbox(label="Resultado")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
  # Conectar eventos
222
+ set_names_btn.click(set_class_names, [class_0_name, class_1_name], names_status)
223
+ create_btn.click(setup_dataset, outputs=create_status)
224
+ save_btn.click(save_image, [upload_image, class_selector], save_status)
225
+ train_btn.click(prepare_and_train, outputs=train_status)
226
+ eval_btn.click(evaluate_model, outputs=eval_status)
227
+ predict_btn.click(predict_single_image, predict_image, predict_result)
 
 
228
 
229
+ demo.launch()
 
requirements.txt CHANGED
@@ -1,8 +1,7 @@
1
- gradio==4.20.0
2
  torch==2.0.1
3
  torchvision==0.15.2
4
  scikit-learn==1.3.0
5
  matplotlib==3.7.1
6
- seaborn==0.12.2
7
  numpy==1.24.3
8
  Pillow==9.5.0
 
1
+ gradio==4.8.0
2
  torch==2.0.1
3
  torchvision==0.15.2
4
  scikit-learn==1.3.0
5
  matplotlib==3.7.1
 
6
  numpy==1.24.3
7
  Pillow==9.5.0