| """ |
| 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 |
| |
| |
| |
| 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) |
| 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)) |
| |
| |
| |
| 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 |
| |
| u = np.random.randn(H, W) * magnitude |
| v = np.random.randn(H, W) * magnitude |
| |
| |
| 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] |
| |
| y_coords, x_coords = np.mgrid[0:H, 0:W].astype(np.float64) |
| |
| |
| new_x = x_coords + u_field |
| new_y = y_coords + v_field |
| |
| |
| new_x = np.clip(new_x, 0, W - 1) |
| new_y = np.clip(new_y, 0, H - 1) |
| |
| |
| 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. |
| """ |
| |
| 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) |
| |
| |
| 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. |
| """ |
| |
| 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 |
| |
| |
| |
| 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])) |
|
|