Spaces:
Running
Running
| """ | |
| Adaptive Eigenfield Node | |
| ======================== | |
| "The field becomes the limiting factor." | |
| This node synthesizes several key insights: | |
| 1. From selfconsistentresonantloopnode: harmonics naturally produce structure | |
| (1→block, 2→complex, 6→star, higher→breakdown). The system should derive | |
| harmonics from the signal, not hardcode them. | |
| 2. From best.py: stable patterns can be detected and tracked. When coherent | |
| regions persist, they become "cells" - exactly like morphogenetic fields. | |
| 3. From the Raj paper: brain eigenmodes are conserved low-frequency patterns | |
| that govern diffusion. Low eigenmodes = coarse structure, high = fine detail. | |
| 4. From the DNA/THz papers: resonant frequencies emerge from geometry and coupling. | |
| The system has natural frequencies determined by its structure. | |
| The node: | |
| - Derives num_waves from spectral peaks in input (adaptive harmonics) | |
| - Computes graph Laplacian eigenmodes for field topology | |
| - Projects eigenmodes onto the field with amplitude/phase from signal | |
| - Detects stable coherent regions (cells) | |
| - Zoom selects which eigenmodes dominate (low zoom = slow modes, high = fast) | |
| - Field limits harmony at high complexity (biological reality) | |
| CREATED: December 2025 | |
| AUTHORS: Antti + Claude | |
| """ | |
| import numpy as np | |
| import cv2 | |
| from collections import deque | |
| from scipy.fft import fft2, ifft2, fftshift, fft, fftfreq | |
| from scipy.ndimage import gaussian_filter, label, binary_erosion, binary_dilation | |
| from scipy.signal import find_peaks | |
| from scipy.sparse import diags | |
| from scipy.sparse.linalg import eigsh | |
| # --- HOST COMMUNICATION --- | |
| import __main__ | |
| try: | |
| BaseNode = __main__.BaseNode | |
| QtGui = __main__.QtGui | |
| except AttributeError: | |
| from PyQt6 import QtGui | |
| class BaseNode: | |
| def __init__(self): | |
| self.inputs = {} | |
| self.outputs = {} | |
| def get_blended_input(self, name, mode): | |
| return None | |
| class StablePattern: | |
| """A detected stable structure - like a cell or coherent domain""" | |
| def __init__(self, id, mask, position, volume, phase_coherence): | |
| self.id = id | |
| self.mask = mask.copy() | |
| self.position = position # Center of mass | |
| self.volume = volume | |
| self.phase_coherence = phase_coherence | |
| self.age = 0 | |
| self.color = np.random.rand(3) # For visualization | |
| def update(self, new_mask=None, new_position=None, new_coherence=None): | |
| if new_mask is not None: | |
| self.mask = new_mask.copy() | |
| if new_position is not None: | |
| self.position = new_position | |
| if new_coherence is not None: | |
| self.phase_coherence = new_coherence | |
| self.age += 1 | |
| class AdaptiveEigenfieldNode(BaseNode): | |
| NODE_CATEGORY = "Consciousness" | |
| NODE_TITLE = "Adaptive Eigenfield" | |
| NODE_COLOR = QtGui.QColor(180, 100, 255) # Purple for eigenmodes | |
| def __init__(self): | |
| super().__init__() | |
| self.node_title = "Adaptive Eigenfield (Signal-Derived Eigenmodes)" | |
| self.inputs = { | |
| 'eeg_signal': 'signal', # Raw signal to buffer | |
| 'eeg_spectrum': 'spectrum', # Direct spectrum input (6-band) | |
| 'frequency_input': 'spectrum', # Alternative spectrum | |
| 'zoom': 'signal', # Eigenmode selection (0=slow only, 1=all) | |
| 'coupling': 'signal', # Field coupling strength | |
| 'damping': 'signal', # Energy dissipation | |
| 'tension': 'signal', # Wave propagation speed | |
| 'topology': 'signal', # 0=box, 1=torus | |
| 'reset': 'signal' | |
| } | |
| self.outputs = { | |
| 'display': 'image', | |
| 'field': 'complex_spectrum', # The main eigenfield | |
| 'eigenspectrum': 'spectrum', # Current eigenvalues | |
| 'num_modes': 'signal', # Number of active eigenmodes | |
| 'num_patterns': 'signal', # Detected stable patterns | |
| 'criticality': 'signal', # Edge of chaos metric | |
| 'total_energy': 'signal', | |
| 'pattern_field': 'image', # Visualization of stable patterns | |
| } | |
| # Field parameters | |
| self.field_size = 128 | |
| self.dt = 0.1 | |
| self.damping = 0.001 | |
| self.tension = 5.0 | |
| self.coupling = 0.5 | |
| self.zoom = 0.5 # 0 = show only slowest modes, 1 = all modes | |
| self.topology = 'box' # 'box' or 'torus' | |
| # Signal processing | |
| self.buffer_size = 512 | |
| self.sample_rate = 160.0 | |
| self.signal_buffer = deque(maxlen=self.buffer_size) | |
| # Eigenmode system | |
| self.max_modes = 32 # Maximum number of eigenmodes to compute | |
| self._eigenvectors = None | |
| self._eigenvalues = None | |
| self._mode_amplitudes = np.zeros(self.max_modes) | |
| self._mode_phases = np.zeros(self.max_modes) | |
| self._num_active_modes = 6 # Derived from signal peaks | |
| # Initialize eigenmodes | |
| self._compute_laplacian_eigenmodes() | |
| # Field state (like best.py) | |
| self.field = np.zeros((self.field_size, self.field_size), dtype=np.complex128) | |
| self.field_prev = np.zeros_like(self.field) | |
| self._init_field() | |
| # Stable pattern tracking | |
| self.patterns = {} | |
| self.next_pattern_id = 1 | |
| self.pattern_mask = np.zeros((self.field_size, self.field_size), dtype=bool) | |
| self.last_detection_time = 0 | |
| # Metrics | |
| self.total_energy = 0.0 | |
| self.energy_history = deque(maxlen=200) | |
| self.criticality = 0.0 | |
| self.criticality_history = deque(maxlen=200) | |
| # Display | |
| self._display = np.zeros((600, 900, 3), dtype=np.uint8) | |
| self.epoch = 0 | |
| def _compute_laplacian_eigenmodes(self): | |
| """Compute eigenmodes of the 2D grid Laplacian""" | |
| n = self.field_size | |
| n_sq = n * n | |
| # Build sparse Laplacian matrix for 2D grid | |
| # Each point connected to 4 neighbors (or wrapped for torus) | |
| main_diag = np.ones(n_sq) * 4 | |
| off_diag = np.ones(n_sq - 1) * -1 | |
| # Handle row boundaries (no connection across rows) | |
| for i in range(n - 1, n_sq - 1, n): | |
| off_diag[i] = 0 | |
| row_diag = np.ones(n_sq - n) * -1 | |
| L = diags([main_diag, off_diag, off_diag, row_diag, row_diag], | |
| [0, -1, 1, -n, n], format='csr') | |
| # Compute smallest eigenvalues (slowest modes) | |
| try: | |
| eigenvalues, eigenvectors = eigsh(L.astype(np.float64), | |
| k=min(self.max_modes, n_sq - 2), | |
| which='SM') | |
| self._eigenvalues = eigenvalues | |
| self._eigenvectors = eigenvectors | |
| except Exception as e: | |
| print(f"Eigenmode computation failed: {e}") | |
| # Fallback to simple sine modes | |
| self._eigenvalues = np.arange(1, self.max_modes + 1).astype(float) | |
| self._eigenvectors = np.zeros((n_sq, self.max_modes)) | |
| for m in range(self.max_modes): | |
| # Simple standing wave approximation | |
| kx = (m % 8) + 1 | |
| ky = (m // 8) + 1 | |
| x = np.arange(n) | |
| X, Y = np.meshgrid(x, x) | |
| mode = np.sin(np.pi * kx * X / n) * np.sin(np.pi * ky * Y / n) | |
| self._eigenvectors[:, m] = mode.flatten() | |
| def _init_field(self): | |
| """Initialize field with small random perturbation""" | |
| n = self.field_size | |
| c = n // 2 | |
| r = n // 6 | |
| X, Y = np.meshgrid(np.arange(n), np.arange(n)) | |
| # Gaussian seed + small noise | |
| self.field = 0.5 * np.exp(-((X - c)**2 + (Y - c)**2) / (2 * r**2)) | |
| self.field = self.field.astype(np.complex128) | |
| self.field += (np.random.randn(n, n) + 1j * np.random.randn(n, n)) * 0.05 | |
| self.field_prev = self.field.copy() | |
| def _derive_modes_from_signal(self): | |
| """Derive number of active modes from spectral peaks in input""" | |
| if len(self.signal_buffer) < self.buffer_size // 4: | |
| return | |
| try: | |
| sig = np.array(list(self.signal_buffer)) | |
| sig = sig - np.mean(sig) | |
| if np.std(sig) < 1e-10: | |
| return | |
| # FFT | |
| spectrum = np.abs(fft(sig * np.hanning(len(sig)))) | |
| freqs = fftfreq(len(sig), 1.0 / self.sample_rate) | |
| # Only positive frequencies | |
| pos_mask = freqs > 0 | |
| spectrum_pos = spectrum[pos_mask] | |
| if len(spectrum_pos) == 0: | |
| return | |
| # Find peaks | |
| threshold = np.mean(spectrum_pos) * 1.5 | |
| peaks, properties = find_peaks(spectrum_pos, height=threshold, distance=5) | |
| # Number of significant peaks determines mode count | |
| num_peaks = len(peaks) | |
| if num_peaks == 0: | |
| self._num_active_modes = 2 # Minimum | |
| elif num_peaks == 1: | |
| self._num_active_modes = 4 | |
| elif num_peaks <= 3: | |
| self._num_active_modes = 6 | |
| elif num_peaks <= 6: | |
| self._num_active_modes = 12 | |
| else: | |
| self._num_active_modes = min(num_peaks * 2, self.max_modes) | |
| # Set mode amplitudes from peak heights | |
| self._mode_amplitudes[:] = 0 | |
| if num_peaks > 0: | |
| heights = properties['peak_heights'] | |
| max_height = np.max(heights) if len(heights) > 0 else 1.0 | |
| for i, (peak_idx, height) in enumerate(zip(peaks, heights)): | |
| if i < self.max_modes: | |
| self._mode_amplitudes[i] = height / max_height | |
| # Phase from signal phase at that frequency | |
| self._mode_phases[i] = np.angle(fft(sig)[pos_mask][peak_idx]) if peak_idx < len(spectrum_pos) else 0 | |
| except Exception as e: | |
| pass # Keep previous mode count | |
| def _project_eigenmodes_to_field(self): | |
| """Project active eigenmodes onto the 2D field with zoom-based selection""" | |
| if self._eigenvectors is None: | |
| return np.zeros((self.field_size, self.field_size), dtype=np.complex128) | |
| n = self.field_size | |
| result = np.zeros((n, n), dtype=np.complex128) | |
| # Zoom determines which modes are active | |
| # zoom=0: only mode 0 (slowest) | |
| # zoom=1: all modes up to num_active | |
| max_mode_idx = max(1, int(self.zoom * self._num_active_modes)) | |
| max_mode_idx = min(max_mode_idx, self._eigenvectors.shape[1]) | |
| for m in range(max_mode_idx): | |
| if m >= len(self._mode_amplitudes): | |
| break | |
| amp = self._mode_amplitudes[m] | |
| phase = self._mode_phases[m] | |
| if amp < 1e-6: | |
| amp = 0.1 # Default amplitude for unset modes | |
| # Get eigenmode and reshape to 2D | |
| if m < self._eigenvectors.shape[1]: | |
| mode_1d = self._eigenvectors[:, m] | |
| mode_2d = mode_1d.reshape(n, n) | |
| # Add with amplitude and phase | |
| result += amp * mode_2d * np.exp(1j * phase) | |
| # Normalize | |
| max_val = np.max(np.abs(result)) | |
| if max_val > 1e-10: | |
| result /= max_val | |
| return result | |
| def _step_field_physics(self): | |
| """Evolve field with wave equation physics (inspired by best.py)""" | |
| n = self.field_size | |
| # Get current eigenmode projection | |
| eigenmode_contribution = self._project_eigenmodes_to_field() | |
| # Boundary mode based on topology | |
| mode = 'wrap' if self.topology == 'torus' else 'reflect' | |
| # Laplacian via convolution | |
| kernel = np.array([[0, 1, 0], | |
| [1, -4, 1], | |
| [0, 1, 0]], dtype=np.float64) | |
| # Apply to real and imaginary parts | |
| lap_real = cv2.filter2D(np.real(self.field).astype(np.float64), -1, kernel, | |
| borderType=cv2.BORDER_WRAP if mode == 'wrap' else cv2.BORDER_REFLECT) | |
| lap_imag = cv2.filter2D(np.imag(self.field).astype(np.float64), -1, kernel, | |
| borderType=cv2.BORDER_WRAP if mode == 'wrap' else cv2.BORDER_REFLECT) | |
| lap = lap_real + 1j * lap_imag | |
| # Wave equation with damping | |
| # φ_new = 2φ - φ_old + dt²(c²∇²φ - V'(φ)) - damping*(φ - φ_old) | |
| # Non-linear potential (encourages phase coherence) | |
| mag = np.abs(self.field) | |
| V_prime = -self.field + 0.2 * self.field * mag**2 | |
| # Wave speed modulated by tension | |
| c2 = self.tension / (1.0 + mag**2 + 1e-6) | |
| acc = c2 * lap - V_prime | |
| # Velocity | |
| vel = self.field - self.field_prev | |
| # Update | |
| field_new = (self.field + (1 - self.damping * self.dt) * vel + | |
| self.dt**2 * acc) | |
| # Couple in eigenmode structure | |
| field_new = (1 - self.coupling * 0.01) * field_new + self.coupling * 0.01 * eigenmode_contribution | |
| # Store history | |
| self.field_prev = self.field.copy() | |
| self.field = field_new | |
| # Normalize to prevent blowup | |
| max_mag = np.max(np.abs(self.field)) | |
| if max_mag > 5.0: | |
| self.field /= (max_mag / 5.0) | |
| def _detect_stable_patterns(self): | |
| """Detect coherent regions in the field (cells)""" | |
| # Only run periodically | |
| import time | |
| current_time = time.time() | |
| if current_time - self.last_detection_time < 0.3: | |
| return | |
| self.last_detection_time = current_time | |
| n = self.field_size | |
| # Coherence = local phase consistency | |
| phase = np.angle(self.field) | |
| # Compute local phase variance (low = coherent) | |
| phase_blurred = gaussian_filter(phase, sigma=3) | |
| phase_diff = np.abs(phase - phase_blurred) | |
| coherence = 1.0 - np.clip(phase_diff / np.pi, 0, 1) | |
| # Also consider magnitude | |
| mag = np.abs(self.field) | |
| mag_norm = mag / (np.max(mag) + 1e-10) | |
| # Pattern mask: high coherence AND significant magnitude | |
| pattern_criterion = coherence * mag_norm | |
| binary_mask = pattern_criterion > 0.5 | |
| # Clean up | |
| binary_mask = binary_erosion(binary_mask, iterations=1) | |
| binary_mask = binary_dilation(binary_mask, iterations=2) | |
| # Label connected components | |
| labeled, num_features = label(binary_mask) | |
| # Track patterns | |
| active_ids = set() | |
| min_volume = 20 | |
| for i in range(1, num_features + 1): | |
| component_mask = (labeled == i) | |
| volume = np.sum(component_mask) | |
| if volume < min_volume: | |
| continue | |
| # Get centroid | |
| coords = np.where(component_mask) | |
| position = (np.mean(coords[0]), np.mean(coords[1])) | |
| # Mean phase coherence in this region | |
| region_coherence = np.mean(coherence[component_mask]) | |
| # Try to match with existing pattern | |
| matched = False | |
| closest_id = None | |
| min_dist = float('inf') | |
| for pid, pattern in self.patterns.items(): | |
| dist = np.sqrt((pattern.position[0] - position[0])**2 + | |
| (pattern.position[1] - position[1])**2) | |
| if dist < min_dist: | |
| min_dist = dist | |
| closest_id = pid | |
| if closest_id is not None and min_dist < 15: | |
| self.patterns[closest_id].update(component_mask, position, region_coherence) | |
| active_ids.add(closest_id) | |
| matched = True | |
| if not matched: | |
| new_id = self.next_pattern_id | |
| self.next_pattern_id += 1 | |
| self.patterns[new_id] = StablePattern(new_id, component_mask, position, | |
| volume, region_coherence) | |
| active_ids.add(new_id) | |
| # Age out patterns not detected | |
| to_remove = [] | |
| for pid in self.patterns: | |
| if pid not in active_ids: | |
| self.patterns[pid].age -= 2 | |
| if self.patterns[pid].age <= 0: | |
| to_remove.append(pid) | |
| for pid in to_remove: | |
| del self.patterns[pid] | |
| # Update global mask | |
| self.pattern_mask = np.zeros((n, n), dtype=bool) | |
| for pattern in self.patterns.values(): | |
| self.pattern_mask |= pattern.mask | |
| def _compute_metrics(self): | |
| """Compute energy and criticality metrics""" | |
| # Energy | |
| mag = np.abs(self.field) | |
| grad_x = np.gradient(np.real(self.field), axis=0) | |
| grad_y = np.gradient(np.real(self.field), axis=1) | |
| kinetic = 0.5 * np.sum(np.abs(self.field - self.field_prev)**2) | |
| potential = 0.5 * np.sum(grad_x**2 + grad_y**2) | |
| self.total_energy = kinetic + potential | |
| self.energy_history.append(self.total_energy) | |
| # Criticality: variance of energy history (high variance = critical) | |
| if len(self.energy_history) > 10: | |
| energy_arr = np.array(list(self.energy_history)) | |
| mean_e = np.mean(energy_arr) | |
| if mean_e > 1e-10: | |
| self.criticality = np.std(energy_arr) / mean_e | |
| else: | |
| self.criticality = 0.0 | |
| self.criticality = np.clip(self.criticality, 0, 1) | |
| self.criticality_history.append(self.criticality) | |
| def step(self): | |
| self.epoch += 1 | |
| # Get inputs | |
| reset = self.get_blended_input('reset', 'sum') | |
| if reset is not None and reset > 0.5: | |
| self._init_field() | |
| self.patterns = {} | |
| self.next_pattern_id = 1 | |
| self.energy_history.clear() | |
| self.criticality_history.clear() | |
| return | |
| # Update parameters from inputs | |
| zoom_in = self.get_blended_input('zoom', 'sum') | |
| if zoom_in is not None: | |
| self.zoom = np.clip(float(zoom_in), 0, 1) | |
| coupling_in = self.get_blended_input('coupling', 'sum') | |
| if coupling_in is not None: | |
| self.coupling = np.clip(float(coupling_in), 0, 1) | |
| damping_in = self.get_blended_input('damping', 'sum') | |
| if damping_in is not None: | |
| self.damping = np.clip(float(damping_in), 0, 0.1) | |
| tension_in = self.get_blended_input('tension', 'sum') | |
| if tension_in is not None: | |
| self.tension = np.clip(float(tension_in), 0.1, 20) | |
| topology_in = self.get_blended_input('topology', 'sum') | |
| if topology_in is not None: | |
| self.topology = 'torus' if float(topology_in) > 0.5 else 'box' | |
| # Buffer signal | |
| sig_in = self.get_blended_input('eeg_signal', 'sum') | |
| if sig_in is not None: | |
| if isinstance(sig_in, np.ndarray): | |
| for s in sig_in.flatten()[:10]: | |
| self.signal_buffer.append(float(s)) | |
| else: | |
| self.signal_buffer.append(float(sig_in)) | |
| # Process spectrum input | |
| spectrum_in = self.get_blended_input('eeg_spectrum', 'sum') | |
| if spectrum_in is None: | |
| spectrum_in = self.get_blended_input('frequency_input', 'sum') | |
| if spectrum_in is not None and isinstance(spectrum_in, np.ndarray): | |
| # Use spectrum peaks to set mode amplitudes directly | |
| spec = np.abs(spectrum_in) | |
| if len(spec) > 0: | |
| max_spec = np.max(spec) | |
| if max_spec > 1e-10: | |
| spec = spec / max_spec | |
| for i, val in enumerate(spec[:self.max_modes]): | |
| self._mode_amplitudes[i] = val | |
| self._num_active_modes = max(2, min(len(spec), self.max_modes)) | |
| else: | |
| # Derive modes from buffered signal | |
| self._derive_modes_from_signal() | |
| # Physics step | |
| self._step_field_physics() | |
| # Pattern detection | |
| self._detect_stable_patterns() | |
| # Metrics | |
| self._compute_metrics() | |
| # Update display | |
| self._update_display() | |
| def _update_display(self): | |
| """Generate visualization""" | |
| img = np.zeros((600, 900, 3), dtype=np.uint8) | |
| # Main field visualization (left side) | |
| field_size_display = 256 | |
| # Field magnitude with phase as hue | |
| mag = np.abs(self.field) | |
| phase = np.angle(self.field) | |
| mag_norm = mag / (np.max(mag) + 1e-10) | |
| hsv = np.zeros((self.field_size, self.field_size, 3), dtype=np.uint8) | |
| hsv[:, :, 0] = ((phase + np.pi) / (2 * np.pi) * 180).astype(np.uint8) | |
| hsv[:, :, 1] = 200 | |
| hsv[:, :, 2] = (mag_norm * 255).astype(np.uint8) | |
| field_color = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) | |
| field_resized = cv2.resize(field_color, (field_size_display, field_size_display)) | |
| img[20:20 + field_size_display, 20:20 + field_size_display] = field_resized | |
| cv2.putText(img, "EIGENFIELD (Phase=Hue, Mag=Bright)", (20, 15), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1) | |
| # Pattern overlay | |
| if np.any(self.pattern_mask): | |
| pattern_overlay = np.zeros((self.field_size, self.field_size, 3), dtype=np.uint8) | |
| for pattern in self.patterns.values(): | |
| c = (pattern.color * 255).astype(np.uint8) | |
| pattern_overlay[pattern.mask] = c | |
| pattern_resized = cv2.resize(pattern_overlay, (field_size_display, field_size_display)) | |
| # Blend | |
| alpha = 0.3 | |
| img[20:20 + field_size_display, 20:20 + field_size_display] = \ | |
| cv2.addWeighted(field_resized, 1 - alpha, pattern_resized, alpha, 0) | |
| # Eigenspectrum visualization (right top) | |
| spec_x, spec_y = 300, 30 | |
| spec_w, spec_h = 250, 100 | |
| cv2.putText(img, f"ACTIVE EIGENMODES (n={self._num_active_modes})", (spec_x, spec_y - 5), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 150), 1) | |
| if self._eigenvalues is not None: | |
| num_show = min(self._num_active_modes, len(self._eigenvalues)) | |
| max_amp = max(np.max(self._mode_amplitudes[:num_show]), 1e-10) | |
| bar_width = max(3, spec_w // num_show - 2) | |
| for i in range(num_show): | |
| x = spec_x + i * (bar_width + 2) | |
| amp = self._mode_amplitudes[i] / max_amp if i < len(self._mode_amplitudes) else 0 | |
| height = int(amp * spec_h) | |
| # Color by eigenvalue (slow=red, fast=blue) | |
| hue = int(120 * (i / max(num_show - 1, 1))) # Green to cyan | |
| color = cv2.cvtColor(np.array([[[hue, 200, 200]]], dtype=np.uint8), | |
| cv2.COLOR_HSV2BGR)[0, 0] | |
| cv2.rectangle(img, (x, spec_y + spec_h - height), | |
| (x + bar_width, spec_y + spec_h), | |
| tuple(int(c) for c in color), -1) | |
| # Zoom indicator | |
| zoom_y = spec_y + spec_h + 30 | |
| cv2.putText(img, f"ZOOM: {self.zoom:.2f}", (spec_x, zoom_y), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1) | |
| cv2.rectangle(img, (spec_x + 80, zoom_y - 10), | |
| (spec_x + 80 + int(self.zoom * 100), zoom_y), | |
| (100, 200, 255), -1) | |
| cv2.putText(img, "low=coarse structure | high=fine detail", (spec_x, zoom_y + 20), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.3, (150, 150, 150), 1) | |
| # Metrics panel | |
| metrics_x, metrics_y = 300, 200 | |
| cv2.putText(img, "METRICS", (metrics_x, metrics_y), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 150), 1) | |
| cv2.putText(img, f"Energy: {self.total_energy:.2f}", (metrics_x, metrics_y + 25), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) | |
| cv2.putText(img, f"Criticality: {self.criticality:.3f}", (metrics_x, metrics_y + 50), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 200, 100), 1) | |
| bar_w = int(self.criticality * 150) | |
| cv2.rectangle(img, (metrics_x, metrics_y + 55), | |
| (metrics_x + bar_w, metrics_y + 65), | |
| (100, 200, 255), -1) | |
| cv2.putText(img, f"Stable Patterns: {len(self.patterns)}", (metrics_x, metrics_y + 90), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (100, 255, 100), 1) | |
| cv2.putText(img, f"Topology: {self.topology.upper()}", (metrics_x, metrics_y + 115), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1) | |
| # Pattern list | |
| pattern_x, pattern_y = 300, 340 | |
| cv2.putText(img, "DETECTED CELLS (age/coherence):", (pattern_x, pattern_y), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 150), 1) | |
| for i, (pid, pattern) in enumerate(list(self.patterns.items())[:8]): | |
| c = (pattern.color * 255).astype(np.uint8) | |
| y = pattern_y + 20 + i * 20 | |
| cv2.rectangle(img, (pattern_x, y - 10), (pattern_x + 15, y + 5), | |
| tuple(int(x) for x in c), -1) | |
| cv2.putText(img, f"#{pid}: age={pattern.age}, coh={pattern.phase_coherence:.2f}", | |
| (pattern_x + 20, y), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.35, (200, 200, 200), 1) | |
| # Criticality history | |
| hist_x, hist_y = 20, 320 | |
| hist_w, hist_h = 250, 80 | |
| cv2.putText(img, "CRITICALITY HISTORY", (hist_x, hist_y), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 150), 1) | |
| if len(self.criticality_history) > 2: | |
| hist = np.array(list(self.criticality_history)) | |
| hist = hist / (np.max(hist) + 1e-10) | |
| for i in range(1, len(hist)): | |
| x1 = hist_x + int((i - 1) / len(hist) * hist_w) | |
| x2 = hist_x + int(i / len(hist) * hist_w) | |
| y1 = hist_y + 10 + hist_h - int(hist[i - 1] * hist_h) | |
| y2 = hist_y + 10 + hist_h - int(hist[i] * hist_h) | |
| cv2.line(img, (x1, y1), (x2, y2), (100, 200, 255), 1) | |
| # Theory notes | |
| theory_y = 450 | |
| cv2.putText(img, "ADAPTIVE EIGENFIELD HYPOTHESIS:", (20, theory_y), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (150, 200, 150), 1) | |
| cv2.putText(img, "Eigenmodes derived from input signal spectrum, not hardcoded.", | |
| (20, theory_y + 20), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.35, (120, 150, 120), 1) | |
| cv2.putText(img, f"Simple signal -> few modes -> blocks. Complex -> many -> stars -> breakdown.", | |
| (20, theory_y + 40), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.35, (120, 150, 120), 1) | |
| cv2.putText(img, "ZOOM selects eigenmode range: 0=slow(coarse), 1=fast(fine)", | |
| (20, theory_y + 60), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.35, (120, 150, 120), 1) | |
| cv2.putText(img, "Stable regions = 'cells' with coherent phase. Field limits harmony at complexity.", | |
| (20, theory_y + 80), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.35, (120, 150, 120), 1) | |
| # Parameters | |
| cv2.putText(img, f"epoch={self.epoch} | coupling={self.coupling:.2f} | damping={self.damping:.4f} | tension={self.tension:.1f}", | |
| (20, 580), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (100, 100, 100), 1) | |
| self._display = img | |
| def get_output(self, name): | |
| if name == 'display': | |
| return self._display | |
| elif name == 'field': | |
| return self.field | |
| elif name == 'eigenspectrum': | |
| if self._eigenvalues is not None: | |
| return self._eigenvalues[:self._num_active_modes] | |
| return np.zeros(6) | |
| elif name == 'num_modes': | |
| return float(self._num_active_modes) | |
| elif name == 'num_patterns': | |
| return float(len(self.patterns)) | |
| elif name == 'criticality': | |
| return float(self.criticality) | |
| elif name == 'total_energy': | |
| return float(self.total_energy) | |
| elif name == 'pattern_field': | |
| # Return visualization of just the patterns | |
| img = np.zeros((self.field_size, self.field_size), dtype=np.uint8) | |
| for pattern in self.patterns.values(): | |
| brightness = int(pattern.age * 10) | |
| img[pattern.mask] = min(255, brightness) | |
| return img | |
| return None | |
| def get_display_image(self): | |
| h, w = self._display.shape[:2] | |
| return QtGui.QImage(self._display.data, w, h, w * 3, | |
| QtGui.QImage.Format.Format_RGB888) | |
| def get_config_options(self): | |
| return [ | |
| ("Zoom (Eigenmode Selection)", "zoom", self.zoom, None), | |
| ("Coupling", "coupling", self.coupling, None), | |
| ("Damping", "damping", self.damping, None), | |
| ("Tension", "tension", self.tension, None), | |
| ("Topology", "topology", self.topology, [("Box", "box"), ("Torus", "torus")]), | |
| ] |