Spaces:
Running
on
Zero
Running
on
Zero
""" | |
Program to compute Voronoi diagram using JFA. | |
@author yisiox | |
@version September 2022 | |
""" | |
import cupy as cp | |
from random import sample | |
# global variables | |
x_dim = 512 | |
y_dim = 512 | |
noSeeds = 1024 | |
# diagram is represented as a 2d array where each element is | |
# x coord of source * y_dim + y coord of source | |
ping = cp.full((x_dim, y_dim), -1, dtype=int) | |
pong = None | |
import torch | |
import time | |
def process_tensors(tensor1, tensor2): | |
# start_time = time.time() | |
tensor2_unique = torch.unique(tensor2) | |
mask = torch.isin(tensor1, tensor2_unique, assume_unique=True) | |
tensor1[~mask] = -1 | |
end_time = time.time() | |
# print(f"Computation time: {end_time - start_time} seconds") | |
return tensor1 | |
def test_performance(): | |
computation_times = [] | |
for _ in range(10): | |
tensor1 = torch.randint(0, 40001, (1024, 1024)).cuda() | |
tensor2 = torch.randint(5000, 15000, (512, 512)).cuda() | |
process_tensors(tensor1, tensor2) | |
def voronoi_solve(texture, mask, device="cuda"): | |
""" | |
This is a warpper of the original cupy voronoi implementation | |
The texture color where mask value is 1 will propagate to its | |
neighbors. | |
args: | |
texture - A multi-channel tensor, (H, W, C) | |
mask - A single-channel tensor, (H, W) | |
return: | |
texture - Propagated tensor | |
""" | |
h, w, c = texture.shape | |
# hwc_texture = texture.permute(1,2,0) | |
valid_pix_coord = torch.where(mask > 0) | |
indices = torch.arange(0, h * w).to(device).reshape(h, w) | |
idx_map = -1 * torch.ones((h, w), dtype=torch.int64).to(device) | |
idx_map[valid_pix_coord] = indices[valid_pix_coord] | |
ping = cp.asarray(idx_map) | |
pong = cp.copy(ping) | |
ping = JFAVoronoiDiagram(ping, pong) | |
voronoi_map = torch.as_tensor(ping, device=device) | |
nc_voronoi_texture = torch.index_select( | |
texture.reshape(h * w, c), 0, voronoi_map.reshape(h * w) | |
) | |
voronoi_texture = nc_voronoi_texture.reshape(h, w, c) | |
return voronoi_texture | |
def generateRandomSeeds(n): | |
""" | |
Function to generate n random seeds. | |
@param n The number of seeds to generate. | |
""" | |
global ping, pong | |
if n > x_dim * y_dim: | |
print("Error: Number of seeds greater than number of pixels.") | |
return | |
# take sample of cartesian product | |
coords = [(x, y) for x in range(x_dim) for y in range(y_dim)] | |
seeds = sample(coords, n) | |
for i in range(n): | |
x, y = seeds[i] | |
ping[x, y] = x * y_dim + y | |
pong = cp.copy(ping) | |
displayKernel = cp.ElementwiseKernel( | |
"int64 x", "int64 y", f"y = (x < 0) ? x : x % 103", "displayTransform" | |
) | |
voronoiKernel = cp.RawKernel( | |
r""" | |
extern "C" __global__ | |
void voronoiPass(const long long step, const long long xDim, const long long yDim, const long long *ping, long long *pong) { | |
long long idx = blockIdx.x * blockDim.x + threadIdx.x; | |
long long stp = blockDim.x * gridDim.x; | |
for (long long k = idx; k < xDim * yDim; k += stp) { | |
long long dydx[] = {-1, 0, 1}; | |
for (int i = 0; i < 3; ++i) { | |
for (int j = 0; j < 3; ++j) { | |
long long dx = (step * dydx[i]) * yDim; | |
long long dy = step * dydx[j]; | |
long long src = k + dx + dy; | |
if (src < 0 || src >= xDim * yDim) | |
continue; | |
if (ping[src] == -1) | |
continue; | |
if (pong[k] == -1) { | |
pong[k] = ping[src]; | |
continue; | |
} | |
long long x1 = k / yDim; | |
long long y1 = k % yDim; | |
long long x2 = pong[k] / yDim; | |
long long y2 = pong[k] % yDim; | |
long long x3 = ping[src] / yDim; | |
long long y3 = ping[src] % yDim; | |
long long curr_dist = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); | |
long long jump_dist = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3); | |
if (jump_dist < curr_dist) | |
pong[k] = ping[src]; | |
} | |
} | |
} | |
} | |
""", | |
"voronoiPass", | |
) | |
""" | |
y and x is actually w and h? (according to experiment result) | |
""" | |
def JFAVoronoiDiagram(ping, pong): | |
# global ping, pong | |
# compute initial step size | |
x_dim, y_dim = ping.shape | |
step = max(x_dim, y_dim) // 2 | |
# initalise frame number and display original state | |
frame = 0 | |
# iterate while step size is greater than 0 | |
while step: | |
voronoiKernel( | |
(min(x_dim, 512),), (min(y_dim, 512),), (step, x_dim, y_dim, ping, pong) | |
) | |
# Ajusted the upper bound of the kernel dimension from 1024 to 512 to avoid CUDA OUT OF RESOURCE problem | |
ping, pong = pong, ping | |
frame += 1 | |
step //= 2 | |
# displayDiagram(frame, ping) | |
return ping | |