File size: 3,751 Bytes
3d46076
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import numpy as np
from PIL import Image
from typing import Dict, Any, Optional
from src.tools.base import ToolConnector

class ObserveVisualConnector(ToolConnector):
    def __init__(self):
        super().__init__(
            name="observe_visual",
            description="Observes and extracts 64-dimensional biophysical visual features from an image file or synthetic visual field.",
            timeout_sec=10.0
        )

    @property
    def input_schema(self) -> Dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "image_path": {"type": "string"},
                "synthetic_target": {"type": "string"}
            }
        }

    @property
    def output_schema(self) -> Dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "features_vector": {"type": "array", "items": {"type": "number"}},
                "mean_brightness": {"type": "number"},
                "contrast": {"type": "number"},
                "dominant_channel": {"type": "string"}
            },
            "required": ["features_vector", "mean_brightness", "contrast", "dominant_channel"]
        }

    def _execute(self, params: Dict[str, Any], execution_id: str) -> Dict[str, Any]:
        img_path = params.get("image_path")
        
        if img_path and os.path.exists(img_path):
            img = Image.open(img_path).convert("RGB").resize((64, 64))
            arr = np.array(img, dtype=np.float32) / 255.0
        else:
            # Generate synthetic sensory visual field based on synthetic_target
            target = params.get("synthetic_target", "neutral")
            arr = np.zeros((64, 64, 3), dtype=np.float32)
            if "red" in target:
                arr[:, :, 0] = 0.8
            elif "green" in target:
                arr[:, :, 1] = 0.8
            elif "blue" in target:
                arr[:, :, 2] = 0.8
            elif "bright" in target:
                arr[:] = 0.9
            else:
                # Gradient field
                for y in range(64):
                    for x in range(64):
                        arr[y, x, 0] = x / 64.0
                        arr[y, x, 1] = y / 64.0
                        arr[y, x, 2] = 0.5

        # Extract 64-d feature vector
        # 16 values: 4x4 spatial grid of mean luminance
        grid = arr.mean(axis=2).reshape(4, 16, 4, 16).mean(axis=(1, 3)).flatten()
        # 16 values: color distribution histograms
        hist_r, _ = np.histogram(arr[:, :, 0], bins=8, range=(0, 1))
        hist_g, _ = np.histogram(arr[:, :, 1], bins=8, range=(0, 1))
        # 16 values: horizontal gradients
        grad_x = np.abs(np.diff(arr.mean(axis=2), axis=1))
        grad_pool_x = grad_x.reshape(4, 16, 63).mean(axis=(1, 2))
        grad_pool_pad = np.pad(grad_pool_x, (0, 12), mode="edge")
        # 16 values: vertical gradients
        grad_y = np.abs(np.diff(arr.mean(axis=2), axis=0))
        grad_pool_y = grad_y.reshape(63, 4, 16).mean(axis=(0, 2))
        grad_pool_y_pad = np.pad(grad_pool_y, (0, 12), mode="edge")

        feat = np.concatenate([grid, hist_r / 4096.0, hist_g / 4096.0, grad_pool_pad[:16], grad_pool_y_pad[:16]])
        feat = feat[:64].astype(np.float32)

        mean_b = float(arr.mean())
        contrast = float(arr.std())
        channel_means = [arr[:, :, 0].mean(), arr[:, :, 1].mean(), arr[:, :, 2].mean()]
        dom_idx = int(np.argmax(channel_means))
        dominant_channel = ["red", "green", "blue"][dom_idx]

        return {
            "features_vector": feat.tolist(),
            "mean_brightness": round(mean_b, 4),
            "contrast": round(contrast, 4),
            "dominant_channel": dominant_channel
        }