Organisation
This commit is contained in:
parent
d484333c7a
commit
5c19f6dd21
181
code/voronoi_helper.py
Normal file
181
code/voronoi_helper.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.widgets import Button
|
||||||
|
|
||||||
|
# Initialize global storage for centers and colors
|
||||||
|
centers = np.empty((0, 2), dtype=int) # Shape: (n_centers, 2)
|
||||||
|
colors = np.empty((0, 3), dtype=int) # Shape: (n_centers, 3)
|
||||||
|
|
||||||
|
def hex_to_rgb(hex_color):
|
||||||
|
"""Convert a hexadecimal color string to an RGB tuple."""
|
||||||
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
def get_voronoi_image(centers, width=1000, height=1000, colors=None):
|
||||||
|
"""
|
||||||
|
Generate a Voronoi-like image based on the provided centers and colors.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
centers (np.ndarray): Array of center coordinates, shape (n_centers, 2).
|
||||||
|
width (int): Width of the image in pixels.
|
||||||
|
height (int): Height of the image in pixels.
|
||||||
|
colors (np.ndarray): Array of RGB colors for each center, shape (n_centers, 3).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
np.ndarray: RGB image array, shape (height, width, 3).
|
||||||
|
"""
|
||||||
|
if len(centers) == 0:
|
||||||
|
return np.ones((height, width, 3), dtype=np.uint8) * 255 # White image
|
||||||
|
|
||||||
|
# Create coordinate grids
|
||||||
|
x = np.arange(width)
|
||||||
|
y = np.arange(height)
|
||||||
|
xx, yy = np.meshgrid(x, y)
|
||||||
|
|
||||||
|
# Calculate Euclidean distances from each pixel to each center
|
||||||
|
# Shape of distances: (height, width, n_centers)
|
||||||
|
distances = np.sqrt((xx[..., np.newaxis] - centers[:, 0])**2 +
|
||||||
|
(yy[..., np.newaxis] - centers[:, 1])**2)
|
||||||
|
|
||||||
|
# Find the minimum distance for each pixel
|
||||||
|
min_distances = np.min(distances, axis=2)
|
||||||
|
|
||||||
|
# Identify which centers are at the minimum distance for each pixel
|
||||||
|
closest_centers = (distances == min_distances[..., np.newaxis])
|
||||||
|
|
||||||
|
# Count how many centers are equally close to each pixel
|
||||||
|
num_closest = np.sum(closest_centers, axis=2)
|
||||||
|
|
||||||
|
# Initialize the image to white
|
||||||
|
data = np.ones((height, width, 3), dtype=np.uint8) * 255
|
||||||
|
|
||||||
|
# Assign colors to pixels where only one center is closest
|
||||||
|
for i, color in enumerate(colors):
|
||||||
|
mask = (closest_centers[:, :, i]) & (num_closest == 1)
|
||||||
|
data[mask] = color
|
||||||
|
|
||||||
|
# Assign edge color (e.g., red) where multiple centers are equally close
|
||||||
|
edge_mask = (num_closest > 1)
|
||||||
|
data[edge_mask] = [255, 0, 0] # Red color for edges
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def update_image():
|
||||||
|
"""
|
||||||
|
Update the Voronoi image and the scatter plot of centers.
|
||||||
|
"""
|
||||||
|
img_data = get_voronoi_image(centers, width=1000, height=1000, colors=colors)
|
||||||
|
im.set_data(img_data)
|
||||||
|
center_scat.set_offsets(centers)
|
||||||
|
plt.draw()
|
||||||
|
|
||||||
|
def add_center_via_button(event):
|
||||||
|
"""
|
||||||
|
Add a new random center with a random color and update the image.
|
||||||
|
"""
|
||||||
|
global centers, colors
|
||||||
|
# Generate a new random center within the image bounds
|
||||||
|
new_center = np.random.randint(0, 1000, size=2)
|
||||||
|
centers = np.vstack([centers, new_center])
|
||||||
|
|
||||||
|
# Generate a random color
|
||||||
|
new_color = np.random.randint(0, 256, size=3)
|
||||||
|
colors = np.vstack([colors, new_color])
|
||||||
|
|
||||||
|
update_image()
|
||||||
|
update_buttons()
|
||||||
|
|
||||||
|
def add_center_via_click(event):
|
||||||
|
"""
|
||||||
|
Add a new center at the clicked location with a random color.
|
||||||
|
"""
|
||||||
|
global centers, colors
|
||||||
|
# Ensure the click is within the axes
|
||||||
|
if event.inaxes != ax:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the x and y coordinates from the click
|
||||||
|
x, y = event.xdata, event.ydata
|
||||||
|
if x is None or y is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Convert to integer pixel positions
|
||||||
|
x_int, y_int = int(x), int(y)
|
||||||
|
|
||||||
|
# Ensure the coordinates are within the image bounds
|
||||||
|
if 0 <= x_int < 1000 and 0 <= y_int < 1000:
|
||||||
|
new_center = np.array([x_int, y_int])
|
||||||
|
centers = np.vstack([centers, new_center])
|
||||||
|
|
||||||
|
# Generate a random color
|
||||||
|
new_color = np.random.randint(0, 256, size=3)
|
||||||
|
colors = np.vstack([colors, new_color])
|
||||||
|
|
||||||
|
update_image()
|
||||||
|
update_buttons()
|
||||||
|
|
||||||
|
def remove_center(event):
|
||||||
|
"""
|
||||||
|
Remove the most recently added center and update the image.
|
||||||
|
"""
|
||||||
|
global centers, colors
|
||||||
|
if len(centers) == 0:
|
||||||
|
print("No centers to remove.")
|
||||||
|
return
|
||||||
|
# Remove the last center and its color
|
||||||
|
centers = centers[:-1]
|
||||||
|
colors = colors[:-1]
|
||||||
|
|
||||||
|
update_image()
|
||||||
|
update_buttons()
|
||||||
|
|
||||||
|
def update_buttons():
|
||||||
|
"""
|
||||||
|
Enable or disable the 'Back' button based on the number of centers.
|
||||||
|
"""
|
||||||
|
if len(centers) == 0:
|
||||||
|
back_button.ax.set_visible(False)
|
||||||
|
else:
|
||||||
|
back_button.ax.set_visible(True)
|
||||||
|
plt.draw()
|
||||||
|
|
||||||
|
# Setup the figure and axes
|
||||||
|
fig, ax = plt.subplots(figsize=(8, 8))
|
||||||
|
plt.subplots_adjust(bottom=0.2) # Adjust to make space for buttons
|
||||||
|
|
||||||
|
# Initial image (white)
|
||||||
|
initial_data = get_voronoi_image(centers, width=1000, height=1000, colors=colors)
|
||||||
|
im = ax.imshow(initial_data, origin='upper')
|
||||||
|
|
||||||
|
# Set axes limits to match image coordinates
|
||||||
|
ax.set_xlim(0, 1000)
|
||||||
|
ax.set_ylim(1000, 0)
|
||||||
|
ax.set_xticks([])
|
||||||
|
ax.set_yticks([])
|
||||||
|
ax.set_title("Interactive Voronoi Diagram\nAdd Centers: Click or Use 'Forward' Button")
|
||||||
|
|
||||||
|
# Scatter plot for centers (initially empty)
|
||||||
|
center_scat = ax.scatter([], [], s=100, edgecolors='white', linewidths=1.5)
|
||||||
|
|
||||||
|
# Define button axes positions [left, bottom, width, height]
|
||||||
|
button_width = 0.15
|
||||||
|
button_height = 0.075
|
||||||
|
button_spacing = 0.05
|
||||||
|
|
||||||
|
# Forward Button (Add Random Center)
|
||||||
|
ax_forward = plt.axes([0.35, 0.05, button_width, button_height])
|
||||||
|
forward_button = Button(ax_forward, 'Forward')
|
||||||
|
forward_button.on_clicked(add_center_via_button)
|
||||||
|
|
||||||
|
# Back Button (Remove Last Center)
|
||||||
|
ax_back = plt.axes([0.35 + button_width + button_spacing, 0.05, button_width, button_height])
|
||||||
|
back_button = Button(ax_back, 'Back')
|
||||||
|
back_button.on_clicked(remove_center)
|
||||||
|
|
||||||
|
# Initially hide the 'Back' button since there are no centers
|
||||||
|
back_button.ax.set_visible(False)
|
||||||
|
|
||||||
|
# Connect the click event to the handler
|
||||||
|
cid = fig.canvas.mpl_connect('button_press_event', add_center_via_click)
|
||||||
|
|
||||||
|
# Display the plot
|
||||||
|
plt.show()
|
Loading…
x
Reference in New Issue
Block a user