Spaces:
Running
Running
| """ | |
| PC Visualizer Node - Visualize what each principal component controls | |
| Shows the "eigenfaces" of your frequency space | |
| """ | |
| import numpy as np | |
| from PyQt6 import QtGui | |
| import cv2 | |
| import __main__ | |
| BaseNode = __main__.BaseNode | |
| QtGui = __main__.QtGui | |
| class PCVisualizerNode(BaseNode): | |
| """ | |
| Visualizes individual principal components as images. | |
| Connect to SpectralPCA to see what each PC represents. | |
| """ | |
| NODE_CATEGORY = "AI / Physics" | |
| NODE_COLOR = QtGui.QColor(180, 220, 120) | |
| def __init__(self, pc_index=0, amplitude=3.0): | |
| super().__init__() | |
| self.node_title = f"PC Visualizer (PC{pc_index})" | |
| self.inputs = { | |
| 'pca_node': 'node_reference', # Reference to SpectralPCA node | |
| 'amplitude': 'signal' # How much to amplify | |
| } | |
| self.outputs = { | |
| 'image': 'image', | |
| 'complex_spectrum': 'complex_spectrum', | |
| 'variance_explained': 'signal' | |
| } | |
| self.pc_index = int(pc_index) | |
| self.amplitude = float(amplitude) | |
| # Visualization | |
| self.pc_image = np.zeros((128, 128, 3), dtype=np.uint8) | |
| self.pc_spectrum = None | |
| self.variance_explained = 0.0 | |
| def step(self): | |
| # Get amplitude modulation | |
| amp_signal = self.get_blended_input('amplitude', 'sum') | |
| if amp_signal is not None: | |
| amplitude = amp_signal * 10.0 # Scale up for visibility | |
| else: | |
| amplitude = self.amplitude | |
| # Get reference to PCA node (this is a bit of a hack) | |
| # In practice, you'd connect SpectralPCA's outputs here | |
| # For now, we'll create a synthetic visualization | |
| # Create a spectrum with just this PC activated | |
| # This would come from: mean_spectrum + pc_component * amplitude | |
| # Placeholder: create a synthetic pattern | |
| size = 64 | |
| freq = self.pc_index + 1 | |
| # Each PC might represent a different frequency pattern | |
| y, x = np.ogrid[0:size, 0:size] | |
| pattern = np.sin(2*np.pi*freq*x/size) * np.cos(2*np.pi*freq*y/size) | |
| pattern = pattern * amplitude | |
| # Visualize | |
| pattern_norm = (pattern - pattern.min()) / (pattern.max() - pattern.min() + 1e-9) | |
| self.pc_image = cv2.applyColorMap((pattern_norm * 255).astype(np.uint8), | |
| cv2.COLORMAP_VIRIDIS) | |
| # Create complex spectrum (simplified) | |
| self.pc_spectrum = np.fft.fft2(pattern) | |
| # Variance explained (would come from PCA node) | |
| self.variance_explained = 1.0 / (self.pc_index + 1) # Decreasing | |
| def get_output(self, port_name): | |
| if port_name == 'image': | |
| return self.pc_image.astype(np.float32) / 255.0 | |
| elif port_name == 'complex_spectrum': | |
| return self.pc_spectrum | |
| elif port_name == 'variance_explained': | |
| return self.variance_explained | |
| return None | |
| def get_display_image(self): | |
| img = self.pc_image.copy() | |
| # Add label | |
| label = f"PC{self.pc_index}: {self.variance_explained:.1%}" | |
| cv2.putText(img, label, (5, 15), cv2.FONT_HERSHEY_SIMPLEX, | |
| 0.4, (255,255,255), 1) | |
| return QtGui.QImage(img.data, 128, 128, 128*3, QtGui.QImage.Format.Format_RGB888) | |
| def get_config_options(self): | |
| return [ | |
| ("PC Index", "pc_index", self.pc_index, None), | |
| ("Amplitude", "amplitude", self.amplitude, None) | |
| ] | |