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