"""
Object detection tool for satellite imagery.
"""
from typing import Optional, List
import numpy as np
import structlog
from geosight.tools.geocoding import resolve_location
from geosight.models.object_detector import ObjectDetector, SUPPORTED_OBJECTS
from geosight.utils.visualization import array_to_base64_png
from geosight.utils.geo import create_bbox_from_point
logger = structlog.get_logger(__name__)
async def detect_objects(
date: str,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
location_name: Optional[str] = None,
radius_km: float = 5.0,
object_types: Optional[List[str]] = None,
confidence_threshold: float = 0.5,
) -> dict:
"""Detect objects in satellite imagery."""
try:
lat, lon, display_name = await resolve_location(
location_name=location_name,
latitude=latitude,
longitude=longitude,
)
if object_types is None:
object_types = ["ship", "aircraft", "building"]
logger.info(
"detecting_objects",
location=display_name,
date=date,
object_types=object_types,
)
# Generate demo image and detections
size = min(int(radius_km * 40), 512)
image = generate_demo_image(size)
detector = ObjectDetector()
detections = await detector.detect(
image,
object_types=object_types,
confidence_threshold=confidence_threshold,
)
# Draw bounding boxes on image
image_with_boxes = draw_detections(image, detections)
image_base64 = array_to_base64_png(image_with_boxes)
# Calculate statistics
detection_counts = {}
for det in detections:
detection_counts[det.class_name] = detection_counts.get(det.class_name, 0) + 1
avg_confidence = np.mean([d.confidence for d in detections]) if detections else 0
stats = {
"total_detections": len(detections),
"detection_counts": detection_counts,
"avg_confidence": float(avg_confidence),
"confidence_threshold": confidence_threshold,
"object_types_searched": object_types,
}
# Build detection list
detection_lines = []
for i, det in enumerate(detections[:20], 1):
x, y = det.center
detection_lines.append(
f" {i}. {det.class_name.title()} at ({x}, {y}) - {det.confidence:.1%} confidence"
)
if len(detections) > 20:
detection_lines.append(f" ... and {len(detections) - 20} more")
summary = f"""🎯 **Object Detection Results**
📍 **Location:** {display_name}
📅 **Date:** {date}
📐 **Area:** {(radius_km * 2) ** 2:.1f} km²
🔍 **Searched for:** {', '.join(object_types)}
**Detection Summary:**
• Total objects found: {len(detections)}
• Average confidence: {avg_confidence:.1%}
• Minimum confidence threshold: {confidence_threshold:.0%}
**Objects by Type:**
{chr(10).join(f' • {k.title()}: {v}' for k, v in detection_counts.items())}
**Detected Objects:**
{chr(10).join(detection_lines) if detection_lines else ' No objects detected'}
"""
return {
"summary": summary,
"statistics": stats,
"detections": [
{
"class": d.class_name,
"confidence": d.confidence,
"bbox": d.bbox,
"center": d.center,
}
for d in detections
],
"image_base64": image_base64,
"image_mime_type": "image/png",
"location": {"name": display_name, "latitude": lat, "longitude": lon},
}
except ValueError as e:
return {"summary": f"❌ **Error:** {str(e)}", "error": str(e)}
except Exception as e:
logger.error("object_detection_error", error=str(e), exc_info=True)
return {"summary": f"❌ **Detection failed:** {str(e)}", "error": str(e)}
def generate_demo_image(size: int) -> np.ndarray:
"""Generate demo satellite image."""
np.random.seed(456)
# Create a blue-ish base (water/sky)
image = np.zeros((size, size, 3), dtype=np.uint8)
image[:, :, 0] = 100 + np.random.randint(0, 30, (size, size)) # R
image[:, :, 1] = 120 + np.random.randint(0, 30, (size, size)) # G
image[:, :, 2] = 150 + np.random.randint(0, 40, (size, size)) # B
# Add some land patches
for _ in range(5):
x, y = np.random.randint(50, size-50, 2)
r = np.random.randint(30, 80)
yy, xx = np.ogrid[:size, :size]
mask = (xx - x)**2 + (yy - y)**2 < r**2
image[mask, 0] = 80 + np.random.randint(0, 40)
image[mask, 1] = 100 + np.random.randint(0, 50)
image[mask, 2] = 60 + np.random.randint(0, 30)
return image
def draw_detections(image: np.ndarray, detections) -> np.ndarray:
"""Draw bounding boxes on image."""
from PIL import Image, ImageDraw
img = Image.fromarray(image)
draw = ImageDraw.Draw(img)
colors = {
"ship": "red",
"aircraft": "yellow",
"vehicle": "orange",
"building": "blue",
"solar_panel": "purple",
"storage_tank": "cyan",
"sports_field": "green",
}
for det in detections:
color = colors.get(det.class_name, "white")
x1, y1, x2, y2 = det.bbox
# Draw rectangle
draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
# Draw label
label = f"{det.class_name}: {det.confidence:.0%}"
draw.text((x1, y1 - 12), label, fill=color)
return np.array(img)