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