File size: 3,563 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
"""

Moiré Interference Node - Generates a 2D moiré pattern by interfering

two perpendicular sine waves. The frequencies of the waves are

controlled by the signal inputs.



Ported from moire_microscope.html

"""

import numpy as np
from PyQt6 import QtGui
import cv2

import __main__
BaseNode = __main__.BaseNode
PA_INSTANCE = getattr(__main__, "PA_INSTANCE", None)
QtGui = __main__.QtGui

class MoireInterferenceNode(BaseNode):
    NODE_CATEGORY = "Transform"
    NODE_COLOR = QtGui.QColor(100, 180, 180) # Moiré Teal
    
    def __init__(self, size=128, base_phase_1=0.0, base_phase_2=0.0):
        super().__init__()
        self.node_title = "Moiré Interference"
        self.size = int(size)
        self.base_phase_1 = float(base_phase_1)
        self.base_phase_2 = float(base_phase_2)
        
        self.inputs = {
            'freq_1': 'signal', # Controls frequency of horizontal wave
            'freq_2': 'signal'  # Controls frequency of vertical wave
        }
        self.outputs = {'image': 'image'}
        
        # Pre-calculate coordinate grids
        self._init_grids()
        self.output_image = np.zeros((self.size, self.size), dtype=np.float32)

    def _init_grids(self):
        """Creates normalized coordinate grids [0, 1]"""
        if self.size == 0: self.size = 1 # Avoid division by zero
        u_vec = np.linspace(0, 1, self.size, dtype=np.float32)
        v_vec = np.linspace(0, 1, self.size, dtype=np.float32)
        # V (rows, 0->1), U (cols, 0->1)
        self.U, self.V = np.meshgrid(u_vec, v_vec) 
        self.output_image = np.zeros((self.size, self.size), dtype=np.float32)

    def step(self):
        # Check if size changed from config
        if self.U.shape[0] != self.size:
            self._init_grids()
            
        # 1. Get frequency inputs
        # We map the input signal (range -1 to 1) to a k-value (frequency)
        # e.g., mapping to a range of [5, 45]
        k1 = ((self.get_blended_input('freq_1', 'sum') or 0.0) + 1.0) * 20.0 + 5.0
        k2 = ((self.get_blended_input('freq_2', 'sum') or 0.0) + 1.0) * 20.0 + 5.0
        
        # 2. Port the core math from moire_microscope.html
        # const field1 = Math.sin(u * 20 * Math.PI + phase1);
        # const field2 = Math.cos(v * 20 * Math.PI + phase2);
        # const moireValue = Math.cos(field1 * Math.PI - field2 * Math.PI);
        
        # We use U (horizontal grid) for field 1 and V (vertical grid) for field 2
        field1 = np.sin(self.U * k1 * np.pi + self.base_phase_1)
        field2 = np.cos(self.V * k2 * np.pi + self.base_phase_2)
        
        # The interference pattern
        moire_value = np.cos(field1 * np.pi - field2 * np.pi)
        
        # 3. Normalize [-1, 1] to [0, 1] for image output
        self.output_image = (moire_value + 1.0) / 2.0

    def get_output(self, port_name):
        if port_name == 'image':
            return self.output_image
        return None
        
    def get_display_image(self):
        img_u8 = (np.clip(self.output_image, 0, 1) * 255).astype(np.uint8)
        img_u8 = np.ascontiguousarray(img_u8)
        return QtGui.QImage(img_u8.data, self.size, self.size, self.size, QtGui.QImage.Format.Format_Grayscale8)

    def get_config_options(self):
        return [
            ("Resolution", "size", self.size, None),
            ("Base Phase 1", "base_phase_1", self.base_phase_1, None),
            ("Base Phase 2", "base_phase_2", self.base_phase_2, None),
        ]