Initial
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Interactive Region Coordinate Helper
|
||||
Opens an image and helps you find pixel coordinates for colorable regions.
|
||||
Click on the image to see coordinates.
|
||||
|
||||
Usage:
|
||||
python region_helper.py images/cobrai.jpg
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import tkinter as tk
|
||||
from tkinter import Canvas
|
||||
from PIL import ImageTk
|
||||
|
||||
|
||||
class RegionHelper:
|
||||
def __init__(self, image_path):
|
||||
self.image_path = image_path
|
||||
self.image = Image.open(image_path)
|
||||
self.width, self.height = self.image.size
|
||||
|
||||
# Setup UI
|
||||
self.root = tk.Tk()
|
||||
self.root.title(f"Region Helper - {image_path}")
|
||||
|
||||
# Scale image if too large
|
||||
self.scale = 1.0
|
||||
max_display_width = 1000
|
||||
max_display_height = 800
|
||||
|
||||
if self.width > max_display_width or self.height > max_display_height:
|
||||
scale_w = max_display_width / self.width
|
||||
scale_h = max_display_height / self.height
|
||||
self.scale = min(scale_w, scale_h)
|
||||
|
||||
new_width = int(self.width * self.scale)
|
||||
new_height = int(self.height * self.scale)
|
||||
self.display_image = self.image.resize((new_width, new_height), Image.LANCZOS)
|
||||
else:
|
||||
self.display_image = self.image.copy()
|
||||
|
||||
# Setup canvas
|
||||
self.canvas = Canvas(
|
||||
self.root,
|
||||
width=self.display_image.width,
|
||||
height=self.display_image.height
|
||||
)
|
||||
self.canvas.pack(side=tk.LEFT)
|
||||
|
||||
# Display image
|
||||
self.photo = ImageTk.PhotoImage(self.display_image)
|
||||
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo)
|
||||
|
||||
# Info panel
|
||||
info_frame = tk.Frame(self.root, width=300)
|
||||
info_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=10, pady=10)
|
||||
|
||||
tk.Label(info_frame, text="Image Dimensions:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=5)
|
||||
tk.Label(info_frame, text=f"{self.width} x {self.height} pixels").pack(anchor=tk.W)
|
||||
|
||||
if self.scale != 1.0:
|
||||
tk.Label(info_frame, text=f"Scaled: {self.scale:.2%}").pack(anchor=tk.W)
|
||||
|
||||
tk.Label(info_frame, text="\nClick to get coordinates:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(20, 5))
|
||||
|
||||
self.coord_label = tk.Label(info_frame, text="X: -, Y: -", font=('Arial', 14))
|
||||
self.coord_label.pack(anchor=tk.W, pady=5)
|
||||
|
||||
tk.Label(info_frame, text="\nFirst click = Top-Left", fg='blue').pack(anchor=tk.W)
|
||||
tk.Label(info_frame, text="Second click = Bottom-Right", fg='blue').pack(anchor=tk.W)
|
||||
|
||||
tk.Label(info_frame, text="\nRegion JSON:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(20, 5))
|
||||
|
||||
self.json_text = tk.Text(info_frame, height=15, width=35, font=('Courier', 9))
|
||||
self.json_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
tk.Button(info_frame, text="Clear Points", command=self.clear_points).pack(pady=10)
|
||||
tk.Button(info_frame, text="Copy JSON", command=self.copy_json).pack()
|
||||
|
||||
# Bind events
|
||||
self.canvas.bind('<Motion>', self.on_mouse_move)
|
||||
self.canvas.bind('<Button-1>', self.on_click)
|
||||
|
||||
# Store points
|
||||
self.points = []
|
||||
self.point_ids = []
|
||||
|
||||
self.update_json()
|
||||
|
||||
def on_mouse_move(self, event):
|
||||
"""Show current mouse coordinates."""
|
||||
x = int(event.x / self.scale)
|
||||
y = int(event.y / self.scale)
|
||||
self.coord_label.config(text=f"X: {x}, Y: {y}")
|
||||
|
||||
def on_click(self, event):
|
||||
"""Record clicked point."""
|
||||
x = int(event.x / self.scale)
|
||||
y = int(event.y / self.scale)
|
||||
|
||||
# Limit to 2 points (top-left, bottom-right)
|
||||
if len(self.points) >= 2:
|
||||
self.clear_points()
|
||||
|
||||
self.points.append((x, y))
|
||||
|
||||
# Draw point on canvas
|
||||
color = 'blue' if len(self.points) == 1 else 'red'
|
||||
point_id = self.canvas.create_oval(
|
||||
event.x - 5, event.y - 5,
|
||||
event.x + 5, event.y + 5,
|
||||
fill=color, outline='white', width=2
|
||||
)
|
||||
self.point_ids.append(point_id)
|
||||
|
||||
# Draw rectangle if we have both points
|
||||
if len(self.points) == 2:
|
||||
x1, y1 = self.points[0]
|
||||
x2, y2 = self.points[1]
|
||||
|
||||
# Draw on canvas (scaled)
|
||||
rect_id = self.canvas.create_rectangle(
|
||||
x1 * self.scale, y1 * self.scale,
|
||||
x2 * self.scale, y2 * self.scale,
|
||||
outline='green', width=2, dash=(5, 5)
|
||||
)
|
||||
self.point_ids.append(rect_id)
|
||||
|
||||
self.update_json()
|
||||
|
||||
def clear_points(self):
|
||||
"""Clear all points and rectangles."""
|
||||
for point_id in self.point_ids:
|
||||
self.canvas.delete(point_id)
|
||||
|
||||
self.points = []
|
||||
self.point_ids = []
|
||||
self.update_json()
|
||||
|
||||
def update_json(self):
|
||||
"""Update the JSON display."""
|
||||
self.json_text.delete('1.0', tk.END)
|
||||
|
||||
if len(self.points) == 0:
|
||||
self.json_text.insert('1.0', '{\n "name": "region_name",\n "topLeft": [?, ?],\n "bottomRight": [?, ?],\n "description": "..."\n}')
|
||||
elif len(self.points) == 1:
|
||||
x, y = self.points[0]
|
||||
self.json_text.insert('1.0', f'{{\n "name": "region_name",\n "topLeft": [{x}, {y}],\n "bottomRight": [?, ?],\n "description": "..."\n}}')
|
||||
else:
|
||||
x1, y1 = self.points[0]
|
||||
x2, y2 = self.points[1]
|
||||
|
||||
# Calculate dimensions
|
||||
width = abs(x2 - x1)
|
||||
height = abs(y2 - y1)
|
||||
|
||||
json_str = f'''{{\n "name": "region_name",\n "topLeft": [{x1}, {y1}],\n "bottomRight": [{x2}, {y2}],\n "description": "Width: {width}px, Height: {height}px"\n}}'''
|
||||
self.json_text.insert('1.0', json_str)
|
||||
|
||||
def copy_json(self):
|
||||
"""Copy JSON to clipboard."""
|
||||
json_str = self.json_text.get('1.0', tk.END).strip()
|
||||
self.root.clipboard_clear()
|
||||
self.root.clipboard_append(json_str)
|
||||
self.root.update()
|
||||
|
||||
def run(self):
|
||||
"""Start the GUI."""
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python region_helper.py <image_path>")
|
||||
print("Example: python region_helper.py images/cobrai.jpg")
|
||||
sys.exit(1)
|
||||
|
||||
image_path = sys.argv[1]
|
||||
|
||||
try:
|
||||
helper = RegionHelper(image_path)
|
||||
helper.run()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: Image file not found: {image_path}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user