joao-dutra ChristianSardo commited on
Commit
3430af6
·
verified ·
1 Parent(s): 76396cf

Create app2.py (#1)

Browse files

- Create app2.py (3f19888171c618d053f540f8c059d0f6beb8ba54)


Co-authored-by: Sardo <ChristianSardo@users.noreply.huggingface.co>

Files changed (1) hide show
  1. app2.py +231 -0
app2.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app2.py
2
+ import os
3
+ import uuid
4
+ import boto3
5
+ import supabase
6
+ import numpy as np
7
+ import cv2
8
+ import base64
9
+ import mimetypes
10
+ from fastapi import FastAPI, File, UploadFile, HTTPException
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from fastapi.responses import JSONResponse
13
+ from botocore.exceptions import NoCredentialsError
14
+
15
+ # ==============================================================================
16
+ # 1. CONFIGURAÇÃO E CLIENTES (CARREGADOS NA INICIALIZAÇÃO)
17
+ # ==============================================================================
18
+
19
+ # Carrega as configurações das variáveis de ambiente (Secrets do Hugging Face)
20
+ AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
21
+ AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
22
+ AWS_S3_BUCKET_NAME = os.getenv("AWS_S3_BUCKET_NAME")
23
+ AWS_S3_REGION = os.getenv("AWS_S3_REGION")
24
+ SUPABASE_URL = os.getenv("SUPABASE_URL")
25
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY")
26
+
27
+ # Validação para garantir que todos os secrets foram configurados
28
+ if not all([AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET_NAME, AWS_S3_REGION, SUPABASE_URL, SUPABASE_KEY]):
29
+ raise RuntimeError("Erro: Nem todos os secrets necessários foram configurados no Hugging Face Space.")
30
+
31
+ # Inicializa os clientes globais
32
+ try:
33
+ s3_client = boto3.client(
34
+ 's3',
35
+ aws_access_key_id=AWS_ACCESS_KEY_ID,
36
+ aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
37
+ region_name=AWS_S3_REGION
38
+ )
39
+ supabase_client = supabase.create_client(SUPABASE_URL, SUPABASE_KEY)
40
+ print("Clientes S3 e Supabase inicializados com sucesso.")
41
+ except Exception as e:
42
+ raise RuntimeError(f"Erro ao inicializar os clientes: {e}")
43
+
44
+ # Inicializa a aplicação FastAPI
45
+ app = FastAPI(title="Detector de Corrosão Branca com Salvamento S3/Supabase")
46
+
47
+ # Em produção, restrinja ao domínio do seu frontend
48
+ app.add_middleware(
49
+ CORSMiddleware,
50
+ allow_origins=["*"],
51
+ allow_credentials=True,
52
+ allow_methods=["*"],
53
+ allow_headers=["*"],
54
+ )
55
+
56
+ # ==============================================================================
57
+ # 2. FUNÇÕES HELPER (LÓGICA REUTILIZÁVEL)
58
+ # ==============================================================================
59
+
60
+ def process_image_bytes(img_bytes: bytes):
61
+ """Lógica de análise de imagem com OpenCV. Retorna um dict com métricas e a imagem de corrosão em numpy."""
62
+ nparr = np.frombuffer(img_bytes, np.uint8)
63
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
64
+ if img is None:
65
+ raise ValueError("Não foi possível decodificar a imagem.")
66
+
67
+ hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
68
+ lower_bg = np.array([0, 0, 0], dtype=np.uint8)
69
+ upper_bg = np.array([180, 255, 50], dtype=np.uint8)
70
+ mask_bg = cv2.inRange(hsv, lower_bg, upper_bg)
71
+ mask_obj = cv2.bitwise_not(mask_bg)
72
+ kernel = np.ones((5, 5), np.uint8)
73
+ mask_obj = cv2.morphologyEx(mask_obj, cv2.MORPH_OPEN, kernel)
74
+ mask_obj = cv2.morphologyEx(mask_obj, cv2.MORPH_CLOSE, kernel)
75
+ contours, _ = cv2.findContours(mask_obj, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
76
+ if not contours:
77
+ raise ValueError("Nenhum objeto principal detectado na imagem.")
78
+
79
+ largest = max(contours, key=cv2.contourArea)
80
+ mask_clean = np.zeros_like(mask_obj)
81
+ cv2.drawContours(mask_clean, [largest], -1, 255, cv2.FILLED)
82
+ isolated = cv2.bitwise_and(img, img, mask=mask_clean)
83
+ hsv_iso = cv2.cvtColor(isolated, cv2.COLOR_BGR2HSV)
84
+ lower_white = np.array([0, 0, 180], dtype=np.uint8)
85
+ upper_white = np.array([180, 60, 255], dtype=np.uint8)
86
+ mask_white = cv2.inRange(hsv_iso, lower_white, upper_white)
87
+ mask_white = cv2.bitwise_and(mask_white, mask_white, mask=mask_clean)
88
+
89
+ total_pixels = int(np.count_nonzero(mask_clean))
90
+ corrosion_pixels = int(np.count_nonzero(mask_white))
91
+ percent = (corrosion_pixels / max(1, total_pixels)) * 100.0
92
+
93
+ isolated_rgb = cv2.cvtColor(isolated, cv2.COLOR_BGR2RGB)
94
+ corrosion_vis_rgb = cv2.bitwise_and(isolated_rgb, isolated_rgb, mask=mask_white)
95
+
96
+ def to_data_uri(img_arr_rgb):
97
+ bgr = cv2.cvtColor(img_arr_rgb, cv2.COLOR_RGB2BGR)
98
+ ok, buf = cv2.imencode(".png", bgr)
99
+ if not ok: return None
100
+ b64 = base64.b64encode(buf.tobytes()).decode("ascii")
101
+ return f"data:image/png;base64,{b64}"
102
+
103
+ analysis_results = {
104
+ "percent": round(percent, 4),
105
+ "total_pixels": total_pixels,
106
+ "corrosion_pixels": corrosion_pixels,
107
+ "isolated_image": to_data_uri(isolated_rgb),
108
+ "corrosion_image": to_data_uri(corrosion_vis_rgb),
109
+ }
110
+
111
+ return analysis_results, corrosion_vis_rgb
112
+
113
+ # ==============================================================================
114
+ # 3. ENDPOINTS DA API
115
+ # ==============================================================================
116
+
117
+ @app.get("/")
118
+ def read_root():
119
+ return {"status": "ok", "message": "API de Análise de Corrosão no ar!"}
120
+
121
+ @app.post("/analyze")
122
+ async def analyze(file: UploadFile = File(...)):
123
+ """
124
+ Endpoint principal: Analisa a imagem, salva original e resultado no S3,
125
+ e persiste os dados no Supabase.
126
+ """
127
+ content = await file.read()
128
+ s3_key_original = None
129
+ s3_key_resultado = None
130
+
131
+ try:
132
+ # 1. Processa a imagem para obter métricas e a imagem de resultado
133
+ analysis_results, corrosion_image_np = process_image_bytes(content)
134
+
135
+ # 2. Faz o upload da IMAGEM ORIGINAL para o S3
136
+ content_type = file.content_type or 'application/octet-stream'
137
+ extensao = mimetypes.guess_extension(content_type) or '.jpg'
138
+ s3_key_original = f"imagens_originais/{uuid.uuid4()}{extensao}"
139
+
140
+ print(f"Fazendo upload da imagem original para: {s3_key_original}")
141
+ s3_client.put_object(
142
+ Bucket=AWS_S3_BUCKET_NAME,
143
+ Key=s3_key_original,
144
+ Body=content,
145
+ ContentType=content_type
146
+ )
147
+
148
+ # 3. Faz o upload da IMAGEM DE RESULTADO (em memória) para o S3
149
+ s3_key_resultado = f"imagens_resultados/{uuid.uuid4()}.png"
150
+
151
+ print(f"Fazendo upload da imagem de resultado para: {s3_key_resultado}")
152
+ bgr_result = cv2.cvtColor(corrosion_image_np, cv2.COLOR_RGB2BGR)
153
+ ok, buffer = cv2.imencode('.png', bgr_result)
154
+ if not ok:
155
+ raise ValueError("Falha ao codificar a imagem de resultado para PNG.")
156
+
157
+ s3_client.put_object(
158
+ Bucket=AWS_S3_BUCKET_NAME,
159
+ Key=s3_key_resultado,
160
+ Body=buffer.tobytes(),
161
+ ContentType='image/png'
162
+ )
163
+
164
+ # 4. Salva os metadados e as chaves S3 no Supabase
165
+ print("Inserindo registro no Supabase...")
166
+ dados_para_inserir = {
167
+ "nome_amostra": file.filename,
168
+ "percentual_corrosao": analysis_results["percent"],
169
+ "pixels_totais_obj": analysis_results["total_pixels"],
170
+ "pixels_corrosao": analysis_results["corrosion_pixels"],
171
+ "imagem_original": s3_key_original,
172
+ "imagem_resultado": s3_key_resultado
173
+ }
174
+ response = supabase_client.from_("amostras").insert(dados_para_inserir).execute()
175
+
176
+ # Adiciona o ID do novo registro à resposta
177
+ new_record_id = response.data[0]['id']
178
+ analysis_results['database_id'] = new_record_id
179
+
180
+ return JSONResponse(content=analysis_results)
181
+
182
+ except Exception as e:
183
+ # Lógica de Rollback: Se algo falhar, tenta limpar os arquivos do S3
184
+ print(f"ERRO no processo de análise: {e}")
185
+ if s3_key_original:
186
+ print(f"Removendo objeto órfão do S3: {s3_key_original}")
187
+ s3_client.delete_object(Bucket=AWS_S3_BUCKET_NAME, Key=s3_key_original)
188
+ if s3_key_resultado:
189
+ print(f"Removendo objeto órfão do S3: {s3_key_resultado}")
190
+ s3_client.delete_object(Bucket=AWS_S3_BUCKET_NAME, Key=s3_key_resultado)
191
+
192
+ raise HTTPException(status_code=500, detail=f"Ocorreu um erro interno: {e}")
193
+
194
+
195
+ @app.get("/samples/{sample_id}")
196
+ async def get_sample_images(sample_id: int):
197
+ """
198
+ Endpoint de recuperação: Busca uma amostra pelo ID e retorna
199
+ links temporários (pré-assinados) para suas imagens no S3.
200
+ """
201
+ try:
202
+ print(f"Buscando amostra com ID: {sample_id}...")
203
+ response = supabase_client.from_("amostras").select("imagem_original, imagem_resultado").eq("id", sample_id).single().execute()
204
+
205
+ if not response.data:
206
+ raise HTTPException(status_code=404, detail=f"Amostra com ID {sample_id} não encontrada.")
207
+
208
+ amostra = response.data
209
+ s3_key_original = amostra.get("imagem_original")
210
+ s3_key_resultado = amostra.get("imagem_resultado")
211
+
212
+ links = {}
213
+
214
+ if s3_key_original:
215
+ links['url_original'] = s3_client.generate_presigned_url(
216
+ 'get_object',
217
+ Params={'Bucket': AWS_S3_BUCKET_NAME, 'Key': s3_key_original},
218
+ ExpiresIn=3600 # 1 hora
219
+ )
220
+
221
+ if s3_key_resultado:
222
+ links['url_resultado'] = s3_client.generate_presigned_url(
223
+ 'get_object',
224
+ Params={'Bucket': AWS_S3_BUCKET_NAME, 'Key': s3_key_resultado},
225
+ ExpiresIn=3600 # 1 hora
226
+ )
227
+
228
+ return JSONResponse(content=links)
229
+
230
+ except Exception as e:
231
+ raise HTTPException(status_code=500, detail=f"Ocorreu um erro interno ao buscar os links: {e}")