# --- 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()