Spaces:
Build error
Build error
nick-leland
commited on
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 |
+
|