File size: 3,782 Bytes
3bb804c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

EEG Simulator Node - Generates a simulated multi-channel EEG signal

Place this file in the 'nodes' folder

"""

import numpy as np
from PyQt6 import QtGui
import cv2
import sys
import os
# --- This is the new, correct block ---
import __main__
BaseNode = __main__.BaseNode
PA_INSTANCE = getattr(__main__, "PA_INSTANCE", None)
# ------------------------------------

class EEGSimulatorNode(BaseNode):
    NODE_CATEGORY = "Source"
    NODE_COLOR = QtGui.QColor(40, 120, 80) # Source Green
    
    def __init__(self, sample_rate=250.0):
        super().__init__()
        self.node_title = "EEG Simulator"
        self.outputs = {'signal': 'signal'}
        
        self.sample_rate = sample_rate
        self.time = 0.0
        
        # Inspired by MNE channel names
        self.channels = ["Fp1", "Fp2", "C3", "C4", "O1", "O2", "T7", "T8"]
        self.selected_channel = self.channels[0]
        
        # Internal state for each channel's oscillators
        self.channel_state = {}
        for ch in self.channels:
            self.channel_state[ch] = {
                'phase': np.random.rand(4) * 2 * np.pi,
                'freqs': np.array([
                    np.random.uniform(2, 4),    # Delta
                    np.random.uniform(5, 8),    # Theta
                    np.random.uniform(9, 12),   # Alpha
                    np.random.uniform(15, 25)   # Beta
                ]),
                'amps': np.array([
                    np.random.uniform(0.5, 1.0),
                    np.random.uniform(0.2, 0.5),
                    np.random.uniform(0.1, 0.8), # Alpha can be strong
                    np.random.uniform(0.05, 0.2)
                ]) * 0.2 # Scale down
            }
        
        self.output_value = 0.0
        self.history = np.zeros(64) # For display

    def step(self):
        dt = 1.0 / self.sample_rate
        self.time += dt
        
        # Get the state for the selected channel
        state = self.channel_state[self.selected_channel]
        
        # Update phases
        state['phase'] += state['freqs'] * dt * 2 * np.pi
        
        # Compute sines
        sines = np.sin(state['phase'])
        
        # Modulate alpha rhythm (make it bursty)
        alpha_mod = (np.sin(self.time * 0.2 * 2 * np.pi) + 1.0) / 2.0 # Slow modulation
        sines[2] *= alpha_mod
        
        # Sum oscillators
        signal = np.dot(sines, state['amps'])
        
        # Add noise
        noise = (np.random.rand() - 0.5) * 0.1
        self.output_value = signal + noise
        
        # Update display history
        self.history[:-1] = self.history[1:]
        self.history[-1] = self.output_value

    def get_output(self, port_name):
        if port_name == 'signal':
            return self.output_value
        return None
        
    def get_display_image(self):
        w, h = 64, 64
        img = np.zeros((h, w), dtype=np.uint8)
        
        # Normalize history from [-1, 1] to [0, h-1]
        vis_data = (self.history + 1.0) / 2.0 * (h - 1)
        
        for i in range(w - 1):
            y1 = int(np.clip(vis_data[i], 0, h - 1))
            y2 = int(np.clip(vis_data[i+1], 0, h - 1))
            # Draw line segment
            img = cv2.line(img, (i, y1), (i+1, y2), (255, 255, 255), 1)

        img = np.ascontiguousarray(img)
        return QtGui.QImage(img.data, w, h, w, QtGui.QImage.Format.Format_Grayscale8)

    def get_config_options(self):
        # Create channel options for the dropdown menu
        channel_options = [(ch, ch) for ch in self.channels]
        
        return [
            ("Channel", "selected_channel", self.selected_channel, channel_options)
        ]