File size: 8,464 Bytes
32d978d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""
Geometry System — Core Knowledge of Geometry

Implements innate geometric/spatial reasoning:
1. Spatial relations: left, right, above, below, inside, outside
2. Distance metrics with smooth deformations (Distortable Canvas integration)
3. Surface layout representations (navigable surfaces)
4. Shape primitives for recognition

Boosted by the Distortable Canvas paper: images as smooth functions
on elastic 2D canvas with deformation fields.

Reference: Spelke & Lee (2012), oneandtrulyone Distortable Canvas paper
Author: Algorembrant, Rembrant Oyangoren Albeos (2026)
"""

import numpy as np
from scipy.ndimage import gaussian_filter


class GeometrySystem:
    """
    Innate spatial and geometric reasoning.
    
    Provides core geometric computations including spatial relations,
    smooth deformation fields (from Distortable Canvas), and surface
    layout representations for navigation and object reasoning.
    """
    
    def __init__(self, canvas_resolution: int = 28):
        """
        Args:
            canvas_resolution: Default canvas size for deformation operations.
        """
        self.canvas_resolution = canvas_resolution
    
    # ----- Spatial Relations (innate categorical distinctions) -----
    
    def spatial_relation(self, pos_a: np.ndarray, pos_b: np.ndarray) -> dict:
        """
        Compute innate categorical spatial relations between two positions.
        
        Infants distinguish these before learning language labels for them.
        
        Args:
            pos_a: Reference position (ndarray, at least 2D).
            pos_b: Target position.
            
        Returns:
            Dict with boolean spatial relations and continuous distances.
        """
        a = np.asarray(pos_a, dtype=np.float64)
        b = np.asarray(pos_b, dtype=np.float64)
        diff = b - a
        
        relations = {
            'distance': float(np.linalg.norm(diff)),
            'direction': diff / (np.linalg.norm(diff) + 1e-8),
        }
        
        if len(diff) >= 2:
            relations['right_of'] = bool(diff[0] > 0)
            relations['left_of'] = bool(diff[0] < 0)
            relations['above'] = bool(diff[1] < 0)  # Assuming y-axis points down (image coords)
            relations['below'] = bool(diff[1] > 0)
            
        return relations
    
    def is_inside(self, point: np.ndarray, bbox_min: np.ndarray, 
                  bbox_max: np.ndarray) -> bool:
        """
        Check if a point is inside a bounding region.
        
        Containment is a core geometric concept — infants reason about
        "inside" and "outside" from very early on.
        """
        p = np.asarray(point, dtype=np.float64)
        return bool(np.all(p >= bbox_min) and np.all(p <= bbox_max))
    
    # ----- Smooth Deformation Fields (Distortable Canvas) -----
    
    def create_deformation_field(self, shape: tuple[int, int],
                                  smoothness: float = 3.0,
                                  magnitude: float = 2.0) -> tuple[np.ndarray, np.ndarray]:
        """
        Create a smooth random deformation field.
        
        From the Distortable Canvas paper: images live on an elastic 2D canvas
        that can be smoothly warped. The deformation field u(x,y), v(x,y) defines
        how each pixel coordinate shifts.
        
        Args:
            shape: (H, W) shape of the canvas.
            smoothness: Gaussian sigma for smoothness regularization.
                       Higher = smoother/more rigid deformation.
            magnitude: Maximum displacement magnitude.
            
        Returns:
            Tuple of (u_field, v_field), each of shape (H, W).
        """
        H, W = shape
        # Random initial displacements
        u = np.random.randn(H, W) * magnitude
        v = np.random.randn(H, W) * magnitude
        
        # Smooth with Gaussian filter (biological smoothness constraint)
        u = gaussian_filter(u, sigma=smoothness)
        v = gaussian_filter(v, sigma=smoothness)
        
        return u, v
    
    def apply_deformation(self, image: np.ndarray,
                          u_field: np.ndarray,
                          v_field: np.ndarray) -> np.ndarray:
        """
        Apply a smooth deformation field to an image.
        
        This is the core operation from the Distortable Canvas paper:
        warp the canvas to align one image to another.
        
        Args:
            image: 2D image array (H, W).
            u_field: Horizontal displacement field (H, W).
            v_field: Vertical displacement field (H, W).
            
        Returns:
            Warped image.
        """
        H, W = image.shape[:2]
        # Create coordinate grids
        y_coords, x_coords = np.mgrid[0:H, 0:W].astype(np.float64)
        
        # Apply deformation
        new_x = x_coords + u_field
        new_y = y_coords + v_field
        
        # Clamp to valid range
        new_x = np.clip(new_x, 0, W - 1)
        new_y = np.clip(new_y, 0, H - 1)
        
        # Bilinear interpolation
        x0 = np.floor(new_x).astype(int)
        x1 = np.minimum(x0 + 1, W - 1)
        y0 = np.floor(new_y).astype(int)
        y1 = np.minimum(y0 + 1, H - 1)
        
        wx = new_x - x0
        wy = new_y - y0
        
        result = (image[y0, x0] * (1 - wx) * (1 - wy) +
                  image[y1, x0] * (1 - wx) * wy +
                  image[y0, x1] * wx * (1 - wy) +
                  image[y1, x1] * wx * wy)
        
        return result
    
    def canvas_distance(self, u_field: np.ndarray, v_field: np.ndarray) -> float:
        """
        Compute the canvas distortion energy (from Distortable Canvas paper).
        
        This measures how much geometric warping is needed. The Jacobian
        penalty ensures the deformation is smooth and doesn't tear/fold.
        
        Energy = sum of squared gradients of the deformation field.
        """
        # Gradient of deformation field (Jacobian components)
        du_dx = np.gradient(u_field, axis=1)
        du_dy = np.gradient(u_field, axis=0)
        dv_dx = np.gradient(v_field, axis=1)
        dv_dy = np.gradient(v_field, axis=0)
        
        # Frobenius norm of the Jacobian of the displacement
        jacobian_energy = np.sum(du_dx**2 + du_dy**2 + dv_dx**2 + dv_dy**2)
        
        return float(jacobian_energy)
    
    def color_distance(self, image1: np.ndarray, image2: np.ndarray) -> float:
        """
        Pixel-wise intensity distance between two images.
        
        From Distortable Canvas: the "color distortion" component
        of the dual distance metric.
        """
        return float(np.sum((image1.astype(np.float64) - image2.astype(np.float64))**2))
    
    def dual_distance(self, image1: np.ndarray, image2: np.ndarray,
                      u_field: np.ndarray, v_field: np.ndarray,
                      lambda_weight: float = 0.1) -> float:
        """
        Compute the dual distance from the Distortable Canvas paper.
        
        dual_distance = color_distance + lambda * canvas_distance
        
        This balances pixel-level similarity against geometric warping cost.
        """
        # Warp image1 toward image2
        warped = self.apply_deformation(image1, u_field, v_field)
        
        color_dist = self.color_distance(warped, image2)
        canvas_dist = self.canvas_distance(u_field, v_field)
        
        return color_dist + lambda_weight * canvas_dist
    
    # ----- Shape Primitives -----
    
    def compute_centroid(self, points: np.ndarray) -> np.ndarray:
        """Compute the centroid (center of mass) of a set of points."""
        return np.mean(points, axis=0)
    
    def compute_extent(self, points: np.ndarray) -> dict:
        """Compute spatial extent (bounding box, spread) of a point set."""
        mins = np.min(points, axis=0)
        maxs = np.max(points, axis=0)
        return {
            'min': mins,
            'max': maxs,
            'extent': maxs - mins,
            'center': (mins + maxs) / 2.0
        }
    
    def angular_relation(self, center: np.ndarray, point: np.ndarray) -> float:
        """
        Compute the angle from center to point (in radians).
        Used for encoding relative angular position.
        """
        diff = np.asarray(point, dtype=np.float64) - np.asarray(center, dtype=np.float64)
        return float(np.arctan2(diff[1], diff[0]))