Spaces:
Sleeping
Sleeping
File size: 7,835 Bytes
2d2c58f 77dfc22 2d2c58f 77dfc22 2d2c58f c3bdd70 a5aa8ae 2d2c58f c3bdd70 77dfc22 b06b4b1 77dfc22 c3bdd70 77dfc22 c3bdd70 77dfc22 c3bdd70 77dfc22 c3bdd70 77dfc22 2d2c58f c3bdd70 77dfc22 c3bdd70 2d2c58f 77dfc22 2d2c58f c3bdd70 2d2c58f c3bdd70 2d2c58f c3bdd70 77dfc22 c3bdd70 2d2c58f c3bdd70 77dfc22 c3bdd70 2d2c58f c3bdd70 77dfc22 c3bdd70 77dfc22 c3bdd70 77dfc22 c3bdd70 2d2c58f 77dfc22 2d2c58f c3bdd70 2d2c58f c3bdd70 2d2c58f c3bdd70 2d2c58f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | # --- START OF FILE app.py ---
import gradio as gr
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import pytesseract
import re
import requests
import json
# --- MÓDULO 1: API WRAPPER E BIBLIOTECAS (Mantido) ---
OCTOPART_API_KEY = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA5NzI5QTkyRDU0RDlERjIyRDQzMENBMjNDNkI4QjJFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE3NTI2NTA3NDgsImV4cCI6MTc1MjczNzE0OCwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5uZXhhci5jb20iLCJjbGllbnRfaWQiOiI5NDJiY2U5NC1mYmYwLTRhM2YtODFlNS1iOWZhYjRkMzI2MDUiLCJzdWIiOiJBNDVBMzU5OS0xNDEwLTQ3NUMtQTg3RC04RDg4ODc5QTg4RTkiLCJhdXRoX3RpbWUiOjE3NTI2NTA0NDgsImlkcCI6Ikdvb2dsZSIsInByaXZhdGVfY2xhaW1zX2lkIjoiNzcwZDUwMWYtNjc5YS00YjVjLWFmYzctYzhlN2FhNzdkNTNmIiwicHJpdmF0ZV9jbGFpbXNfc2VjcmV0IjoiTWF5TnhaYjFxKzlEdis2UGJkMTlRZFp4OWQ4ek9sR0IrVjVvT2hGZGFmTT0iLCJqdGkiOiI4NUFGMkU3QUQ1MzI0MzAxNUZDRkQ1QjMyQzc0QjVDMSIsInNpZCI6Ijg4OUQ1RDM0MUMyQ0U4MjZDODFBMThFMURFODkxMjY0IiwiaWF0IjoxNzUyNjUwNzQ4LCJzY29wZSI6WyJvcGVuaWQiLCJ1c2VyLmFjY2VzcyIsInByb2ZpbGUiLCJlbWFpbCIsInVzZXIuZGV0YWlscyIsInN1cHBseS5kb21haW4iLCJkZXNpZ24uZG9tYWluIl0sImFtciI6WyJleHRlcm5hbCJdfQ.tLu5fClrokUwW-5W3crUIR8DXp5RiSbtduvz-RIT3RAaNCkuMYSQQaSGg9CtLF_LzlcNQ3a5vIKLT47aX1CQRnvc2M-Q_R28SP2-AuNqHs_O4UwRFoVsbuSQfa3enGCUpm7L2ZCtWtkW4AG38iwH97zBYAHryl6cMRmvi-dGmc6H26sSBAcg7G7sDbHlnxxjM_I7V3wSTzrQUAgkQcCsu2h_fxhEiWzWWiJ2SjNKZt6_gv3vnMWKI3Y4FC_zytv-Yf0dcBGtLhX8Z9o5DdB2V1uLXEF6VUsnl_zIz52-YuxUn0wuztnig3g0hy-fS6OnWW6zb3uI_A_mBNjsOJH36Q" # <-- SUBSTITUA PELA SUA CHAVE REAL
FOOTPRINT_LIBRARY = {
"DIP-8": {"size_mm": (9.8, 6.35), "pin_count": 8},
"DIP-16": {"size_mm": (19.3, 6.35), "pin_count": 16},
"TO-92": {"size_mm": (4.5, 4.5), "pin_count": 3},
"AXIAL-0.4": {"size_mm": (10.2, 3.6), "pin_count": 2},
"RADIAL-0.2": {"size_mm": (5, 5), "pin_count": 2},
"UNKNOWN": {"size_mm": (10, 10), "pin_count": 2}
}
def get_component_data_from_api(part_number):
if OCTOPART_API_KEY == "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA5NzI5QTkyRDU0RDlERjIyRDQzMENBMjNDNkI4QjJFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE3NTI2NTA3NDgsImV4cCI6MTc1MjczNzE0OCwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5uZXhhci5jb20iLCJjbGllbnRfaWQiOiI5NDJiY2U5NC1mYmYwLTRhM2YtODFlNS1iOWZhYjRkMzI2MDUiLCJzdWIiOiJBNDVBMzU5OS0xNDEwLTQ3NUMtQTg3RC04RDg4ODc5QTg4RTkiLCJhdXRoX3RpbWUiOjE3NTI2NTA0NDgsImlkcCI6Ikdvb2dsZSIsInByaXZhdGVfY2xhaW1zX2lkIjoiNzcwZDUwMWYtNjc5YS00YjVjLWFmYzctYzhlN2FhNzdkNTNmIiwicHJpdmF0ZV9jbGFpbXNfc2VjcmV0IjoiTWF5TnhaYjFxKzlEdis2UGJkMTlRZFp4OWQ4ek9sR0IrVjVvT2hGZGFmTT0iLCJqdGkiOiI4NUFGMkU3QUQ1MzI0MzAxNUZDRkQ1QjMyQzc0QjVDMSIsInNpZCI6Ijg4OUQ1RDM0MUMyQ0U4MjZDODFBMThFMURFODkxMjY0IiwiaWF0IjoxNzUyNjUwNzQ4LCJzY29wZSI6WyJvcGVuaWQiLCJ1c2VyLmFjY2VzcyIsInByb2ZpbGUiLCJlbWFpbCIsInVzZXIuZGV0YWlscyIsInN1cHBseS5kb21haW4iLCJkZXNpZ24uZG9tYWluIl0sImFtciI6WyJleHRlcm5hbCJdfQ.tLu5fClrokUwW-5W3crUIR8DXp5RiSbtduvz-RIT3RAaNCkuMYSQQaSGg9CtLF_LzlcNQ3a5vIKLT47aX1CQRnvc2M-Q_R28SP2-AuNqHs_O4UwRFoVsbuSQfa3enGCUpm7L2ZCtWtkW4AG38iwH97zBYAHryl6cMRmvi-dGmc6H26sSBAcg7G7sDbHlnxxjM_I7V3wSTzrQUAgkQcCsu2h_fxhEiWzWWiJ2SjNKZt6_gv3vnMWKI3Y4FC_zytv-Yf0dcBGtLhX8Z9o5DdB2V1uLXEF6VUsnl_zIz52-YuxUn0wuztnig3g0hy-fS6OnWW6zb3uI_A_mBNjsOJH36Q":
return None, "Erro: Chave de API da Octopart não configurada."
url = f"http://octopart.com/api/v3/parts/match?apikey={OCTOPART_API_KEY}&queries=[{{\"mpn\":\"{part_number}\"}}]"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
if data['results'] and data['results'][0]['hits'] > 0:
item = data['results'][0]['items'][0]
specs = item.get('specs', {})
footprint = specs.get('case_package', {}).get('value', ["Desconhecido"])[0]
datasheet_url = item.get('datasheet_url', "N/A")
return {'footprint': footprint, 'datasheet': datasheet_url}, None
except requests.exceptions.RequestException as e:
return None, f"Erro de API: {e}"
return None, f"Componente '{part_number}' não encontrado."
# --- MÓDULO 2: OCR CIRÚRGICO E ANÁLISE (A GRANDE MUDANÇA) ---
def analyze_schematic_with_surgical_ocr(image_pil):
if image_pil is None:
return Image.new('RGB', (1,1)), "Faça o upload de uma imagem."
image_cv = np.array(image_pil.convert("RGB"))
gray = cv2.cvtColor(image_cv, cv2.COLOR_BGR2GRAY)
# Pré-processamento da imagem para melhorar OCR e deteção de contornos
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
components = []
for cnt in contours:
# Ignorar contornos muito pequenos (ruído) ou muito grandes (bordas da imagem)
if not (50 < cv2.contourArea(cnt) < 50000):
continue
x, y, w, h = cv2.boundingRect(cnt)
# OCR CIRÚRGICO: Ler texto apenas na vizinhança do contorno
roi_x, roi_y = max(0, x - 30), max(0, y - 30)
roi_w, roi_h = w + 60, h + 60
roi = gray[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w]
text = pytesseract.image_to_string(roi, config='--psm 6').strip()
# Expressões Regulares mais abrangentes
identifier_match = re.search(r'([RUCTZDQ]\d{1,2})', text, re.IGNORECASE)
value_match = re.search(r'(\d{3,4}|[A-Z]{2}\d{4,5}|BC\d{3}|[0-9.]+[MUKPFVnµ])', text, re.IGNORECASE)
if identifier_match:
identifier = identifier_match.group(1).upper()
value = value_match.group(1).upper() if value_match else "N/A"
# Limpar valores
if "555" in value: value = "555"
if "CD4017" in value: value = "CD4017"
if "BC547" in value: value = "BC547"
components.append({
'id': identifier,
'value': value,
'box': (x, y, w, h)
})
# --- MÓDULO 3: CONSULTA À API E VISUALIZAÇÃO ---
log = ""
vis_image = image_cv.copy()
if not components:
return Image.new('RGB', (1,1)), "Nenhum componente detetado pelos contornos."
for comp in components:
log += f"--- Componente Detetado ---\n"
log += f" ID: {comp['id']}\n"
log += f" Valor/Nome: {comp['value']}\n"
# Desenha a caixa delimitadora do componente
x, y, w, h = comp['box']
cv2.rectangle(vis_image, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.putText(vis_image, f"{comp['id']}: {comp['value']}", (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
# O valor/nome é o que procuramos na API
part_number = comp['value']
api_data, error = get_component_data_from_api(part_number)
if error:
log += f" API: {error}\n"
else:
footprint = api_data.get('footprint', "Desconhecido")
datasheet = api_data.get('datasheet', "N/A")
log += f" Footprint (API): {footprint}\n"
log += f" Datasheet (API): {datasheet}\n"
return Image.fromarray(cv2.cvtColor(vis_image, cv2.COLOR_BGR2RGB)), log
# --- Interface Gradio ---
app = gr.Interface(
fn=analyze_schematic_with_surgical_ocr,
inputs=gr.Image(type="pil", label="Upload do Esquema Elétrico"),
outputs=[
gr.Image(type="pil", label="Análise do Esquema"),
gr.Textbox(label="Log da Análise (OCR + API)", lines=20)
],
title="Analisador de Esquemas com OCR Cirúrgico (v17)",
description="""**Esta versão usa uma abordagem de OCR muito mais precisa.**
1. **Deteta** a localização de cada símbolo de componente.
2. **Lê** o texto (identificador e valor) apenas na área próxima a cada símbolo.
3. **Consulta** a API da Octopart usando o valor/nome lido.
4. **Apresenta** um log detalhado da análise."""
)
if __name__ == "__main__":
app.launch() |