IF3D / code /lib /libmise /mise.pyx
leobcc's picture
vid2avatar baseline
6325697
raw
history blame
13 kB
# distutils: language = c++
cimport cython
from libc.stdint cimport int32_t, int64_t
from cython.operator cimport dereference as dref
from libcpp.vector cimport vector
from libcpp.map cimport map
from libc.math cimport isnan, NAN
import numpy as np
cdef struct Vector3D:
int x, y, z
cdef struct Voxel:
Vector3D loc
unsigned int level
bint is_leaf
unsigned long children[2][2][2]
cdef struct GridPoint:
Vector3D loc
double value
bint known
cdef inline unsigned long vec_to_idx(Vector3D coord, long resolution):
cdef unsigned long idx
idx = resolution * resolution * coord.x + resolution * coord.y + coord.z
return idx
cdef class MISE:
cdef vector[Voxel] voxels
cdef vector[GridPoint] grid_points
cdef map[long, long] grid_point_hash
cdef readonly int resolution_0
cdef readonly int depth
cdef readonly double threshold
cdef readonly int voxel_size_0
cdef readonly int resolution
def __cinit__(self, int resolution_0, int depth, double threshold):
self.resolution_0 = resolution_0
self.depth = depth
self.threshold = threshold
self.voxel_size_0 = (1 << depth)
self.resolution = resolution_0 * self.voxel_size_0
# Create initial voxels
self.voxels.reserve(resolution_0 * resolution_0 * resolution_0)
cdef Voxel voxel
cdef GridPoint point
cdef Vector3D loc
cdef int i, j, k
for i in range(resolution_0):
for j in range(resolution_0):
for k in range (resolution_0):
loc = Vector3D(
i * self.voxel_size_0,
j * self.voxel_size_0,
k * self.voxel_size_0,
)
voxel = Voxel(
loc=loc,
level=0,
is_leaf=True,
)
assert(self.voxels.size() == vec_to_idx(Vector3D(i, j, k), resolution_0))
self.voxels.push_back(voxel)
# Create initial grid points
self.grid_points.reserve((resolution_0 + 1) * (resolution_0 + 1) * (resolution_0 + 1))
for i in range(resolution_0 + 1):
for j in range(resolution_0 + 1):
for k in range(resolution_0 + 1):
loc = Vector3D(
i * self.voxel_size_0,
j * self.voxel_size_0,
k * self.voxel_size_0,
)
assert(self.grid_points.size() == vec_to_idx(Vector3D(i, j, k), resolution_0 + 1))
self.add_grid_point(loc)
def update(self, int64_t[:, :] points, double[:] values):
"""Update points and set their values. Also determine all active voxels and subdivide them."""
assert(points.shape[0] == values.shape[0])
assert(points.shape[1] == 3)
cdef Vector3D loc
cdef long idx
cdef int i
# Find all indices of point and set value
for i in range(points.shape[0]):
loc = Vector3D(points[i, 0], points[i, 1], points[i, 2])
idx = self.get_grid_point_idx(loc)
if idx == -1:
raise ValueError('Point not in grid!')
self.grid_points[idx].value = values[i]
self.grid_points[idx].known = True
# Subdivide activate voxels and add new points
self.subdivide_voxels()
def query(self):
"""Query points to evaluate."""
# Find all points with unknown value
cdef vector[Vector3D] points
cdef int n_unknown = 0
for p in self.grid_points:
if not p.known:
n_unknown += 1
points.reserve(n_unknown)
for p in self.grid_points:
if not p.known:
points.push_back(p.loc)
# Convert to numpy
points_np = np.zeros((points.size(), 3), dtype=np.int64)
cdef int64_t[:, :] points_view = points_np
for i in range(points.size()):
points_view[i, 0] = points[i].x
points_view[i, 1] = points[i].y
points_view[i, 2] = points[i].z
return points_np
def to_dense(self):
"""Output dense matrix at highest resolution."""
out_array = np.full((self.resolution + 1,) * 3, np.nan)
cdef double[:, :, :] out_view = out_array
cdef GridPoint point
cdef int i, j, k
for point in self.grid_points:
# Take voxel for which points is upper left corner
# assert(point.known)
out_view[point.loc.x, point.loc.y, point.loc.z] = point.value
# Complete along x axis
for i in range(1, self.resolution + 1):
for j in range(self.resolution + 1):
for k in range(self.resolution + 1):
if isnan(out_view[i, j, k]):
out_view[i, j, k] = out_view[i-1, j, k]
# Complete along y axis
for i in range(self.resolution + 1):
for j in range(1, self.resolution + 1):
for k in range(self.resolution + 1):
if isnan(out_view[i, j, k]):
out_view[i, j, k] = out_view[i, j-1, k]
# Complete along z axis
for i in range(self.resolution + 1):
for j in range(self.resolution + 1):
for k in range(1, self.resolution + 1):
if isnan(out_view[i, j, k]):
out_view[i, j, k] = out_view[i, j, k-1]
assert(not isnan(out_view[i, j, k]))
return out_array
def get_points(self):
points_np = np.zeros((self.grid_points.size(), 3), dtype=np.int64)
values_np = np.zeros((self.grid_points.size()), dtype=np.float64)
cdef long[:, :] points_view = points_np
cdef double[:] values_view = values_np
cdef Vector3D loc
cdef int i
for i in range(self.grid_points.size()):
loc = self.grid_points[i].loc
points_view[i, 0] = loc.x
points_view[i, 1] = loc.y
points_view[i, 2] = loc.z
values_view[i] = self.grid_points[i].value
return points_np, values_np
cdef void subdivide_voxels(self) except +:
cdef vector[bint] next_to_positive
cdef vector[bint] next_to_negative
cdef int i, j, k
cdef long idx
cdef Vector3D loc, adj_loc
# Initialize vectors
next_to_positive.resize(self.voxels.size(), False)
next_to_negative.resize(self.voxels.size(), False)
# Iterate over grid points and mark voxels active
# TODO: can move this to update operation and add attibute to voxel
for grid_point in self.grid_points:
loc = grid_point.loc
if not grid_point.known:
continue
# Iterate over the 8 adjacent voxels
for i in range(-1, 1):
for j in range(-1, 1):
for k in range(-1, 1):
adj_loc = Vector3D(
x=loc.x + i,
y=loc.y + j,
z=loc.z + k,
)
idx = self.get_voxel_idx(adj_loc)
if idx == -1:
continue
if grid_point.value >= self.threshold:
next_to_positive[idx] = True
if grid_point.value <= self.threshold:
next_to_negative[idx] = True
cdef int n_subdivide = 0
for idx in range(self.voxels.size()):
if not self.voxels[idx].is_leaf or self.voxels[idx].level == self.depth:
continue
if next_to_positive[idx] and next_to_negative[idx]:
n_subdivide += 1
self.voxels.reserve(self.voxels.size() + 8 * n_subdivide)
self.grid_points.reserve(self.voxels.size() + 19 * n_subdivide)
for idx in range(self.voxels.size()):
if not self.voxels[idx].is_leaf or self.voxels[idx].level == self.depth:
continue
if next_to_positive[idx] and next_to_negative[idx]:
self.subdivide_voxel(idx)
cdef void subdivide_voxel(self, long idx):
cdef Voxel voxel
cdef GridPoint point
cdef Vector3D loc0 = self.voxels[idx].loc
cdef Vector3D loc
cdef int new_level = self.voxels[idx].level + 1
cdef int new_size = 1 << (self.depth - new_level)
assert(new_level <= self.depth)
assert(1 <= new_size <= self.voxel_size_0)
# Current voxel is not leaf anymore
self.voxels[idx].is_leaf = False
# Add new voxels
cdef int i, j, k
for i in range(2):
for j in range(2):
for k in range(2):
loc = Vector3D(
x=loc0.x + i * new_size,
y=loc0.y + j * new_size,
z=loc0.z + k * new_size,
)
voxel = Voxel(
loc=loc,
level=new_level,
is_leaf=True
)
self.voxels[idx].children[i][j][k] = self.voxels.size()
self.voxels.push_back(voxel)
# Add new grid points
for i in range(3):
for j in range(3):
for k in range(3):
loc = Vector3D(
loc0.x + i * new_size,
loc0.y + j * new_size,
loc0.z + k * new_size,
)
# Only add new grid points
if self.get_grid_point_idx(loc) == -1:
self.add_grid_point(loc)
@cython.cdivision(True)
cdef long get_voxel_idx(self, Vector3D loc) except +:
"""Utility function for getting voxel index corresponding to 3D coordinates."""
# Shorthands
cdef long resolution = self.resolution
cdef long resolution_0 = self.resolution_0
cdef long depth = self.depth
cdef long voxel_size_0 = self.voxel_size_0
# Return -1 if point lies outside bounds
if not (0 <= loc.x < resolution and 0<= loc.y < resolution and 0 <= loc.z < resolution):
return -1
# Coordinates in coarse voxel grid
cdef Vector3D loc0 = Vector3D(
x=loc.x >> depth,
y=loc.y >> depth,
z=loc.z >> depth,
)
# Initial voxels
cdef int idx = vec_to_idx(loc0, resolution_0)
cdef Voxel voxel = self.voxels[idx]
assert(voxel.loc.x == loc0.x * voxel_size_0)
assert(voxel.loc.y == loc0.y * voxel_size_0)
assert(voxel.loc.z == loc0.z * voxel_size_0)
# Relative coordinates
cdef Vector3D loc_rel = Vector3D(
x=loc.x - (loc0.x << depth),
y=loc.y - (loc0.y << depth),
z=loc.z - (loc0.z << depth),
)
cdef Vector3D loc_offset
cdef long voxel_size = voxel_size_0
while not voxel.is_leaf:
voxel_size = voxel_size >> 1
assert(voxel_size >= 1)
# Determine child
loc_offset = Vector3D(
x=1 if (loc_rel.x >= voxel_size) else 0,
y=1 if (loc_rel.y >= voxel_size) else 0,
z=1 if (loc_rel.z >= voxel_size) else 0,
)
# New voxel
idx = voxel.children[loc_offset.x][loc_offset.y][loc_offset.z]
voxel = self.voxels[idx]
# New relative coordinates
loc_rel = Vector3D(
x=loc_rel.x - loc_offset.x * voxel_size,
y=loc_rel.y - loc_offset.y * voxel_size,
z=loc_rel.z - loc_offset.z * voxel_size,
)
assert(0<= loc_rel.x < voxel_size)
assert(0<= loc_rel.y < voxel_size)
assert(0<= loc_rel.z < voxel_size)
# Return idx
return idx
cdef inline void add_grid_point(self, Vector3D loc):
cdef GridPoint point = GridPoint(
loc=loc,
value=0.,
known=False,
)
self.grid_point_hash[vec_to_idx(loc, self.resolution + 1)] = self.grid_points.size()
self.grid_points.push_back(point)
cdef inline int get_grid_point_idx(self, Vector3D loc):
p_idx = self.grid_point_hash.find(vec_to_idx(loc, self.resolution + 1))
if p_idx == self.grid_point_hash.end():
return -1
cdef int idx = dref(p_idx).second
assert(self.grid_points[idx].loc.x == loc.x)
assert(self.grid_points[idx].loc.y == loc.y)
assert(self.grid_points[idx].loc.z == loc.z)
return idx