Spaces:
Build error
Build error
| import numpy as np | |
| from PIL import Image | |
| from scipy import ndimage | |
| import matplotlib.pyplot as plt | |
| def apply_vector_field_transform(image, func, radius, center=(0.5, 0.5), strength=1, edge_smoothness=0.1): | |
| """ | |
| Apply a vector field transformation to an image based on a given multivariate function. | |
| :param image: Input image as a numpy array (height, width, channels) | |
| :param func: A function that takes x and y as inputs and returns a scalar | |
| :param radius: Radius of the effect as a fraction of the image size | |
| :param center: Tuple (y, x) for the center of the effect, normalized to [0, 1] | |
| :param strength: Strength of the effect, scaled to image size | |
| :param edge_smoothness: Width of the smooth transition at the edge, as a fraction of the radius | |
| :return: Tuple of (transformed image as a numpy array, gradient vectors for vector field) | |
| """ | |
| rows, cols = image.shape[:2] | |
| max_dim = max(rows, cols) | |
| # Convert normalized center to pixel coordinates | |
| center_y = int(center[0] * rows) | |
| center_x = int(center[1] * cols) | |
| # Convert normalized radius to pixel radius | |
| pixel_radius = int(max_dim * radius) | |
| y, x = np.ogrid[:rows, :cols] | |
| y = (y - center_y) / max_dim | |
| x = (x - center_x) / max_dim | |
| # Calculate distance from center | |
| dist_from_center = np.sqrt(x**2 + y**2) | |
| # Calculate function values | |
| z = func(x, y) | |
| # Calculate gradients | |
| gy, gx = np.gradient(z) | |
| # Create smooth transition mask | |
| mask = np.clip((radius - dist_from_center) / (radius * edge_smoothness), 0, 1) | |
| # Apply mask to gradients | |
| gx = gx * mask | |
| gy = gy * mask | |
| # Normalize gradient vectors | |
| magnitude = np.sqrt(gx**2 + gy**2) | |
| magnitude[magnitude == 0] = 1 # Avoid division by zero | |
| gx = gx / magnitude | |
| gy = gy / magnitude | |
| # Scale the effect (Play with the number 5) | |
| scale_factor = strength * np.log(max_dim) / 100 # Adjust strength based on image size | |
| gx = gx * scale_factor * mask | |
| gy = gy * scale_factor * mask | |
| # Create the mapping | |
| x_new = x + gx | |
| y_new = y + gy | |
| # Convert back to pixel coordinates | |
| x_new = x_new * max_dim + center_x | |
| y_new = y_new * max_dim + center_y | |
| # Ensure the new coordinates are within the image boundaries | |
| x_new = np.clip(x_new, 0, cols - 1) | |
| y_new = np.clip(y_new, 0, rows - 1) | |
| # Apply the transformation to each channel | |
| channels = [ndimage.map_coordinates(image[..., i], [y_new, x_new], order=1, mode='reflect') | |
| for i in range(image.shape[2])] | |
| transformed_image = np.dstack(channels).astype(image.dtype) | |
| return transformed_image, (gx, gy) | |
| def create_gradient_vector_field(gx, gy, image_shape, step=20, reverse=False): | |
| """ | |
| Create a gradient vector field visualization with option to reverse direction. | |
| :param gx: X-component of the gradient | |
| :param gy: Y-component of the gradient | |
| :param image_shape: Shape of the original image (height, width) | |
| :param step: Spacing between arrows | |
| :param reverse: If True, reverse the direction of the arrows | |
| :return: Gradient vector field as a numpy array (RGB image) | |
| """ | |
| rows, cols = image_shape | |
| y, x = np.mgrid[step/2:rows:step, step/2:cols:step].reshape(2, -1).astype(int) | |
| # Calculate the scale based on image size | |
| max_dim = max(rows, cols) | |
| scale = max_dim / 1000 # Adjusted for longer arrows | |
| # Reverse direction if specified | |
| direction = -1 if reverse else 1 | |
| fig, ax = plt.subplots(figsize=(cols/50, rows/50), dpi=100) | |
| ax.quiver(x, y, direction * gx[y, x], direction * -gy[y, x], | |
| scale=scale, | |
| scale_units='width', | |
| width=0.002 * max_dim / 500, | |
| headwidth=8, | |
| headlength=12, | |
| headaxislength=0, | |
| color='black', | |
| minshaft=2, | |
| minlength=0, | |
| pivot='tail') | |
| ax.set_xlim(0, cols) | |
| ax.set_ylim(rows, 0) | |
| ax.set_aspect('equal') | |
| ax.axis('off') | |
| fig.tight_layout(pad=0) | |
| fig.canvas.draw() | |
| vector_field = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) | |
| vector_field = vector_field.reshape(fig.canvas.get_width_height()[::-1] + (3,)) | |
| plt.close(fig) | |
| return vector_field | |
| def transform_image(image, func_choice, radius, center_x, center_y, strength, edge_smoothness, reverse_gradient=True, spiral_frequency=1): | |
| I = np.asarray(Image.open(image)) | |
| def pinch(x, y): | |
| return x**2 + y**2 | |
| def shift(x, y): | |
| return np.arctan2(y, x) | |
| def bulge(x, y): | |
| r = np.sqrt(x**2 + y**2) | |
| # return -1 / (r + 1) | |
| return -r | |
| def spiral(x, y, frequency=1): | |
| r = np.sqrt(x**2 + y**2) | |
| theta = np.arctan2(y, x) | |
| return r * np.sin(theta - frequency * r) | |
| if func_choice == "Pinch": | |
| func = pinch | |
| elif func_choice == "Spiral": | |
| func = shift | |
| elif func_choice == "Bulge": | |
| func = bulge | |
| elif func_choice == "Shift": | |
| func = lambda x, y: spiral(x, y, frequency=spiral_frequency) | |
| transformed, (gx, gy) = apply_vector_field_transform(I, func, radius, (center_y, center_x), strength, edge_smoothness) | |
| vector_field = create_gradient_vector_field(gx, gy, I.shape[:2], reverse=reverse_gradient) | |
| return transformed, vector_field | |