Ju-Am commited on
Commit
7a2e61d
·
1 Parent(s): c43c806
Files changed (3) hide show
  1. Dockerfile +2 -0
  2. main.py +70 -31
  3. requirements.txt +1 -6
Dockerfile CHANGED
@@ -1,7 +1,9 @@
1
  #Imagem base
2
  FROM python:3.10
3
 
 
4
  ENV HUGGINGFACE_HUB_CACHE="/tmp/huggingface"
 
5
 
6
  #Define um diretório de trabalho limpo
7
  WORKDIR /app
 
1
  #Imagem base
2
  FROM python:3.10
3
 
4
+ #Variáveis de Ambiente para Caches
5
  ENV HUGGINGFACE_HUB_CACHE="/tmp/huggingface"
6
+ ENV NUMBA_CACHE_DIR="/tmp/numba_cache"
7
 
8
  #Define um diretório de trabalho limpo
9
  WORKDIR /app
main.py CHANGED
@@ -1,24 +1,18 @@
1
  import os
2
  import shutil
 
 
3
  from fastapi import FastAPI, File, UploadFile, Depends, HTTPException, status
4
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
5
- from feature_extractor_single import process_single_image
6
 
7
- #Configuração da aplicação
8
- app = FastAPI(title="SojaFeatExtractorAPI")
9
-
10
- #Autenticação ---
11
  security = HTTPBearer()
12
-
13
- #1. Lê o Token Secreto da Variável de Ambiente
14
- #No teste local, vamos injetar isso com 'docker run -e ...'
15
- #No Hugging Face, vamos usar os "Secrets"
16
  API_SECRET_TOKEN = os.environ.get("API_SECRET_TOKEN")
17
 
18
  if API_SECRET_TOKEN is None:
19
  print("AVISO: Variável de ambiente API_SECRET_TOKEN não definida.")
20
- #Podemos lançar um erro aqui se não quisermos que a app inicie sem um token
21
- #raise ValueError("API_SECRET_TOKEN não pode ser nula")
22
 
23
  async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
24
  """Verifica se o token enviado pelo cliente é o correto."""
@@ -27,7 +21,6 @@ async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(secur
27
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
28
  detail="Token de segurança não configurado no servidor",
29
  )
30
-
31
  if credentials.scheme != "Bearer" or credentials.credentials != API_SECRET_TOKEN:
32
  raise HTTPException(
33
  status_code=status.HTTP_401_UNAUTHORIZED,
@@ -35,35 +28,81 @@ async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(secur
35
  headers={"WWW-Authenticate": "Bearer"},
36
  )
37
  return credentials.credentials
38
- #Fim da Autenticação
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
 
41
  @app.post("/extract_features/")
42
- #2. Adiciona 'Depends(verify_token)' para proteger endpoint
43
  async def extract_features(file: UploadFile = File(...), token: str = Depends(verify_token)):
44
  """
45
- Endpoint que recebe uma imagem, valida o token
46
- e retorna vetor de características extraído.
47
  """
48
- #A única pasta em um contêiner Docker que tem permissão de escrita garantida é a pasta /tmp
49
- #Precisamos direcionar todos os arquivos temporários para lá
50
  temp_path = f"/tmp/temp_{file.filename}"
51
-
52
  with open(temp_path, "wb") as buffer:
53
  shutil.copyfileobj(file.file, buffer)
54
 
55
- features_array = process_single_image(temp_path, output_dir="/tmp")
56
-
57
- #Remove imagem temporária
58
  os.remove(temp_path)
59
 
60
- #Converte o array Numpy (ex: (1536,)) em uma lista Python padrão
61
  features_list = features_array.tolist()
62
-
63
- #Retorna o vetor de features REAL no JSON
64
- return {"features": features_list}
65
-
66
- @app.get("/health", status_code=status.HTTP_200_OK)
67
- async def health_check():
68
- """Endpoint simples para verificar se a API está no ar."""
69
- return {"status": "ok"}
 
1
  import os
2
  import shutil
3
+ import joblib # <-- Importa o joblib
4
+ import numpy as np # <-- Importa o numpy
5
  from fastapi import FastAPI, File, UploadFile, Depends, HTTPException, status
6
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
7
+ from feature_extractor_single import process_single_image # Esta é a extração do ConvNext
8
 
9
+ #1. CONFIGURAÇÃO DA APLICAÇÃO E AUTENTICAÇÃO
10
+ app = FastAPI(title="SojaClassifierAPI")
 
 
11
  security = HTTPBearer()
 
 
 
 
12
  API_SECRET_TOKEN = os.environ.get("API_SECRET_TOKEN")
13
 
14
  if API_SECRET_TOKEN is None:
15
  print("AVISO: Variável de ambiente API_SECRET_TOKEN não definida.")
 
 
16
 
17
  async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
18
  """Verifica se o token enviado pelo cliente é o correto."""
 
21
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
22
  detail="Token de segurança não configurado no servidor",
23
  )
 
24
  if credentials.scheme != "Bearer" or credentials.credentials != API_SECRET_TOKEN:
25
  raise HTTPException(
26
  status_code=status.HTTP_401_UNAUTHORIZED,
 
28
  headers={"WWW-Authenticate": "Bearer"},
29
  )
30
  return credentials.credentials
31
+
32
+ #2. CARREGAMENTO DOS MODELOS DE CLASSIFICAÇÃO (ML)
33
+ #Carrega os 4 arquivos .pkl UMA VEZ quando a API inicia.
34
+ print("Carregando modelos de classificação (.pkl)...")
35
+ try:
36
+ SCALER = joblib.load('scaler.pkl')
37
+ UMAP = joblib.load('umap_reducer.pkl')
38
+ SVM = joblib.load('svm_model.pkl')
39
+ ENCODER = joblib.load('encoder.pkl')
40
+ print("Modelos de classificação carregados com sucesso.")
41
+ except FileNotFoundError:
42
+ print("ERRO: Arquivos .pkl do modelo não encontrados. Certifique-se de que 'scaler.pkl', 'umap_reducer.pkl', 'svm_model.pkl', e 'encoder.pkl' estão no repositório.")
43
+ #Em um cenário real, poderíamos impedir a API de iniciar aqui
44
+
45
+ #3. ENDPOINTS DA API
46
+
47
+ @app.post("/classify/")
48
+ async def classify_image(file: UploadFile = File(...), token: str = Depends(verify_token)):
49
+ """
50
+ Endpoint principal: Recebe uma imagem, extrai features e classifica.
51
+ """
52
+ temp_path = f"/tmp/temp_{file.filename}"
53
+
54
+ #Salva a imagem temporariamente
55
+ try:
56
+ with open(temp_path, "wb") as buffer:
57
+ shutil.copyfileobj(file.file, buffer)
58
+ except Exception as e:
59
+ raise HTTPException(status_code=500, detail=f"Erro ao salvar arquivo: {e}")
60
+
61
+ try:
62
+ #Extrai Features (ConvNext - 1536 dimensões)
63
+ #(process_single_image vem do seu feature_extractor_single.py)
64
+ features_array = process_single_image(temp_path)
65
+
66
+ #Prepara o vetor para o Scikit-learn
67
+ #O sklearn espera um array 2D (1, 1536) e não (1536,)
68
+ nova_feature = features_array.reshape(1, -1)
69
+
70
+ #Executa o Pipeline de Classificação (a lógica do seu notebook)
71
+ #Normaliza a nova feature com o mesmo scaler usado no treino
72
+ nova_feature_scaled = SCALER.transform(nova_feature)
73
+
74
+ #Reduz usando o mesmo UMAP já treinado
75
+ nova_feature_umap = UMAP.transform(nova_feature_scaled)
76
+
77
+ #Faz a predição
78
+ pred_numerica = SVM.predict(nova_feature_umap)
79
+
80
+ #Converte a predição numérica (ex: 2) de volta para o nome da classe (ex: "Ferrugem")
81
+ classe_predita = ENCODER.inverse_transform(pred_numerica)[0]
82
+
83
+ return {"diagnostico": classe_predita}
84
+
85
+ except Exception as e:
86
+ #Pega qualquer erro que acontecer durante a extração ou classificação
87
+ raise HTTPException(status_code=500, detail=f"Erro no processamento: {e}")
88
+
89
+ finally:
90
+ #Remove a imagem temporária, aconteça o que acontecer
91
+ if os.path.exists(temp_path):
92
+ os.remove(temp_path)
93
 
94
 
95
  @app.post("/extract_features/")
 
96
  async def extract_features(file: UploadFile = File(...), token: str = Depends(verify_token)):
97
  """
98
+ Endpoint de debug: Apenas extrai as features sem classificar.
 
99
  """
 
 
100
  temp_path = f"/tmp/temp_{file.filename}"
 
101
  with open(temp_path, "wb") as buffer:
102
  shutil.copyfileobj(file.file, buffer)
103
 
104
+ features_array = process_single_image(temp_path)
 
 
105
  os.remove(temp_path)
106
 
 
107
  features_list = features_array.tolist()
108
+ return {"features": features_list}
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -9,7 +9,6 @@ torchvision==0.17.2+cpu
9
  -f https://download.pytorch.org/whl/torch_stable.html
10
 
11
  transformers==4.39.3
12
- pretrainedmodels
13
 
14
  #Processamento de imagem
15
  pillow==10.3.0
@@ -21,8 +20,4 @@ umap-learn==0.5.6
21
  joblib==1.4.2
22
 
23
  #Utilitários gerais
24
- requests==2.32.3
25
- timm
26
- tdqm
27
- seaborn==0.13.2
28
- matplotlib==3.8.4
 
9
  -f https://download.pytorch.org/whl/torch_stable.html
10
 
11
  transformers==4.39.3
 
12
 
13
  #Processamento de imagem
14
  pillow==10.3.0
 
20
  joblib==1.4.2
21
 
22
  #Utilitários gerais
23
+ requests==2.32.3