import numpy as np import cv2 from scipy.fft import ifft2, ifftshift import __main__ try: BaseNode = __main__.BaseNode QtGui = __main__.QtGui except AttributeError: class BaseNode: def get_blended_input(self, name, mode): return None import PyQt6.QtGui as QtGui class HolographicIFFTNodeX(BaseNode): """ Holographic IFFT - The Inverse Reality ====================================== Treats the input image not as "Space", but as "Frequency" (k-space). It effectively asks: "If this image were a diffraction pattern, what hidden object created it?" MECHANISM: 1. Input (Gradient/Field) -> Treated as MAGNITUDE spectrum. 2. Phase Synthesis -> We generate the missing phase (The "Light"). - 'Void': Phase = 0 (Autocorrelation) - 'Chaos': Random Phase - 'Structure': Phase derived from image structure 3. Inverse FFT -> Collapses the spectrum into a spatial image. This reveals the "Reciprocal Ghost" - the hidden symmetries that exist in the frequency domain of your field. """ NODE_CATEGORY = "Analysis" NODE_TITLE = "Holographic IFFT 2" NODE_COLOR = QtGui.QColor(100, 255, 200) # Spectral Cyan def __init__(self): super().__init__() self.inputs = { 'diffraction_pattern': 'image', # The Gradient Field (treated as FFT Mag) 'phase_mod': 'signal', # Rotate the phase (The "Laser" angle) 'frequency_scale': 'signal', # Zoom in k-space 'reset': 'signal' } self.outputs = { 'hologram': 'image', # The reconstructed "Ghost" 'phase_mask': 'image' # The phase we used } self.size = 128 self.phase_mode = 'structure' # 'void', 'chaos', 'structure' # Internal state self.phase_map = None self.t = 0.0 def step(self): # 1. Get Inputs pattern = self.get_blended_input('diffraction_pattern', 'first') p_mod = self.get_blended_input('phase_mod', 'sum') f_scale = self.get_blended_input('frequency_scale', 'sum') if pattern is None: return # Defaults if p_mod is None: p_mod = 0.0 if f_scale is None: f_scale = 1.0 # Resize/Format if pattern.shape[:2] != (self.size, self.size): pattern = cv2.resize(pattern, (self.size, self.size)) if pattern.ndim == 3: pattern = np.mean(pattern, axis=2) # 2. Treat Input as MAGNITUDE (The Diffraction Pattern) # We shift it so the center of the image is DC (0 frequency) magnitude = np.abs(pattern) # Apply Frequency Scaling (Zooming in Reciprocal Space) if f_scale != 1.0 and f_scale > 0.1: # Zooming the spectrum changes the size of the object center = self.size // 2 M = cv2.getRotationMatrix2D((center, center), 0, f_scale) magnitude = cv2.warpAffine(magnitude, M, (self.size, self.size)) # 3. Synthesize the Missing Phase # This is where we "change the values" to catch the ghost if self.phase_map is None: self.phase_map = np.zeros_like(magnitude) # Create a phase ramp (spatial offset) + modulation y, x = np.ogrid[:self.size, :self.size] # 'Structure' Mode: Phase is related to position (creates coherence) # We animate the phase over time to "scan" the hologram self.t += 0.05 phase_structure = (x * np.cos(self.t) + y * np.sin(self.t)) * (0.1 + p_mod * 0.1) # Combine Magnitude and Phase into Complex Spectrum # Z = Mag * e^(i * theta) complex_spectrum = magnitude * np.exp(1j * phase_structure) # 4. The Inverse FFT (The Reconstruction) # We assume the input image had (0,0) in top-left, so we don't shift first. # But usually spectral view has DC in center. Let's try shifting. complex_spectrum = ifftshift(complex_spectrum) reconstructed = ifft2(complex_spectrum) # 5. Extract Real Component (The Hologram) self.hologram = np.abs(reconstructed) self.current_phase = np.angle(complex_spectrum) def get_output(self, port_name): if port_name == 'hologram': if hasattr(self, 'hologram'): return self._normalize(self.hologram) elif port_name == 'phase_mask': if hasattr(self, 'current_phase'): return self._normalize(self.current_phase) return None def _normalize(self, img): img = np.nan_to_num(img) norm = (img - np.min(img)) / (np.max(img) - np.min(img) + 1e-10) return (norm * 255).astype(np.uint8) def get_display_image(self): if not hasattr(self, 'hologram'): return None # Display: The Hologram (Reconstructed Reality) img = self._normalize(self.hologram) display = cv2.applyColorMap(img, cv2.COLORMAP_OCEAN) cv2.putText(display, "Holographic IFFT", (5, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 1) return QtGui.QImage(display.data, self.size, self.size, self.size*3, QtGui.QImage.Format.Format_RGB888) def get_config_options(self): return [ ("Resolution", "size", 128, None), ("Scale", "scale", 1.0, None) ]