Sinal cerebral vira luz. EsquizoCap lê EEG de um BITalino, transforma o dado em uma cor via um modelo de machine learning e acende uma fita de LED em tempo real — para uma instalação de arte que expõe atividade eletrofisiológica como experiência visual.
⚠️ Projeto Windows-only, de uso artístico/experimental e não é um dispositivo médico.
🧑 BITalino (EEG)
│ Bluetooth
▼
📡 OpenSignals ──publica──▶ Lab Streaming Layer (LSL)
│
▼
🐍 EsquizoCap (Python)
│ lê o sinal (µV) ──▶ prevê uma cor (HSV) ──▶ envia via serial
▼
💡 Arduino + fita de LED
- O BITalino capta o sinal eletrofisiológico e o OpenSignals publica esse sinal como um stream LSL (a aplicação nunca fala com o BITalino diretamente).
- O EsquizoCap lê esse stream e obtém uma métrica bruta, de um de dois jeitos — Amplitude ou Frequência (ver abaixo).
- Um modelo de árvore de decisão (scikit-learn) prevê um matiz (HUE) a partir dessa métrica. Saturação e brilho são ajustados ao vivo pelo usuário, na interface.
- A cor HSV resultante é convertida e enviada por porta serial a um Arduino, que comanda a fita de LED.
| Modo | Como funciona | Quando usar |
|---|---|---|
| Amplitude | Cada amostra bruta (µV) já lida do BITalino vira uma predição de cor. Uma leitura, uma cor. | Resposta mais imediata; a cor reage a cada instante do sinal. |
| Frequência | Um bloco de amostras é acumulado, filtrado (Butterworth passa-alta + passa-baixa) e analisado por densidade espectral (Welch). A frequência dominante do bloco vira a predição, e é também classificada numa banda de EEG humana (Delta, Theta, Alpha, Beta, Gamma). | Quando o que importa é o estado geral do sinal (relaxamento, atenção, etc.), não o instante. |
Em ambos os modos, só o matiz vem do modelo. Saturação e brilho são escolhas do usuário, feitas ao vivo por medidores na interface — o modelo nunca decide a intensidade ou a pureza da cor, só qual cor.
O projeto segue uma separação em camadas, cada uma com uma responsabilidade e sem depender da que vem depois dela na lista:
src/esquizocap/
├── dominio/ 🧮 As regras de negócio. Sem GUI, sem hardware, sem threading.
├── hardware/ 🔌 As bordas físicas: BITalino e Arduino, cada um com uma
│ implementação REAL e uma FAKE (para rodar sem hardware).
├── aplicacao/ 🧵 Orquestração: a thread que roda o domínio e publica
│ resultados para a interface, sem travar a tela.
├── infraestrutura/ ⚙️ Logging, configuração, persistência (Excel), assets.
└── interface/ 🖼️ A GUI, em PySide6/QML.
dominio/é o coração:CicloAquisicaoexecuta ler → pré-processar → prever → distribuir, e devolve umResultadoCiclo. Não sabe que existe interface gráfica nem que existe hardware de verdade — por isso é a camada mais fácil de testar.hardware/define contratos (ControladorLedArduino,LeitorBitalino) como classes abstratas. Cada um tem uma implementação real (arduino_real.py,bitalino_real.py) e uma fake (arduino_fake.py,bitalino_fake.py) que simula um EEG sintético plausível — é o que permite rodar e testar o projeto inteiro sem nenhum hardware plugado.aplicacao/existe porque a aquisição de um sinal biológico não pode depender do loop de eventos de uma interface gráfica: ela roda numa thread própria, publicando eventos (EventoResultado,EventoErro,EventoParado) numa fila que a GUI apenas drena e pinta.
Para o mapa completo de módulos e as decisões de arquitetura por trás disso, veja
CLAUDE.md e ARCHITECTURE.md.
Assume Windows, Python 3.12+, e que os comandos rodam a partir da raiz do projeto (o app usa caminhos relativos para
logs\,models\etc.).
# 1. Ambiente virtual
python -m venv .venv
.venv\Scripts\activate
# 2. Instalar dependências
pip install -r requirements.txt
# 3. Instalar o projeto em modo editável — OBRIGATÓRIO
# (o código está em layout src/; sem isto, `import esquizocap` não resolve)
pip install -e .
# 4. Rodar
python main.pyPara testar a interface, os modos de análise ou o modelo sem BITalino nem Arduino plugados, troque a borda real pela simulada. Pelo app, sem terminal: abra Configurações (ícone ☰ no trilho esquerdo) → aba Simulação, e ligue Arduino e/ou BITalino. A troca vale na hora, exige tudo desconectado, e é lembrada na próxima abertura.
Com a simulação ativa, um selo âmbar aparece na barra de topo e os indicadores ARD/BIT ganham um anel âmbar. A execução é idêntica à real em todo o resto.
Pela linha de comando — útil em scripts e na CI, e tem precedência sobre a escolha feita no app (que então aparece travada, com o motivo na tela):
$env:ESQUIZOCAP_FAKE="1" # tudo simulado
$env:ESQUIZOCAP_FAKE="arduino" # só o Arduino simulado
$env:ESQUIZOCAP_FAKE="bitalino" # só o BITalino simulado
python main.pyO BITalino simulado gera uma senoide de 10 Hz (banda Alpha) com ruído gaussiano — sinal plausível o bastante para exercitar a análise espectral de ponta a ponta. Ele responde igual pelos dois modos de aquisição, então o seletor de modo fica desabilitado: a escolha não teria efeito, e fingir que tem seria pior.
Também dá para rodar só o núcleo, sem abrir janela nenhuma:
python scripts/rodar_ciclo_sem_gui.py --modo Frequência --ciclos 4- Abra o OpenSignals manualmente e ative o compartilhamento "Lab Streaming Layer" — a aplicação não abre o OpenSignals sozinha, e não funciona sem esse passo.
- Rode
python main.py. - Na interface: escolha o modelo → configure e conecte o Arduino → configure o BITalino → "Começar aquisição".
BITalino lê 32.4 µV no canal escolhido
│
▼ (modo Amplitude)
modelo.predict([[32.4]]) → HUE = 187
│
▼ + Saturação=255, Brilho=120 (medidores da interface)
hsv_para_rgb_hex(187, 255, 120) → "#0078F0", RGB=(0, 120, 240)
│
▼
Arduino recebe: "(2,187,255,120)\n"
│
▼
💡 fita de LED acende em azul
- Testes automatizados (
pytest) cobremdominio/,hardware/eaplicacao/— incluindo testes de regressão para bugs já corrigidos (escala de frequência, canal ignorado, atraso de leitura do LSL) e testes de concorrência da thread de aquisição. - Tipos estritos (
mypy --strict) nas mesmas três camadas. - Lint e formatação via
ruff.
pytest # testes
mypy # checagem de tipos
ruff check . # lint
ruff format --check . # formataçãoPython · PySide6/QML · scikit-learn · pylsl (Lab Streaming Layer) · pyserial · scipy · pandas/openpyxl · pytest · mypy · ruff