Spaces:
Running
Running
File size: 5,743 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 |
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)
] |