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)}"
|