Spaces:
Running
Running
File size: 6,786 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 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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
"""
Auto-Explorer Node - Automatically animates through PC space
Creates smooth explorations of the learned manifold
"""
import numpy as np
from PyQt6 import QtGui
import cv2
import __main__
BaseNode = __main__.BaseNode
QtGui = __main__.QtGui
class AutoExplorerNode(BaseNode):
"""
Automatically explores PCA latent space with smooth animations.
Multiple modes: sequential, random walk, circular, spiral
"""
NODE_CATEGORY = "AI / Physics"
NODE_COLOR = QtGui.QColor(100, 220, 180)
def __init__(self, mode='sequential'):
super().__init__()
self.node_title = "Auto-Explorer"
self.inputs = {
'latent_in': 'spectrum',
'speed': 'signal',
'amplitude': 'signal',
'chaos': 'signal' # Randomness amount
}
self.outputs = {
'latent_out': 'spectrum',
'current_pc': 'signal',
'phase': 'signal' # 0-1 oscillation
}
self.mode = mode # 'sequential', 'random_walk', 'circular', 'spiral'
# State
self.base_latent = None
self.current_latent = None
self.phase = 0.0
self.current_pc = 0
self.random_state = np.random.randn(8) # For random walk
def step(self):
latent_in = self.get_blended_input('latent_in', 'first')
speed = self.get_blended_input('speed', 'sum') or 0.05
amplitude = self.get_blended_input('amplitude', 'sum') or 2.0
chaos = self.get_blended_input('chaos', 'sum') or 0.0
if latent_in is not None:
if self.base_latent is None:
self.base_latent = latent_in.copy()
self.current_latent = self.base_latent.copy()
# Advance phase
self.phase += speed
if self.mode == 'sequential':
self._sequential_mode(amplitude)
elif self.mode == 'random_walk':
self._random_walk_mode(amplitude, chaos)
elif self.mode == 'circular':
self._circular_mode(amplitude)
elif self.mode == 'spiral':
self._spiral_mode(amplitude)
def _sequential_mode(self, amplitude):
"""Oscillate through PCs one at a time"""
latent_dim = len(self.base_latent)
# Current PC index (cycles through all)
self.current_pc = int(self.phase / (2*np.pi)) % latent_dim
# Oscillate that PC
modulation = np.sin(self.phase) * amplitude
self.current_latent[self.current_pc] += modulation
def _random_walk_mode(self, amplitude, chaos):
"""Brownian motion in latent space"""
latent_dim = len(self.base_latent)
# Update random state
self.random_state += np.random.randn(latent_dim) * chaos * 0.1
# Apply damping
self.random_state *= 0.98
# Add to latent
for i in range(min(latent_dim, len(self.random_state))):
self.current_latent[i] += self.random_state[i] * amplitude
def _circular_mode(self, amplitude):
"""Rotate in PC0-PC1 plane"""
if len(self.base_latent) >= 2:
self.current_latent[0] += np.cos(self.phase) * amplitude
self.current_latent[1] += np.sin(self.phase) * amplitude
self.current_pc = 0 # Indicate using PC0-PC1
def _spiral_mode(self, amplitude):
"""Spiral outward in PC0-PC1 plane while oscillating PC2"""
if len(self.base_latent) >= 3:
# Expanding spiral
radius = (self.phase / (2*np.pi)) % 5.0 # Expand over 5 cycles
self.current_latent[0] += np.cos(self.phase) * radius * amplitude * 0.3
self.current_latent[1] += np.sin(self.phase) * radius * amplitude * 0.3
self.current_latent[2] += np.sin(self.phase * 2) * amplitude * 0.5
self.current_pc = 2 # Indicate complex motion
def get_output(self, port_name):
if port_name == 'latent_out':
return self.current_latent
elif port_name == 'current_pc':
return float(self.current_pc)
elif port_name == 'phase':
return (self.phase % (2*np.pi)) / (2*np.pi) # Normalized 0-1
return None
def get_display_image(self):
"""Show current exploration trajectory"""
img = np.zeros((256, 256, 3), dtype=np.uint8)
if self.current_latent is None:
cv2.putText(img, "Waiting for input...", (10, 128),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1)
return QtGui.QImage(img.data, 256, 256, 256*3, QtGui.QImage.Format.Format_RGB888)
# Draw mode and state
mode_text = f"Mode: {self.mode}"
pc_text = f"PC: {self.current_pc}"
phase_text = f"Phase: {self.phase:.2f}"
cv2.putText(img, mode_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1)
cv2.putText(img, pc_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,255), 1)
cv2.putText(img, phase_text, (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200,200,200), 1)
# Visualize current latent code as bars
latent_dim = len(self.current_latent)
bar_width = max(1, 256 // latent_dim)
delta = self.current_latent - self.base_latent
delta_max = np.abs(delta).max()
if delta_max > 1e-6:
delta_norm = delta / delta_max
else:
delta_norm = delta
for i, val in enumerate(delta_norm):
x = i * bar_width
h = int(abs(val) * 80)
y_base = 200
if val >= 0:
color = (0, 255, 0)
y_start = y_base - h
y_end = y_base
else:
color = (0, 0, 255)
y_start = y_base
y_end = y_base + h
# Highlight current PC
if i == self.current_pc:
color = (255, 255, 0)
cv2.rectangle(img, (x, y_start), (x+bar_width-1, y_end), color, -1)
# Draw baseline
cv2.line(img, (0, 200), (256, 200), (100, 100, 100), 1)
return QtGui.QImage(img.data, 256, 256, 256*3, QtGui.QImage.Format.Format_RGB888)
def get_config_options(self):
return [
("Mode", "mode", self.mode, None)
] |