Spaces:
Build error
Build error
Commit ·
8a01cdc
1
Parent(s): d533e21
Updated the app to adjust the image generation
Browse files- app.py +48 -25
- bulk_bulge_generation.py +68 -0
- transformation.py +157 -0
app.py
CHANGED
|
@@ -3,27 +3,19 @@ import gradio as gr
|
|
| 3 |
from PIL import Image
|
| 4 |
from scipy import ndimage
|
| 5 |
import matplotlib.pyplot as plt
|
|
|
|
| 6 |
|
| 7 |
-
def apply_vector_field_transform(image, func, radius, center=(0.5, 0.5), strength=1, edge_smoothness=0.1):
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
:param image: Input image as a numpy array (height, width, channels)
|
| 12 |
-
:param func: A function that takes x and y as inputs and returns a scalar
|
| 13 |
-
:param radius: Radius of the effect as a fraction of the image size
|
| 14 |
-
:param center: Tuple (y, x) for the center of the effect, normalized to [0, 1]
|
| 15 |
-
:param strength: Strength of the effect, scaled to image size
|
| 16 |
-
:param edge_smoothness: Width of the smooth transition at the edge, as a fraction of the radius
|
| 17 |
-
:return: Tuple of (transformed image as a numpy array, gradient vectors for vector field)
|
| 18 |
-
"""
|
| 19 |
rows, cols = image.shape[:2]
|
| 20 |
max_dim = max(rows, cols)
|
| 21 |
|
| 22 |
-
#
|
| 23 |
-
|
| 24 |
-
|
|
|
|
| 25 |
|
| 26 |
-
# Convert normalized radius to pixel radius
|
| 27 |
pixel_radius = int(max_dim * radius)
|
| 28 |
|
| 29 |
y, x = np.ogrid[:rows, :cols]
|
|
@@ -39,8 +31,16 @@ def apply_vector_field_transform(image, func, radius, center=(0.5, 0.5), strengt
|
|
| 39 |
# Calculate gradients
|
| 40 |
gy, gx = np.gradient(z)
|
| 41 |
|
| 42 |
-
#
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
# Apply mask to gradients
|
| 46 |
gx = gx * mask
|
|
@@ -123,7 +123,7 @@ def create_gradient_vector_field(gx, gy, image_shape, step=20, reverse=False):
|
|
| 123 |
|
| 124 |
return vector_field
|
| 125 |
|
| 126 |
-
def transform_image(image, func_choice, radius, center_x, center_y, strength, edge_smoothness, reverse_gradient=True, spiral_frequency=1):
|
| 127 |
I = np.asarray(Image.open(image))
|
| 128 |
|
| 129 |
def pinch(x, y):
|
|
@@ -133,9 +133,23 @@ def transform_image(image, func_choice, radius, center_x, center_y, strength, ed
|
|
| 133 |
return np.arctan2(y, x)
|
| 134 |
|
| 135 |
def bulge(x, y):
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
# return -1 / (r + 1)
|
| 138 |
-
return
|
| 139 |
|
| 140 |
def spiral(x, y, frequency=1):
|
| 141 |
r = np.sqrt(x**2 + y**2)
|
|
@@ -148,10 +162,17 @@ def transform_image(image, func_choice, radius, center_x, center_y, strength, ed
|
|
| 148 |
func = shift
|
| 149 |
elif func_choice == "Bulge":
|
| 150 |
func = bulge
|
| 151 |
-
elif func_choice == "Shift":
|
| 152 |
func = lambda x, y: spiral(x, y, frequency=spiral_frequency)
|
| 153 |
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
vector_field = create_gradient_vector_field(gx, gy, I.shape[:2], reverse=reverse_gradient)
|
| 156 |
|
| 157 |
return transformed, vector_field
|
|
@@ -160,12 +181,14 @@ demo = gr.Interface(
|
|
| 160 |
fn=transform_image,
|
| 161 |
inputs=[
|
| 162 |
gr.Image(type="filepath"),
|
| 163 |
-
gr.Dropdown(["Pinch", "Spiral", "Shift", "Bulge"], value="Bulge", label="Function"),
|
|
|
|
| 164 |
gr.Slider(0, 0.5, value=0.25, label="Radius (as fraction of image size)"),
|
| 165 |
gr.Slider(0, 1, value=0.5, label="Center X"),
|
| 166 |
gr.Slider(0, 1, value=0.5, label="Center Y"),
|
| 167 |
gr.Slider(0, 1, value=0.5, label="Strength"),
|
| 168 |
-
gr.Slider(0, 1, value=0.5, label="Edge Smoothness")
|
|
|
|
| 169 |
# gr.Checkbox(label="Reverse Gradient Direction"),
|
| 170 |
],
|
| 171 |
outputs=[
|
|
|
|
| 3 |
from PIL import Image
|
| 4 |
from scipy import ndimage
|
| 5 |
import matplotlib.pyplot as plt
|
| 6 |
+
from bulk_bulge_generation import definitions
|
| 7 |
|
| 8 |
+
def apply_vector_field_transform(image, func, radius, center=(0.5, 0.5), strength=1, edge_smoothness=0.1, center_smoothness=0.20):
|
| 9 |
+
# 0.106 strength = .50
|
| 10 |
+
# 0.106 strength = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
rows, cols = image.shape[:2]
|
| 12 |
max_dim = max(rows, cols)
|
| 13 |
|
| 14 |
+
#Normalize the positions
|
| 15 |
+
# Y Needs to be flipped
|
| 16 |
+
center_y = int(center[1] * rows)
|
| 17 |
+
center_x = int(center[0] * cols)
|
| 18 |
|
|
|
|
| 19 |
pixel_radius = int(max_dim * radius)
|
| 20 |
|
| 21 |
y, x = np.ogrid[:rows, :cols]
|
|
|
|
| 31 |
# Calculate gradients
|
| 32 |
gy, gx = np.gradient(z)
|
| 33 |
|
| 34 |
+
# Creating a sigmoid function to apply to masks
|
| 35 |
+
def sigmoid(x, center, steepness):
|
| 36 |
+
return 1 / (1 + np.exp(-steepness * (x - center)))
|
| 37 |
+
|
| 38 |
+
# Masking
|
| 39 |
+
edge_mask = np.clip((radius - dist_from_center) / (radius * edge_smoothness), 0, 1)
|
| 40 |
+
|
| 41 |
+
center_mask = np.clip((dist_from_center - radius * center_smoothness) / (radius * center_smoothness), 0, 1)
|
| 42 |
+
|
| 43 |
+
mask = edge_mask * center_mask
|
| 44 |
|
| 45 |
# Apply mask to gradients
|
| 46 |
gx = gx * mask
|
|
|
|
| 123 |
|
| 124 |
return vector_field
|
| 125 |
|
| 126 |
+
def transform_image(image, func_choice, randomization_check, radius, center_x, center_y, strength, edge_smoothness, center_smoothness, reverse_gradient=True, spiral_frequency=1):
|
| 127 |
I = np.asarray(Image.open(image))
|
| 128 |
|
| 129 |
def pinch(x, y):
|
|
|
|
| 133 |
return np.arctan2(y, x)
|
| 134 |
|
| 135 |
def bulge(x, y):
|
| 136 |
+
# Where does this make an array???:w
|
| 137 |
+
|
| 138 |
+
print(x.shape)
|
| 139 |
+
print(y.shape)
|
| 140 |
+
# Mess with this number
|
| 141 |
+
# num = 10
|
| 142 |
+
|
| 143 |
+
# if x < num or y < num:
|
| 144 |
+
# # This might not be correct
|
| 145 |
+
# return 1
|
| 146 |
+
# else:
|
| 147 |
+
# r = -np.sqrt(x**2 + y**2)
|
| 148 |
+
r = -np.sqrt(x**2 + y**2)
|
| 149 |
+
print(r.shape)
|
| 150 |
+
print(type(r))
|
| 151 |
# return -1 / (r + 1)
|
| 152 |
+
return r
|
| 153 |
|
| 154 |
def spiral(x, y, frequency=1):
|
| 155 |
r = np.sqrt(x**2 + y**2)
|
|
|
|
| 162 |
func = shift
|
| 163 |
elif func_choice == "Bulge":
|
| 164 |
func = bulge
|
| 165 |
+
elif func_choice == "Shift Up":
|
| 166 |
func = lambda x, y: spiral(x, y, frequency=spiral_frequency)
|
| 167 |
|
| 168 |
+
if randomization_check == True:
|
| 169 |
+
rng = np.random.default_rng()
|
| 170 |
+
radius, location, strength, edge_smoothness= definitions(rng)
|
| 171 |
+
center_x = location[0]
|
| 172 |
+
center_y = location[1]
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
transformed, (gx, gy) = apply_vector_field_transform(I, func, radius, (center_x, center_y), strength, edge_smoothness, center_smoothness)
|
| 176 |
vector_field = create_gradient_vector_field(gx, gy, I.shape[:2], reverse=reverse_gradient)
|
| 177 |
|
| 178 |
return transformed, vector_field
|
|
|
|
| 181 |
fn=transform_image,
|
| 182 |
inputs=[
|
| 183 |
gr.Image(type="filepath"),
|
| 184 |
+
gr.Dropdown(["Pinch", "Spiral", "Shift Up", "Bulge"], value="Bulge", label="Function"),
|
| 185 |
+
gr.Checkbox(label="Randomize inputs?"),
|
| 186 |
gr.Slider(0, 0.5, value=0.25, label="Radius (as fraction of image size)"),
|
| 187 |
gr.Slider(0, 1, value=0.5, label="Center X"),
|
| 188 |
gr.Slider(0, 1, value=0.5, label="Center Y"),
|
| 189 |
gr.Slider(0, 1, value=0.5, label="Strength"),
|
| 190 |
+
gr.Slider(0, 1, value=0.5, label="Edge Smoothness"),
|
| 191 |
+
gr.Slider(0, 0.5, value=0.1, label="Center Smoothness")
|
| 192 |
# gr.Checkbox(label="Reverse Gradient Direction"),
|
| 193 |
],
|
| 194 |
outputs=[
|
bulk_bulge_generation.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from PIL import Image
|
| 3 |
+
from transformation import apply_vector_field_transform
|
| 4 |
+
import os
|
| 5 |
+
from os import path
|
| 6 |
+
|
| 7 |
+
def definitions(generator):
|
| 8 |
+
|
| 9 |
+
# The image bulge should be entirly contained within the image. For instance, if we have a radius of 0.5 (the max), the image should be force to be at 0.5 (x and y) locations.
|
| 10 |
+
radius = generator.random() * 0.5
|
| 11 |
+
# radius = generator.normal(loc=0.5, scale=(0.5/4))
|
| 12 |
+
# print(f"Radius is {radius}")
|
| 13 |
+
# strengtH = Generator.random()
|
| 14 |
+
strength = generator.normal(loc=1, scale=(1/6))
|
| 15 |
+
# print(f"Strength is {strength}")
|
| 16 |
+
|
| 17 |
+
smoothness = generator.normal(loc=1, scale=(1/6))
|
| 18 |
+
# print(f"Smoothness is {smoothness}")
|
| 19 |
+
|
| 20 |
+
# size = 3
|
| 21 |
+
|
| 22 |
+
vmin = min([1-radius, radius])
|
| 23 |
+
vmax = max([1-radius, radius])
|
| 24 |
+
print()
|
| 25 |
+
print("Radius is {radius}")
|
| 26 |
+
print("Max and Min positions to calculate mean + std")
|
| 27 |
+
print(vmin)
|
| 28 |
+
print(vmax)
|
| 29 |
+
print()
|
| 30 |
+
|
| 31 |
+
print("Mean and Std Dev")
|
| 32 |
+
mean = (vmax+vmin) / 2
|
| 33 |
+
std = (vmax-vmin) / 4
|
| 34 |
+
print(mean)
|
| 35 |
+
print(std)
|
| 36 |
+
print()
|
| 37 |
+
# x = generator.normal(loc=mean, scale=std, size=(2))
|
| 38 |
+
x = np.random.uniform(low=vmin, high=vmax, size=(2))
|
| 39 |
+
# print(x)
|
| 40 |
+
|
| 41 |
+
# print(f"({np.random.uniform(vmin, vmax)}, {np.random.uniform(vmin, vmax)})")
|
| 42 |
+
# print(f"({x[0]}, {x[1]})")
|
| 43 |
+
return radius, x, strength, smoothness
|
| 44 |
+
|
| 45 |
+
def bulge(x, y):
|
| 46 |
+
return -np.sqrt(x**2 + y**2)
|
| 47 |
+
|
| 48 |
+
if __name__ == "__main__":
|
| 49 |
+
# Sets the numpy generator
|
| 50 |
+
rng = np.random.default_rng()
|
| 51 |
+
|
| 52 |
+
os.chdir("data/")
|
| 53 |
+
os.makedirs("grid", exist_ok=True)
|
| 54 |
+
os.makedirs("output", exist_ok=True)
|
| 55 |
+
files = os.listdir("grid/")
|
| 56 |
+
os.chdir("grid/")
|
| 57 |
+
|
| 58 |
+
for _ in files:
|
| 59 |
+
rad, location, strth, smth = definitions(rng)
|
| 60 |
+
I = np.asarray(Image.open(_))
|
| 61 |
+
transformed, (gx, gy) = apply_vector_field_transform(I, bulge, rad, location, strth, smth)
|
| 62 |
+
|
| 63 |
+
os.chdir("../output/")
|
| 64 |
+
result = Image.fromarray(transformed)
|
| 65 |
+
result.save(f"{_.title()}.jpg")
|
| 66 |
+
os.chdir("../grid/")
|
| 67 |
+
|
| 68 |
+
|
transformation.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from PIL import Image
|
| 3 |
+
from scipy import ndimage
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
|
| 6 |
+
def apply_vector_field_transform(image, func, radius, center=(0.5, 0.5), strength=1, edge_smoothness=0.1):
|
| 7 |
+
"""
|
| 8 |
+
Apply a vector field transformation to an image based on a given multivariate function.
|
| 9 |
+
|
| 10 |
+
:param image: Input image as a numpy array (height, width, channels)
|
| 11 |
+
:param func: A function that takes x and y as inputs and returns a scalar
|
| 12 |
+
:param radius: Radius of the effect as a fraction of the image size
|
| 13 |
+
:param center: Tuple (y, x) for the center of the effect, normalized to [0, 1]
|
| 14 |
+
:param strength: Strength of the effect, scaled to image size
|
| 15 |
+
:param edge_smoothness: Width of the smooth transition at the edge, as a fraction of the radius
|
| 16 |
+
:return: Tuple of (transformed image as a numpy array, gradient vectors for vector field)
|
| 17 |
+
"""
|
| 18 |
+
rows, cols = image.shape[:2]
|
| 19 |
+
max_dim = max(rows, cols)
|
| 20 |
+
|
| 21 |
+
# Convert normalized center to pixel coordinates
|
| 22 |
+
center_y = int(center[0] * rows)
|
| 23 |
+
center_x = int(center[1] * cols)
|
| 24 |
+
|
| 25 |
+
# Convert normalized radius to pixel radius
|
| 26 |
+
pixel_radius = int(max_dim * radius)
|
| 27 |
+
|
| 28 |
+
y, x = np.ogrid[:rows, :cols]
|
| 29 |
+
y = (y - center_y) / max_dim
|
| 30 |
+
x = (x - center_x) / max_dim
|
| 31 |
+
|
| 32 |
+
# Calculate distance from center
|
| 33 |
+
dist_from_center = np.sqrt(x**2 + y**2)
|
| 34 |
+
|
| 35 |
+
# Calculate function values
|
| 36 |
+
z = func(x, y)
|
| 37 |
+
|
| 38 |
+
# Calculate gradients
|
| 39 |
+
gy, gx = np.gradient(z)
|
| 40 |
+
|
| 41 |
+
# Create smooth transition mask
|
| 42 |
+
mask = np.clip((radius - dist_from_center) / (radius * edge_smoothness), 0, 1)
|
| 43 |
+
|
| 44 |
+
# Apply mask to gradients
|
| 45 |
+
gx = gx * mask
|
| 46 |
+
gy = gy * mask
|
| 47 |
+
|
| 48 |
+
# Normalize gradient vectors
|
| 49 |
+
magnitude = np.sqrt(gx**2 + gy**2)
|
| 50 |
+
magnitude[magnitude == 0] = 1 # Avoid division by zero
|
| 51 |
+
gx = gx / magnitude
|
| 52 |
+
gy = gy / magnitude
|
| 53 |
+
|
| 54 |
+
# Scale the effect (Play with the number 5)
|
| 55 |
+
scale_factor = strength * np.log(max_dim) / 100 # Adjust strength based on image size
|
| 56 |
+
gx = gx * scale_factor * mask
|
| 57 |
+
gy = gy * scale_factor * mask
|
| 58 |
+
|
| 59 |
+
# Create the mapping
|
| 60 |
+
x_new = x + gx
|
| 61 |
+
y_new = y + gy
|
| 62 |
+
|
| 63 |
+
# Convert back to pixel coordinates
|
| 64 |
+
x_new = x_new * max_dim + center_x
|
| 65 |
+
y_new = y_new * max_dim + center_y
|
| 66 |
+
|
| 67 |
+
# Ensure the new coordinates are within the image boundaries
|
| 68 |
+
x_new = np.clip(x_new, 0, cols - 1)
|
| 69 |
+
y_new = np.clip(y_new, 0, rows - 1)
|
| 70 |
+
|
| 71 |
+
# Apply the transformation to each channel
|
| 72 |
+
channels = [ndimage.map_coordinates(image[..., i], [y_new, x_new], order=1, mode='reflect')
|
| 73 |
+
for i in range(image.shape[2])]
|
| 74 |
+
|
| 75 |
+
transformed_image = np.dstack(channels).astype(image.dtype)
|
| 76 |
+
|
| 77 |
+
return transformed_image, (gx, gy)
|
| 78 |
+
|
| 79 |
+
def create_gradient_vector_field(gx, gy, image_shape, step=20, reverse=False):
|
| 80 |
+
"""
|
| 81 |
+
Create a gradient vector field visualization with option to reverse direction.
|
| 82 |
+
|
| 83 |
+
:param gx: X-component of the gradient
|
| 84 |
+
:param gy: Y-component of the gradient
|
| 85 |
+
:param image_shape: Shape of the original image (height, width)
|
| 86 |
+
:param step: Spacing between arrows
|
| 87 |
+
:param reverse: If True, reverse the direction of the arrows
|
| 88 |
+
:return: Gradient vector field as a numpy array (RGB image)
|
| 89 |
+
"""
|
| 90 |
+
rows, cols = image_shape
|
| 91 |
+
y, x = np.mgrid[step/2:rows:step, step/2:cols:step].reshape(2, -1).astype(int)
|
| 92 |
+
|
| 93 |
+
# Calculate the scale based on image size
|
| 94 |
+
max_dim = max(rows, cols)
|
| 95 |
+
scale = max_dim / 1000 # Adjusted for longer arrows
|
| 96 |
+
|
| 97 |
+
# Reverse direction if specified
|
| 98 |
+
direction = -1 if reverse else 1
|
| 99 |
+
|
| 100 |
+
fig, ax = plt.subplots(figsize=(cols/50, rows/50), dpi=100)
|
| 101 |
+
ax.quiver(x, y, direction * gx[y, x], direction * -gy[y, x],
|
| 102 |
+
scale=scale,
|
| 103 |
+
scale_units='width',
|
| 104 |
+
width=0.002 * max_dim / 500,
|
| 105 |
+
headwidth=8,
|
| 106 |
+
headlength=12,
|
| 107 |
+
headaxislength=0,
|
| 108 |
+
color='black',
|
| 109 |
+
minshaft=2,
|
| 110 |
+
minlength=0,
|
| 111 |
+
pivot='tail')
|
| 112 |
+
ax.set_xlim(0, cols)
|
| 113 |
+
ax.set_ylim(rows, 0)
|
| 114 |
+
ax.set_aspect('equal')
|
| 115 |
+
ax.axis('off')
|
| 116 |
+
|
| 117 |
+
fig.tight_layout(pad=0)
|
| 118 |
+
fig.canvas.draw()
|
| 119 |
+
vector_field = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
|
| 120 |
+
vector_field = vector_field.reshape(fig.canvas.get_width_height()[::-1] + (3,))
|
| 121 |
+
plt.close(fig)
|
| 122 |
+
|
| 123 |
+
return vector_field
|
| 124 |
+
|
| 125 |
+
def transform_image(image, func_choice, radius, center_x, center_y, strength, edge_smoothness, reverse_gradient=True, spiral_frequency=1):
|
| 126 |
+
I = np.asarray(Image.open(image))
|
| 127 |
+
|
| 128 |
+
def pinch(x, y):
|
| 129 |
+
return x**2 + y**2
|
| 130 |
+
|
| 131 |
+
def shift(x, y):
|
| 132 |
+
return np.arctan2(y, x)
|
| 133 |
+
|
| 134 |
+
def bulge(x, y):
|
| 135 |
+
r = np.sqrt(x**2 + y**2)
|
| 136 |
+
# return -1 / (r + 1)
|
| 137 |
+
return -r
|
| 138 |
+
|
| 139 |
+
def spiral(x, y, frequency=1):
|
| 140 |
+
r = np.sqrt(x**2 + y**2)
|
| 141 |
+
theta = np.arctan2(y, x)
|
| 142 |
+
return r * np.sin(theta - frequency * r)
|
| 143 |
+
|
| 144 |
+
if func_choice == "Pinch":
|
| 145 |
+
func = pinch
|
| 146 |
+
elif func_choice == "Spiral":
|
| 147 |
+
func = shift
|
| 148 |
+
elif func_choice == "Bulge":
|
| 149 |
+
func = bulge
|
| 150 |
+
elif func_choice == "Shift":
|
| 151 |
+
func = lambda x, y: spiral(x, y, frequency=spiral_frequency)
|
| 152 |
+
|
| 153 |
+
transformed, (gx, gy) = apply_vector_field_transform(I, func, radius, (center_y, center_x), strength, edge_smoothness)
|
| 154 |
+
vector_field = create_gradient_vector_field(gx, gy, I.shape[:2], reverse=reverse_gradient)
|
| 155 |
+
|
| 156 |
+
return transformed, vector_field
|
| 157 |
+
|