Upload analyze_spatial_relationships.py
#282
by
Loucif
- opened
tools/analyze_spatial_relationships.py
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from smolagents import tool
|
2 |
+
import os
|
3 |
+
import json
|
4 |
+
import math
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
from typing import Optional
|
8 |
+
|
9 |
+
@tool
|
10 |
+
def analyze_spatial_relationships(image_path: Optional[str] = None, object_type1: Optional[str] = None, object_type2: Optional[str] = None) -> str:
|
11 |
+
"""Analyze spatial relationships between objects in an image.
|
12 |
+
Args:
|
13 |
+
image_path: Path to the image file to analyze.
|
14 |
+
object_type1: First object type to analyze.
|
15 |
+
object_type2: Second object type to analyze.
|
16 |
+
Returns:
|
17 |
+
A description of spatial relationships between the objects.
|
18 |
+
"""
|
19 |
+
try:
|
20 |
+
# Check if image path exists
|
21 |
+
if not image_path or not os.path.exists(image_path):
|
22 |
+
return f"Error: Image path '{image_path}' does not exist."
|
23 |
+
|
24 |
+
# Check if detection data exists, if not run detection
|
25 |
+
detection_data_path = f"{os.path.splitext(image_path)[0]}_detection_data.json"
|
26 |
+
|
27 |
+
if not os.path.exists(detection_data_path):
|
28 |
+
# Run detection first
|
29 |
+
from tools.detect_objects import detect_objects_with_roboflow
|
30 |
+
detect_objects_with_roboflow(image_path=image_path)
|
31 |
+
|
32 |
+
if not os.path.exists(detection_data_path):
|
33 |
+
return f"Error: Detection failed. Could not create detection data."
|
34 |
+
|
35 |
+
# Load existing detection data
|
36 |
+
with open(detection_data_path, "r") as f:
|
37 |
+
result = json.load(f)
|
38 |
+
|
39 |
+
# Load the original image to get dimensions
|
40 |
+
img = cv2.imread(image_path)
|
41 |
+
if img is None:
|
42 |
+
return f"Error: Could not read image at '{image_path}'."
|
43 |
+
|
44 |
+
img_height, img_width = img.shape[:2]
|
45 |
+
|
46 |
+
# Define "close" threshold as 30% of image width
|
47 |
+
close_threshold = img_width * 0.3
|
48 |
+
|
49 |
+
# Extract objects of the specified types
|
50 |
+
objects1 = []
|
51 |
+
objects2 = []
|
52 |
+
|
53 |
+
if "predictions" in result:
|
54 |
+
for prediction in result["predictions"]:
|
55 |
+
class_name = prediction["class"]
|
56 |
+
|
57 |
+
# Check if this prediction matches either object type
|
58 |
+
if object_type1 and object_type1.lower() in class_name.lower():
|
59 |
+
if "x" in prediction and "y" in prediction:
|
60 |
+
objects1.append({
|
61 |
+
"class": class_name,
|
62 |
+
"x": prediction["x"],
|
63 |
+
"y": prediction["y"],
|
64 |
+
"width": prediction["width"],
|
65 |
+
"height": prediction["height"],
|
66 |
+
"confidence": prediction["confidence"]
|
67 |
+
})
|
68 |
+
|
69 |
+
if object_type2 and object_type2.lower() in class_name.lower():
|
70 |
+
if "x" in prediction and "y" in prediction:
|
71 |
+
objects2.append({
|
72 |
+
"class": class_name,
|
73 |
+
"x": prediction["x"],
|
74 |
+
"y": prediction["y"],
|
75 |
+
"width": prediction["width"],
|
76 |
+
"height": prediction["height"],
|
77 |
+
"confidence": prediction["confidence"]
|
78 |
+
})
|
79 |
+
|
80 |
+
# If no objects found, return appropriate message
|
81 |
+
if not objects1:
|
82 |
+
return f"No objects matching '{object_type1}' were found in the image."
|
83 |
+
|
84 |
+
if not objects2:
|
85 |
+
return f"No objects matching '{object_type2}' were found in the image."
|
86 |
+
|
87 |
+
# Analyze spatial relationships
|
88 |
+
close_pairs = []
|
89 |
+
|
90 |
+
for obj1 in objects1:
|
91 |
+
for obj2 in objects2:
|
92 |
+
# Calculate Euclidean distance between centers
|
93 |
+
distance = math.sqrt(
|
94 |
+
(obj1["x"] - obj2["x"])**2 +
|
95 |
+
(obj1["y"] - obj2["y"])**2
|
96 |
+
)
|
97 |
+
|
98 |
+
# Check if they are close
|
99 |
+
if distance <= close_threshold:
|
100 |
+
close_pairs.append({
|
101 |
+
"object1": obj1["class"],
|
102 |
+
"object2": obj2["class"],
|
103 |
+
"distance": distance,
|
104 |
+
"distance_percent": (distance / img_width) * 100
|
105 |
+
})
|
106 |
+
|
107 |
+
# Format the results
|
108 |
+
if close_pairs:
|
109 |
+
output = f"Found {len(close_pairs)} instances where {object_type1} is close to {object_type2}:\n"
|
110 |
+
for i, pair in enumerate(close_pairs, 1):
|
111 |
+
output += f"{i}. {pair['object1']} is close to {pair['object2']} (distance: {pair['distance']:.1f} pixels, {pair['distance_percent']:.1f}% of image width)\n"
|
112 |
+
|
113 |
+
output += f"\nClose is defined as objects with centers within {close_threshold:.1f} pixels (30% of image width) from each other."
|
114 |
+
return output
|
115 |
+
else:
|
116 |
+
return f"No instances found where {object_type1} is close to {object_type2}. Close is defined as objects with centers within {close_threshold:.1f} pixels (30% of image width) from each other."
|
117 |
+
|
118 |
+
except Exception as e:
|
119 |
+
return f"Error analyzing spatial relationships: {str(e)}"
|