Spaces:
Runtime error
Runtime error
""" | |
Ray factory | |
classes that provide vertex and triangle information for rays on spheres | |
Example: | |
rays = Rays_Tetra(n_level = 4) | |
print(rays.vertices) | |
print(rays.faces) | |
""" | |
from __future__ import print_function, unicode_literals, absolute_import, division | |
import numpy as np | |
from scipy.spatial import ConvexHull | |
import copy | |
import warnings | |
class Rays_Base(object): | |
def __init__(self, **kwargs): | |
self.kwargs = kwargs | |
self._vertices, self._faces = self.setup_vertices_faces() | |
self._vertices = np.asarray(self._vertices, np.float32) | |
self._faces = np.asarray(self._faces, int) | |
self._faces = np.asanyarray(self._faces) | |
def setup_vertices_faces(self): | |
"""has to return | |
verts , faces | |
verts = ( (z_1,y_1,x_1), ... ) | |
faces ( (0,1,2), (2,3,4), ... ) | |
""" | |
raise NotImplementedError() | |
def vertices(self): | |
"""read-only property""" | |
return self._vertices.copy() | |
def faces(self): | |
"""read-only property""" | |
return self._faces.copy() | |
def __getitem__(self, i): | |
return self.vertices[i] | |
def __len__(self): | |
return len(self._vertices) | |
def __repr__(self): | |
def _conv(x): | |
if isinstance(x,(tuple, list, np.ndarray)): | |
return "_".join(_conv(_x) for _x in x) | |
if isinstance(x,float): | |
return "%.2f"%x | |
return str(x) | |
return "%s_%s" % (self.__class__.__name__, "_".join("%s_%s" % (k, _conv(v)) for k, v in sorted(self.kwargs.items()))) | |
def to_json(self): | |
return { | |
"name": self.__class__.__name__, | |
"kwargs": self.kwargs | |
} | |
def dist_loss_weights(self, anisotropy = (1,1,1)): | |
"""returns the anisotropy corrected weights for each ray""" | |
anisotropy = np.array(anisotropy) | |
assert anisotropy.shape == (3,) | |
return np.linalg.norm(self.vertices*anisotropy, axis = -1) | |
def volume(self, dist=None): | |
"""volume of the starconvex polyhedron spanned by dist (if None, uses dist=1) | |
dist can be a nD array, but the last dimension has to be of length n_rays | |
""" | |
if dist is None: dist = np.ones_like(self.vertices) | |
dist = np.asarray(dist) | |
if not dist.shape[-1]==len(self.vertices): | |
raise ValueError("last dimension of dist should have length len(rays.vertices)") | |
# all the shuffling below is to allow dist to be an arbitrary sized array (with last dim n_rays) | |
# self.vertices -> (n_rays,3) | |
# dist -> (m,n,..., n_rays) | |
# dist -> (m,n,..., n_rays, 3) | |
dist = np.repeat(np.expand_dims(dist,-1), 3, axis = -1) | |
# verts -> (m,n,..., n_rays, 3) | |
verts = np.broadcast_to(self.vertices, dist.shape) | |
# dist, verts -> (n_rays, m,n, ..., 3) | |
dist = np.moveaxis(dist,-2,0) | |
verts = np.moveaxis(verts,-2,0) | |
# vs -> (n_faces, 3, m, n, ..., 3) | |
vs = (dist*verts)[self.faces] | |
# vs -> (n_faces, m, n, ..., 3, 3) | |
vs = np.moveaxis(vs, 1,-2) | |
# vs -> (n_faces * m * n, 3, 3) | |
vs = vs.reshape((len(self.faces)*int(np.prod(dist.shape[1:-1])),3,3)) | |
d = np.linalg.det(list(vs)).reshape((len(self.faces),)+dist.shape[1:-1]) | |
return -1./6*np.sum(d, axis = 0) | |
def surface(self, dist=None): | |
"""surface area of the starconvex polyhedron spanned by dist (if None, uses dist=1)""" | |
dist = np.asarray(dist) | |
if not dist.shape[-1]==len(self.vertices): | |
raise ValueError("last dimension of dist should have length len(rays.vertices)") | |
# self.vertices -> (n_rays,3) | |
# dist -> (m,n,..., n_rays) | |
# all the shuffling below is to allow dist to be an arbitrary sized array (with last dim n_rays) | |
# dist -> (m,n,..., n_rays, 3) | |
dist = np.repeat(np.expand_dims(dist,-1), 3, axis = -1) | |
# verts -> (m,n,..., n_rays, 3) | |
verts = np.broadcast_to(self.vertices, dist.shape) | |
# dist, verts -> (n_rays, m,n, ..., 3) | |
dist = np.moveaxis(dist,-2,0) | |
verts = np.moveaxis(verts,-2,0) | |
# vs -> (n_faces, 3, m, n, ..., 3) | |
vs = (dist*verts)[self.faces] | |
# vs -> (n_faces, m, n, ..., 3, 3) | |
vs = np.moveaxis(vs, 1,-2) | |
# vs -> (n_faces * m * n, 3, 3) | |
vs = vs.reshape((len(self.faces)*int(np.prod(dist.shape[1:-1])),3,3)) | |
pa = vs[...,1,:]-vs[...,0,:] | |
pb = vs[...,2,:]-vs[...,0,:] | |
d = .5*np.linalg.norm(np.cross(list(pa), list(pb)), axis = -1) | |
d = d.reshape((len(self.faces),)+dist.shape[1:-1]) | |
return np.sum(d, axis = 0) | |
def copy(self, scale=(1,1,1)): | |
""" returns a copy whose vertices are scaled by given factor""" | |
scale = np.asarray(scale) | |
assert scale.shape == (3,) | |
res = copy.deepcopy(self) | |
res._vertices *= scale[np.newaxis] | |
return res | |
def rays_from_json(d): | |
return eval(d["name"])(**d["kwargs"]) | |
################################################################ | |
class Rays_Explicit(Rays_Base): | |
def __init__(self, vertices0, faces0): | |
self.vertices0, self.faces0 = vertices0, faces0 | |
super().__init__(vertices0=list(vertices0), faces0=list(faces0)) | |
def setup_vertices_faces(self): | |
return self.vertices0, self.faces0 | |
class Rays_Cartesian(Rays_Base): | |
def __init__(self, n_rays_x=11, n_rays_z=5): | |
super().__init__(n_rays_x=n_rays_x, n_rays_z=n_rays_z) | |
def setup_vertices_faces(self): | |
"""has to return list of ( (z_1,y_1,x_1), ... ) _""" | |
n_rays_x, n_rays_z = self.kwargs["n_rays_x"], self.kwargs["n_rays_z"] | |
dphi = np.float32(2. * np.pi / n_rays_x) | |
dtheta = np.float32(np.pi / n_rays_z) | |
verts = [] | |
for mz in range(n_rays_z): | |
for mx in range(n_rays_x): | |
phi = mx * dphi | |
theta = mz * dtheta | |
if mz == 0: | |
theta = 1e-12 | |
if mz == n_rays_z - 1: | |
theta = np.pi - 1e-12 | |
dx = np.cos(phi) * np.sin(theta) | |
dy = np.sin(phi) * np.sin(theta) | |
dz = np.cos(theta) | |
if mz == 0 or mz == n_rays_z - 1: | |
dx += 1e-12 | |
dy += 1e-12 | |
verts.append([dz, dy, dx]) | |
verts = np.array(verts) | |
def _ind(mz, mx): | |
return mz * n_rays_x + mx | |
faces = [] | |
for mz in range(n_rays_z - 1): | |
for mx in range(n_rays_x): | |
faces.append([_ind(mz, mx), _ind(mz + 1, (mx + 1) % n_rays_x), _ind(mz, (mx + 1) % n_rays_x)]) | |
faces.append([_ind(mz, mx), _ind(mz + 1, mx), _ind(mz + 1, (mx + 1) % n_rays_x)]) | |
faces = np.array(faces) | |
return verts, faces | |
class Rays_SubDivide(Rays_Base): | |
""" | |
Subdivision polyehdra | |
n_level = 1 -> base polyhedra | |
n_level = 2 -> 1x subdivision | |
n_level = 3 -> 2x subdivision | |
... | |
""" | |
def __init__(self, n_level=4): | |
super().__init__(n_level=n_level) | |
def base_polyhedron(self): | |
raise NotImplementedError() | |
def setup_vertices_faces(self): | |
n_level = self.kwargs["n_level"] | |
verts0, faces0 = self.base_polyhedron() | |
return self._recursive_split(verts0, faces0, n_level) | |
def _recursive_split(self, verts, faces, n_level): | |
if n_level <= 1: | |
return verts, faces | |
else: | |
verts, faces = Rays_SubDivide.split(verts, faces) | |
return self._recursive_split(verts, faces, n_level - 1) | |
def split(self, verts0, faces0): | |
"""split a level""" | |
split_edges = dict() | |
verts = list(verts0[:]) | |
faces = [] | |
def _add(a, b): | |
""" returns index of middle point and adds vertex if not already added""" | |
edge = tuple(sorted((a, b))) | |
if not edge in split_edges: | |
v = .5 * (verts[a] + verts[b]) | |
v *= 1. / np.linalg.norm(v) | |
verts.append(v) | |
split_edges[edge] = len(verts) - 1 | |
return split_edges[edge] | |
for v1, v2, v3 in faces0: | |
ind1 = _add(v1, v2) | |
ind2 = _add(v2, v3) | |
ind3 = _add(v3, v1) | |
faces.append([v1, ind1, ind3]) | |
faces.append([v2, ind2, ind1]) | |
faces.append([v3, ind3, ind2]) | |
faces.append([ind1, ind2, ind3]) | |
return verts, faces | |
class Rays_Tetra(Rays_SubDivide): | |
""" | |
Subdivision of a tetrahedron | |
n_level = 1 -> normal tetrahedron (4 vertices) | |
n_level = 2 -> 1x subdivision (10 vertices) | |
n_level = 3 -> 2x subdivision (34 vertices) | |
... | |
""" | |
def base_polyhedron(self): | |
verts = np.array([ | |
[np.sqrt(8. / 9), 0., -1. / 3], | |
[-np.sqrt(2. / 9), np.sqrt(2. / 3), -1. / 3], | |
[-np.sqrt(2. / 9), -np.sqrt(2. / 3), -1. / 3], | |
[0., 0., 1.] | |
]) | |
faces = [[0, 1, 2], | |
[0, 3, 1], | |
[0, 2, 3], | |
[1, 3, 2]] | |
return verts, faces | |
class Rays_Octo(Rays_SubDivide): | |
""" | |
Subdivision of a tetrahedron | |
n_level = 1 -> normal Octahedron (6 vertices) | |
n_level = 2 -> 1x subdivision (18 vertices) | |
n_level = 3 -> 2x subdivision (66 vertices) | |
""" | |
def base_polyhedron(self): | |
verts = np.array([ | |
[0, 0, 1], | |
[0, 1, 0], | |
[0, 0, -1], | |
[0, -1, 0], | |
[1, 0, 0], | |
[-1, 0, 0]]) | |
faces = [[0, 1, 4], | |
[0, 5, 1], | |
[1, 2, 4], | |
[1, 5, 2], | |
[2, 3, 4], | |
[2, 5, 3], | |
[3, 0, 4], | |
[3, 5, 0], | |
] | |
return verts, faces | |
def reorder_faces(verts, faces): | |
"""reorder faces such that their orientation points outward""" | |
def _single(face): | |
return face[::-1] if np.linalg.det(verts[face])>0 else face | |
return tuple(map(_single, faces)) | |
class Rays_GoldenSpiral(Rays_Base): | |
def __init__(self, n=70, anisotropy = None): | |
if n<4: | |
raise ValueError("At least 4 points have to be given!") | |
super().__init__(n=n, anisotropy = anisotropy if anisotropy is None else tuple(anisotropy)) | |
def setup_vertices_faces(self): | |
n = self.kwargs["n"] | |
anisotropy = self.kwargs["anisotropy"] | |
if anisotropy is None: | |
anisotropy = np.ones(3) | |
else: | |
anisotropy = np.array(anisotropy) | |
# the smaller golden angle = 2pi * 0.3819... | |
g = (3. - np.sqrt(5.)) * np.pi | |
phi = g * np.arange(n) | |
# z = np.linspace(-1, 1, n + 2)[1:-1] | |
# rho = np.sqrt(1. - z ** 2) | |
# verts = np.stack([rho*np.cos(phi), rho*np.sin(phi),z]).T | |
# | |
z = np.linspace(-1, 1, n) | |
rho = np.sqrt(1. - z ** 2) | |
verts = np.stack([z, rho * np.sin(phi), rho * np.cos(phi)]).T | |
# warnings.warn("ray definition has changed! Old results are invalid!") | |
# correct for anisotropy | |
verts = verts/anisotropy | |
#verts /= np.linalg.norm(verts, axis=-1, keepdims=True) | |
hull = ConvexHull(verts) | |
faces = reorder_faces(verts,hull.simplices) | |
verts /= np.linalg.norm(verts, axis=-1, keepdims=True) | |
return verts, faces | |