Spaces:
Runtime error
Runtime error
initial commit
Browse files
README.md
CHANGED
@@ -9,4 +9,7 @@ app_file: app.py
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
|
|
|
|
|
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
+
|
13 |
+
Convert a spritesheet (that is slightly misaligned) to a gif automagically
|
14 |
+
|
15 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from PIL import Image
|
3 |
+
from io import BytesIO
|
4 |
+
import base64
|
5 |
+
import requests
|
6 |
+
from io import BytesIO
|
7 |
+
|
8 |
+
|
9 |
+
from collections import Counter
|
10 |
+
|
11 |
+
from PIL import Image
|
12 |
+
|
13 |
+
import numpy as np
|
14 |
+
|
15 |
+
import matplotlib.pyplot as plt
|
16 |
+
|
17 |
+
|
18 |
+
def compute_fft_cross_correlation(img1, img2):
|
19 |
+
|
20 |
+
fft1 = np.fft.fft2(img1)
|
21 |
+
|
22 |
+
fft2 = np.fft.fft2(np.rot90(img2, 2), s=img1.shape)
|
23 |
+
|
24 |
+
result = np.fft.ifft2(fft1 * fft2).real
|
25 |
+
|
26 |
+
return result
|
27 |
+
|
28 |
+
|
29 |
+
|
30 |
+
def compute_offsets(reference, images, window_size):
|
31 |
+
|
32 |
+
reference_gray = np.array(reference.convert('L'))
|
33 |
+
|
34 |
+
offsets = []
|
35 |
+
|
36 |
+
for img in images:
|
37 |
+
|
38 |
+
img_gray = np.array(img.convert('L'))
|
39 |
+
|
40 |
+
correlation = compute_fft_cross_correlation(reference_gray, img_gray)
|
41 |
+
|
42 |
+
# Roll the correlation by half the width and height
|
43 |
+
height, width = correlation.shape
|
44 |
+
correlation = np.roll(correlation, height // 2, axis=0)
|
45 |
+
correlation = np.roll(correlation, width // 2, axis=1)
|
46 |
+
|
47 |
+
|
48 |
+
# Find the peak in the central region of the correlation
|
49 |
+
center_x, center_y = height // 2, width // 2
|
50 |
+
start_x, start_y = center_x - window_size // 2, center_y - window_size // 2
|
51 |
+
end_x, end_y = start_x + window_size, start_y + window_size
|
52 |
+
|
53 |
+
#make sure starts and ends are in the range(0,height) and (0,width)
|
54 |
+
start_x = max(start_x,0)
|
55 |
+
start_y = max(start_y,0)
|
56 |
+
end_x = min(end_x,height-1)
|
57 |
+
end_y = min(end_y,width-1)
|
58 |
+
|
59 |
+
|
60 |
+
window_size_x = end_x - start_x
|
61 |
+
window_size_y = end_y - start_y
|
62 |
+
|
63 |
+
|
64 |
+
peak_x, peak_y = np.unravel_index(np.argmax(correlation[start_x:end_x, start_y:end_y]), (window_size_x, window_size_y))
|
65 |
+
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
'''
|
70 |
+
#plot the correlation
|
71 |
+
fig, axs = plt.subplots(1, 5, figsize=(10, 5))
|
72 |
+
axs[0].imshow(reference_gray, cmap='gray')
|
73 |
+
axs[0].set_title('Reference')
|
74 |
+
axs[1].imshow(img_gray, cmap='gray')
|
75 |
+
axs[1].set_title('Image')
|
76 |
+
axs[2].imshow(correlation, cmap='hot', interpolation='nearest', extent=[-window_size, window_size, -window_size, window_size])
|
77 |
+
axs[2].set_title('Correlation')
|
78 |
+
axs[3].imshow(correlation, cmap='hot', interpolation='nearest')
|
79 |
+
axs[3].set_title('Correlation full')
|
80 |
+
axs[4].imshow(correlation[start_x:end_x, start_y:end_y], cmap='hot', interpolation='nearest')
|
81 |
+
axs[4].set_title('Correlation cropped')
|
82 |
+
plt.show()
|
83 |
+
|
84 |
+
|
85 |
+
print("what?",np.argmax(correlation[start_x:end_x, start_y:end_y]))
|
86 |
+
|
87 |
+
print(peak_x, peak_y,start_x,end_x,start_y,end_y,center_x,center_y)
|
88 |
+
'''
|
89 |
+
|
90 |
+
|
91 |
+
# Compute the offset in the range [-window_size, window_size]
|
92 |
+
peak_x += start_x - center_x + 1
|
93 |
+
peak_y += start_y - center_y + 1
|
94 |
+
|
95 |
+
#signs are wrong
|
96 |
+
#peak_x = -peak_x
|
97 |
+
#peak_y = -peak_y
|
98 |
+
|
99 |
+
print(peak_x, peak_y)
|
100 |
+
|
101 |
+
# Compute the offset in the range [-window_size, window_size]
|
102 |
+
if peak_x > correlation.shape[0] // 2:
|
103 |
+
peak_x -= correlation.shape[0]
|
104 |
+
if peak_y > correlation.shape[1] // 2:
|
105 |
+
peak_y -= correlation.shape[1]
|
106 |
+
|
107 |
+
if peak_x >= 0:
|
108 |
+
peak_x = min(peak_x, window_size)
|
109 |
+
else:
|
110 |
+
peak_x = max(peak_x, -window_size)
|
111 |
+
|
112 |
+
if peak_y >= 0:
|
113 |
+
peak_y = min(peak_y, window_size)
|
114 |
+
else:
|
115 |
+
peak_y = max(peak_y, -window_size)
|
116 |
+
|
117 |
+
offsets.append((peak_x, peak_y))
|
118 |
+
|
119 |
+
return offsets
|
120 |
+
|
121 |
+
|
122 |
+
def find_most_common_color(image):
|
123 |
+
|
124 |
+
pixels = list(image.getdata())
|
125 |
+
|
126 |
+
color_counter = Counter(pixels)
|
127 |
+
|
128 |
+
return color_counter.most_common(1)[0][0]
|
129 |
+
|
130 |
+
|
131 |
+
|
132 |
+
def slice_frames_final(original, centers, frame_width, frame_height, background_color=(255, 255, 0, 255)):
|
133 |
+
|
134 |
+
sliced_frames = []
|
135 |
+
|
136 |
+
original_width, original_height = original.size
|
137 |
+
|
138 |
+
for center_x, center_y in centers:
|
139 |
+
|
140 |
+
left = center_x - frame_width // 2
|
141 |
+
|
142 |
+
upper = center_y - frame_height // 2
|
143 |
+
|
144 |
+
right = left + frame_width
|
145 |
+
|
146 |
+
lower = upper + frame_height
|
147 |
+
|
148 |
+
new_frame = Image.new("RGBA", (frame_width, frame_height), background_color)
|
149 |
+
|
150 |
+
paste_x = max(0, -left)
|
151 |
+
|
152 |
+
paste_y = max(0, -upper)
|
153 |
+
|
154 |
+
cropped_frame = original.crop((max(0, left), max(0, upper), min(original_width, right), min(original_height, lower)))
|
155 |
+
|
156 |
+
new_frame.paste(cropped_frame, (paste_x, paste_y))
|
157 |
+
|
158 |
+
sliced_frames.append(new_frame)
|
159 |
+
|
160 |
+
return sliced_frames
|
161 |
+
|
162 |
+
|
163 |
+
|
164 |
+
def create_aligned_gif(original_image, columns_per_row, window_size=200, duration=100,output_gif_path = 'output.gif'):
|
165 |
+
|
166 |
+
|
167 |
+
original_width, original_height = original_image.size
|
168 |
+
|
169 |
+
rows = len(columns_per_row)
|
170 |
+
|
171 |
+
total_frames = sum(columns_per_row)
|
172 |
+
|
173 |
+
background_color = find_most_common_color(original_image)
|
174 |
+
|
175 |
+
frame_height = original_height // rows
|
176 |
+
|
177 |
+
min_frame_width = min([original_width // cols for cols in columns_per_row])
|
178 |
+
|
179 |
+
frames = []
|
180 |
+
|
181 |
+
for i in range(rows):
|
182 |
+
|
183 |
+
frame_width = original_width // columns_per_row[i]
|
184 |
+
|
185 |
+
for j in range(columns_per_row[i]):
|
186 |
+
|
187 |
+
left = j * frame_width + (frame_width - min_frame_width) // 2
|
188 |
+
|
189 |
+
upper = i * frame_height
|
190 |
+
|
191 |
+
right = left + min_frame_width
|
192 |
+
|
193 |
+
lower = upper + frame_height
|
194 |
+
|
195 |
+
frame = original_image.crop((left, upper, right, lower))
|
196 |
+
|
197 |
+
frames.append(frame)
|
198 |
+
|
199 |
+
fft_offsets = compute_offsets(frames[0], frames, window_size=window_size)
|
200 |
+
|
201 |
+
center_coordinates = []
|
202 |
+
|
203 |
+
frame_idx = 0
|
204 |
+
|
205 |
+
for i in range(rows):
|
206 |
+
|
207 |
+
frame_width = original_width // columns_per_row[i]
|
208 |
+
|
209 |
+
for j in range(columns_per_row[i]):
|
210 |
+
|
211 |
+
offset_y,offset_x = fft_offsets[frame_idx]
|
212 |
+
|
213 |
+
center_x = j * frame_width + (frame_width) // 2 - offset_x
|
214 |
+
|
215 |
+
center_y = frame_height * i + frame_height//2 - offset_y
|
216 |
+
|
217 |
+
center_coordinates.append((center_x, center_y))
|
218 |
+
|
219 |
+
frame_idx += 1
|
220 |
+
|
221 |
+
sliced_frames = slice_frames_final(original_image, center_coordinates, min_frame_width, frame_height, background_color=background_color)
|
222 |
+
|
223 |
+
|
224 |
+
|
225 |
+
sliced_frames[0].save(output_gif_path, save_all=True, append_images=sliced_frames[1:], loop=0, duration=duration)
|
226 |
+
|
227 |
+
'''
|
228 |
+
#display frames
|
229 |
+
for frame in sliced_frames:
|
230 |
+
plt.figure()
|
231 |
+
plt.imshow(frame)
|
232 |
+
'''
|
233 |
+
|
234 |
+
|
235 |
+
return output_gif_path
|
236 |
+
|
237 |
+
def wrapper_func(img, columns_per_row_str):
|
238 |
+
#img = Image.open(BytesIO(file))
|
239 |
+
|
240 |
+
#img = Image.fromarray(img_arr)
|
241 |
+
|
242 |
+
columns_per_row = [int(x.strip()) for x in columns_per_row_str.split(',')]
|
243 |
+
output_gif_path = 'output.gif'
|
244 |
+
|
245 |
+
|
246 |
+
print("about to die",img,columns_per_row)
|
247 |
+
|
248 |
+
create_aligned_gif(img, columns_per_row)
|
249 |
+
#with open(output_gif_path, "rb") as f:
|
250 |
+
#return base64.b64encode(f.read()).decode()
|
251 |
+
# Image.open(output_gif_path)
|
252 |
+
|
253 |
+
return output_gif_path
|
254 |
+
|
255 |
+
|
256 |
+
# Example image in the form of a NumPy array
|
257 |
+
#example_image = Image.open("https://raw.githubusercontent.com/nagolinc/notebooks/main/ss5.png")
|
258 |
+
|
259 |
+
url = "https://raw.githubusercontent.com/nagolinc/notebooks/main/ss5.png"
|
260 |
+
response = requests.get(url)
|
261 |
+
example_image = Image.open(BytesIO(response.content))
|
262 |
+
|
263 |
+
# Example for "Columns per Row" as a string
|
264 |
+
example_columns_per_row = "5,5,5"
|
265 |
+
|
266 |
+
|
267 |
+
|
268 |
+
iface = gr.Interface(
|
269 |
+
fn=wrapper_func,
|
270 |
+
inputs=[
|
271 |
+
gr.components.Image(label="Upload Spritesheet",type='pil'),
|
272 |
+
gr.components.Textbox(label="Columns per Row", default="3,4,3")
|
273 |
+
],
|
274 |
+
outputs=gr.components.Image(type="filepath", label="Generated GIF"),
|
275 |
+
examples=[[example_image, example_columns_per_row]], # Adding examples here
|
276 |
+
live=False,
|
277 |
+
server_name="Hugging Face Spaces",
|
278 |
+
server_port=80,
|
279 |
+
analytics_enabled=False
|
280 |
+
)
|
281 |
+
|
282 |
+
iface.launch()
|