Luis-Filipe commited on
Commit
77dfc22
·
verified ·
1 Parent(s): 5a9277c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -110
app.py CHANGED
@@ -3,138 +3,120 @@
3
  import gradio as gr
4
  import cv2
5
  import numpy as np
6
- from PIL import Image
7
  import pytesseract
8
  import re
 
 
9
 
10
- # Função para encontrar todas as caixas retangulares (componentes como ICs, resistores)
11
- def find_component_boxes(image):
12
- # Converte para escala de cinza e aplica um limiar para isolar formas
13
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
14
- _, thresh = cv2.threshold(gray, 230, 255, cv2.THRESH_BINARY_INV)
15
-
16
- # Encontra contornos
17
- contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
18
-
19
- boxes = []
20
- for cnt in contours:
21
- # Aproxima o contorno a um polígono
22
- approx = cv2.approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True)
23
- # Filtra por área para remover ruído
24
- if cv2.contourArea(cnt) > 100:
25
- x, y, w, h = cv2.boundingRect(approx)
26
- # Componentes são tipicamente retangulares, não muito finos nem muito quadrados
27
- aspect_ratio = w / float(h)
28
- if 1.2 < aspect_ratio < 10 or 0.1 < aspect_ratio < 0.8:
29
- boxes.append((x, y, w, h))
30
- return boxes
31
 
32
- # Função para remover os componentes e isolar as trilhas
33
- def find_wires(image, boxes):
34
- # Converte para escala de cinza e aplica um limiar
35
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
36
- _, thresh = cv2.threshold(gray, 230, 255, cv2.THRESH_BINARY_INV)
37
 
38
- # Cria uma máscara para apagar os componentes
39
- component_mask = np.zeros_like(thresh)
40
- for (x, y, w, h) in boxes:
41
- # Desenha retângulos preenchidos para apagar os componentes
42
- cv2.rectangle(component_mask, (x, y), (x + w, y + h), 255, -1)
43
-
44
- # Dilata a máscara para também apagar o texto próximo
45
- component_mask = cv2.dilate(component_mask, np.ones((10,10)), iterations=2)
46
 
47
- # Subtrai os componentes, deixando apenas as trilhas
48
- wires_mask = cv2.subtract(thresh, component_mask)
49
-
50
- # Limpeza final para remover pequenas linhas e ruído
51
- wires_mask = cv2.morphologyEx(wires_mask, cv2.MORPH_OPEN, np.ones((3,1)))
52
- wires_mask = cv2.morphologyEx(wires_mask, cv2.MORPH_OPEN, np.ones((1,3)))
 
 
 
 
 
 
 
 
53
 
54
- return wires_mask
55
 
56
- # Função para fazer OCR perto de cada caixa de componente
57
- def ocr_labels(image, boxes):
58
- labels = []
59
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
60
-
61
- for (x, y, w, h) in boxes:
62
- # Define uma região de interesse (ROI) expandida à volta da caixa
63
- roi_x = max(0, x - 40)
64
- roi_y = max(0, y - 40)
65
- roi_w = w + 80
66
- roi_h = h + 80
67
- roi = gray[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w]
68
-
69
- # Usa Tesseract para ler o texto na ROI
70
- text = pytesseract.image_to_string(roi, config='--psm 6')
71
-
72
- # Filtra e limpa o texto para encontrar identificadores (ex: R1, C1, U1)
73
- # Usamos uma expressão regular para encontrar padrões como "letra + número"
74
- matches = re.findall(r'[A-Z]\d{1,2}', text, re.IGNORECASE)
75
- if matches:
76
- # Pega no primeiro identificador encontrado
77
- label = matches[0].upper()
78
- labels.append((label, (x + w // 2, y + h // 2))) # Guarda o identificador e o centro da caixa
79
-
80
- return labels
81
 
82
- def analyze_schematic(image_pil):
 
 
83
  if image_pil is None:
84
- return Image.new('RGB', (512, 512)), "Faça o upload de uma imagem."
85
 
86
- # Converte a imagem PIL para o formato do OpenCV
87
  image_cv = np.array(image_pil.convert("RGB"))
88
- image_cv = cv2.cvtColor(image_cv, cv2.COLOR_RGB2BGR)
89
-
90
- # Etapa 1: Deteção de Componentes
91
- component_boxes = find_component_boxes(image_cv)
92
 
93
- # Etapa 2: Deteção de Trilhas (Wires)
94
- wires_mask = find_wires(image_cv, component_boxes)
 
 
 
95
 
96
- # Etapa 3: Leitura de Texto (OCR)
97
- found_labels = ocr_labels(image_cv, component_boxes)
98
-
99
- # Etapa 4: Visualização dos Resultados
100
- vis_image = image_cv.copy()
101
-
102
- # Desenha as trilhas detetadas em Ciano
103
- vis_image[wires_mask > 0] = [255, 255, 0]
104
 
105
- # Desenha as caixas dos componentes em Vermelho
106
- for (x, y, w, h) in component_boxes:
107
- cv2.rectangle(vis_image, (x, y), (x + w, y + h), (0, 0, 255), 2)
 
 
 
 
 
 
 
 
108
 
109
- # Escreve os identificadores lidos por OCR em Verde
110
- for label, center in found_labels:
111
- cv2.putText(vis_image, label, (center[0], center[1] - 15),
112
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
113
-
114
- # Geração de um "pseudo-netlist" em texto
115
- netlist_str = "--- Netlist Parcial (Componentes Detetados) ---\n"
116
- for label, (cx, cy) in found_labels:
117
- netlist_str += f"Componente: {label} encontrado em ({cx}, {cy})\n"
118
- if not found_labels:
119
- netlist_str += "Nenhum identificador de componente foi lido com sucesso via OCR."
120
 
121
- return Image.fromarray(cv2.cvtColor(vis_image, cv2.COLOR_BGR2RGB)), netlist_str
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- # Configuração da Interface Gradio
124
  app = gr.Interface(
125
- fn=analyze_schematic,
126
  inputs=gr.Image(type="pil", label="Upload do Esquema Elétrico"),
127
  outputs=[
128
- gr.Image(type="pil", label="Visualização da Análise"),
129
- gr.Textbox(label="Netlist Parcial / Log de Deteção", lines=10)
130
  ],
131
- title="Analisador de Esquemas Elétricos (Conceito)",
132
- description="""Faça o upload de uma imagem de um esquema elétrico. A aplicação irá tentar "ler" o diagrama usando técnicas de visão computacional.
133
-
134
- **Legenda da Visualização:**
135
- - **Vermelho:** Caixas delimitadoras de componentes detetados.
136
- - **Ciano:** Linhas de conexão (trilhas/fios) detetadas.
137
- - **Verde:** Identificadores dos componentes lidos via OCR (ex: R1, U1)."""
138
  )
139
 
140
  if __name__ == "__main__":
 
3
  import gradio as gr
4
  import cv2
5
  import numpy as np
6
+ from PIL import Image, ImageDraw, ImageFont
7
  import pytesseract
8
  import re
9
+ import requests
10
+ import json
11
 
12
+ # --- MÓDULO 1: API WRAPPER PARA A OCTOPART ---
13
+ OCTOPART_API_KEY = "SUA_CHAVE_API_AQUI" # <-- SUBSTITUA PELA SUA CHAVE REAL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ def get_component_data_from_api(part_number):
16
+ """Consulta a API da Octopart para obter dados de um componente."""
17
+ if OCTOPART_API_KEY == "SUA_CHAVE_API_AQUI":
18
+ return None, "Erro: Chave de API da Octopart não configurada."
 
19
 
20
+ url = f"http://octopart.com/api/v3/parts/match?apikey={OCTOPART_API_KEY}"
21
+ queries = [{'mpn': part_number}]
 
 
 
 
 
 
22
 
23
+ try:
24
+ response = requests.post(url, data=json.dumps({'queries': queries}))
25
+ response.raise_for_status()
26
+ data = response.json()
27
+
28
+ if data['results'] and data['results'][0]['hits'] > 0:
29
+ part_info = data['results'][0]['items'][0]
30
+ # Extrair o nome do footprint (case) e a URL da ficha técnica
31
+ specs = part_info.get('specs', {})
32
+ footprint = specs.get('case_package', {}).get('value', ["Desconhecido"])[0]
33
+ datasheet_url = part_info.get('datasheet_url', "N/A")
34
+ return {'footprint': footprint, 'datasheet': datasheet_url}, None
35
+ except requests.exceptions.RequestException as e:
36
+ return None, f"Erro de API: {e}"
37
 
38
+ return None, f"Componente '{part_number}' não encontrado."
39
 
40
+ # --- MÓDULO 2: BIBLIOTECA LOCAL DE FOOTPRINTS ---
41
+ # Mapeia o nome do footprint (obtido da API) para a sua geometria.
42
+ FOOTPRINT_LIBRARY = {
43
+ "SOIC-14": {"size_mm": (8.65, 3.9), "pin_count": 14},
44
+ "DIP-28": {"size_mm": (35.56, 7.62), "pin_count": 28},
45
+ "0805": {"size_mm": (2.0, 1.25), "pin_count": 2}, # Footprint para resistores/capacitores SMD
46
+ "UNKNOWN": {"size_mm": (10, 10), "pin_count": 2} # Default
47
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ # --- MÓDULO 3: PIPELINE DE ANÁLISE E GERAÇÃO ---
50
+
51
+ def analyze_and_generate_from_api(image_pil):
52
  if image_pil is None:
53
+ return Image.new('RGB', (1,1)), "Faça o upload de uma imagem."
54
 
 
55
  image_cv = np.array(image_pil.convert("RGB"))
 
 
 
 
56
 
57
+ # 1. OCR para extrair nomes de componentes (simplificado)
58
+ text = pytesseract.image_to_string(image_cv)
59
+ # Procurar por nomes de componentes comuns
60
+ part_numbers = re.findall(r'LM\d{3}|ATMEGA\d{3,4}P?|NE\d{3}', text, re.IGNORECASE)
61
+ part_numbers = list(set([p.upper() for p in part_numbers])) # Remove duplicados
62
 
63
+ if not part_numbers:
64
+ return Image.new('RGB', (1,1)), "Nenhum nome de componente reconhecido via OCR."
65
+
66
+ log = ""
67
+ placed_components = {}
 
 
 
68
 
69
+ # 2. Consultar a API e a biblioteca de footprints
70
+ for i, pn in enumerate(part_numbers):
71
+ log += f"A procurar por '{pn}'...\n"
72
+ api_data, error = get_component_data_from_api(pn)
73
+ if error:
74
+ log += f" > {error}\n"
75
+ continue
76
+
77
+ footprint_name = api_data.get('footprint', "UNKNOWN").upper()
78
+ # Simplificação: assume que footprints como "SOIC-14" estão na nossa biblioteca.
79
+ footprint_key = next((k for k in FOOTPRINT_LIBRARY if k in footprint_name), "UNKNOWN")
80
 
81
+ footprint_data = FOOTPRINT_LIBRARY[footprint_key]
82
+ log += f" > Encontrado! Footprint: {footprint_name} -> {footprint_key}\n"
83
+ log += f" > Ficha Técnica: {api_data.get('datasheet')}\n"
84
+
85
+ # 3. Placement simples
86
+ placed_components[pn] = {
87
+ 'center': (150 + i * 200, 200),
88
+ 'footprint': footprint_data
89
+ }
 
 
90
 
91
+ # 4. Geração do Layout Visual
92
+ layout_img = Image.new('RGB', (800, 600), '#003300')
93
+ draw = ImageDraw.Draw(layout_img)
94
+ DPI = 96; MM_TO_PIXEL = DPI / 25.4
95
+
96
+ for name, data in placed_components.items():
97
+ cx, cy = data['center']
98
+ size_mm = data['footprint']['size_mm']
99
+ w, h = int(size_mm[0] * MM_TO_PIXEL), int(size_mm[1] * MM_TO_PIXEL)
100
+
101
+ draw.rectangle([cx-w//2, cy-h//2, cx+w//2, cy+h//2], outline='gold', width=2)
102
+ draw.text((cx, cy-h//2 - 10), name, fill='white', anchor="ms")
103
+
104
+ return layout_img, log
105
 
106
+ # --- Interface Gradio ---
107
  app = gr.Interface(
108
+ fn=analyze_and_generate_from_api,
109
  inputs=gr.Image(type="pil", label="Upload do Esquema Elétrico"),
110
  outputs=[
111
+ gr.Image(type="pil", label="Layout com Footprints do Mundo Real"),
112
+ gr.Textbox(label="Log da API / Análise", lines=15)
113
  ],
114
+ title="Gerador de PCB com Dados Reais da API Octopart (v16)",
115
+ description="""**Esta aplicação demonstra um fluxo EDA profissional.**
116
+ 1. **Lê** os nomes dos componentes no esquemático com OCR.
117
+ 2. **Consulta** a API da Octopart na internet para obter dados reais.
118
+ 3. **Extrai** o nome do footprint (ex: SOIC-14) e a ficha técnica.
119
+ 4. **Usa** uma biblioteca local para obter a geometria desse footprint e desenha-o na placa."""
 
120
  )
121
 
122
  if __name__ == "__main__":