summaryrefslogtreecommitdiff
path: root/geometry.py
diff options
context:
space:
mode:
authorStan Seibert <stan@mtrr.org>2011-09-16 14:27:46 -0400
committerStan Seibert <stan@mtrr.org>2011-09-16 14:27:46 -0400
commit084dfd08b714faefaea77cb7dc04d2e93dc04b1d (patch)
tree5be8c1f1d30dc52d74c70c4964ec54f66294c265 /geometry.py
parentcfecff941fc619eb7269128afc62d9c11ae78aff (diff)
downloadchroma-084dfd08b714faefaea77cb7dc04d2e93dc04b1d.tar.gz
chroma-084dfd08b714faefaea77cb7dc04d2e93dc04b1d.tar.bz2
chroma-084dfd08b714faefaea77cb7dc04d2e93dc04b1d.zip
File reorganization to move toward standard python package layout
Diffstat (limited to 'geometry.py')
-rw-r--r--geometry.py403
1 files changed, 0 insertions, 403 deletions
diff --git a/geometry.py b/geometry.py
deleted file mode 100644
index f942222..0000000
--- a/geometry.py
+++ /dev/null
@@ -1,403 +0,0 @@
-import sys
-import os
-import numpy as np
-from chroma.itertoolset import *
-from chroma.tools import timeit, profile_if_possible
-from hashlib import md5
-import cPickle as pickle
-import gzip
-
-# all material/surface properties are interpolated at these
-# wavelengths when they are sent to the gpu
-standard_wavelengths = np.arange(200, 810, 20).astype(np.float32)
-
-class Mesh(object):
- "Triangle mesh object."
- def __init__(self, vertices, triangles, remove_duplicate_vertices=False):
- vertices = np.asarray(vertices, dtype=np.float32)
- triangles = np.asarray(triangles, dtype=np.int32)
-
- if len(vertices.shape) != 2 or vertices.shape[1] != 3:
- raise ValueError('shape mismatch')
-
- if len(triangles.shape) != 2 or triangles.shape[1] != 3:
- raise ValueError('shape mismatch')
-
- if (triangles < 0).any():
- raise ValueError('indices in `triangles` must be positive.')
-
- if (triangles >= len(vertices)).any():
- raise ValueError('indices in `triangles` must be less than the '
- 'length of the vertex array.')
-
- self.vertices = vertices
- self.triangles = triangles
-
- if remove_duplicate_vertices:
- self.remove_duplicate_vertices()
-
- def get_bounds(self):
- "Return the lower and upper bounds for the mesh as a tuple."
- return np.min(self.vertices, axis=0), np.max(self.vertices, axis=0)
-
- def remove_duplicate_vertices(self):
- "Remove any duplicate vertices in the mesh."
- # view the vertices as a structured array in order to identify unique
- # rows, i.e. unique vertices
- unique_vertices, inverse = np.unique(self.vertices.view([('', self.vertices.dtype)]*self.vertices.shape[1]), return_inverse=True)
- # turn the structured vertex array back into a normal array
- self.vertices = unique_vertices.view(self.vertices.dtype).reshape((unique_vertices.shape[0], self.vertices.shape[1]))
- # remap the triangles to the unique vertices
- self.triangles = np.vectorize(lambda i: inverse[i])(self.triangles)
-
- def assemble(self, key=slice(None), group=True):
- """
- Return an assembled triangle mesh; i.e. return the vertex positions
- of every triangle. If `group` is True, the array returned will have
- an extra axis denoting the triangle number; i.e. if the mesh contains
- N triangles, the returned array will have the shape (N,3,3). If `group`
- is False, return just the vertex positions without any grouping; in
- this case the grouping is implied (i.e. elements [0:3] are the first
- triangle, [3:6] the second, and so on.
-
- The `key` argument is a slice object if you just want to assemble
- a certain range of the triangles, i.e. to get the assembled mesh for
- triangles 3 through 6, key = slice(3,7).
- """
- if group:
- vertex_indices = self.triangles[key]
- else:
- vertex_indices = self.triangles[key].flatten()
-
- return self.vertices[vertex_indices]
-
- def __add__(self, other):
- return Mesh(np.concatenate((self.vertices, other.vertices)), np.concatenate((self.triangles, other.triangles + len(self.vertices))))
-
-class Solid(object):
- """Solid object attaches materials, surfaces, and colors to each triangle
- in a Mesh object."""
- def __init__(self, mesh, material1=None, material2=None, surface=None, color=0xffffff):
- self.mesh = mesh
-
- if np.iterable(material1):
- if len(material1) != len(mesh.triangles):
- raise ValueError('shape mismatch')
- self.material1 = np.array(material1, dtype=np.object)
- else:
- self.material1 = np.tile(material1, len(self.mesh.triangles))
-
- if np.iterable(material2):
- if len(material2) != len(mesh.triangles):
- raise ValueError('shape mismatch')
- self.material2 = np.array(material2, dtype=np.object)
- else:
- self.material2 = np.tile(material2, len(self.mesh.triangles))
-
- if np.iterable(surface):
- if len(surface) != len(mesh.triangles):
- raise ValueError('shape mismatch')
- self.surface = np.array(surface, dtype=np.object)
- else:
- self.surface = np.tile(surface, len(self.mesh.triangles))
-
- if np.iterable(color):
- if len(color) != len(mesh.triangles):
- raise ValueError('shape mismatch')
- self.color = np.array(color, dtype=np.uint32)
- else:
- self.color = np.tile(color, len(self.mesh.triangles)).astype(np.uint32)
-
- self.unique_materials = \
- np.unique(np.concatenate([self.material1, self.material2]))
-
- self.unique_surfaces = np.unique(self.surface)
-
- def __add__(self, other):
- return Solid(self.mesh + other.mesh, np.concatenate((self.material1, other.material1)), np.concatenate((self.material2, other.material2)), np.concatenate((self.surface, other.surface)), np.concatenate((self.color, other.color)))
-
-class Material(object):
- """Material optical properties."""
- def __init__(self, name='none'):
- self.name = name
-
- self.refractive_index = None
- self.absorption_length = None
- self.scattering_length = None
- self.density = 0.0 # g/cm^3
- self.composition = {} # by mass
-
- def set(self, name, value, wavelengths=standard_wavelengths):
- if np.iterable(value):
- if len(value) != len(wavelengths):
- raise ValueError('shape mismatch')
- else:
- value = np.tile(value, len(wavelengths))
-
- self.__dict__[name] = np.array(zip(wavelengths, value), dtype=np.float32)
-
-class Surface(object):
- """Surface optical properties."""
- def __init__(self, name='none'):
- self.name = name
-
- self.set('detect', 0)
- self.set('absorb', 0)
- self.set('reflect_diffuse', 0)
- self.set('reflect_specular', 0)
-
- def set(self, name, value, wavelengths=standard_wavelengths):
- if np.iterable(value):
- if len(value) != len(wavelengths):
- raise ValueError('shape mismatch')
- else:
- value = np.tile(value, len(wavelengths))
-
- if (np.asarray(value) < 0.0).any():
- raise Exception('all probabilities must be >= 0.0')
-
- self.__dict__[name] = np.array(zip(wavelengths, value), dtype=np.float32)
-
-def interleave(arr, bits):
- """
- Interleave the bits of quantized three-dimensional points in space.
-
- Example
- >>> interleave(np.identity(3, dtype=np.int))
- array([4, 2, 1], dtype=uint64)
- """
- if len(arr.shape) != 2 or arr.shape[1] != 3:
- raise Exception('shape mismatch')
-
- z = np.zeros(arr.shape[0], dtype=np.uint64)
- for i in range(bits):
- z |= (arr[:,2] & 1 << i) << (2*i) | \
- (arr[:,1] & 1 << i) << (2*i+1) | \
- (arr[:,0] & 1 << i) << (2*i+2)
- return z
-
-def morton_order(mesh, bits):
- """
- Return a list of zvalues for triangles in `mesh` by interleaving the
- bits of the quantized center coordinates of each triangle. Each coordinate
- axis is quantized into 2**bits bins.
- """
- lower_bound, upper_bound = mesh.get_bounds()
-
- if bits <= 0 or bits > 21:
- raise Exception('number of bits must be in the range (0,21].')
-
- max_value = 2**bits - 1
-
- def quantize(x):
- return np.uint64((x-lower_bound)*max_value/(upper_bound-lower_bound))
-
- mean_positions = quantize(np.mean(mesh.assemble(), axis=1))
-
- return interleave(mean_positions, bits)
-
-class Geometry(object):
- "Geometry object."
- def __init__(self, detector_material=None):
- self.detector_material = detector_material
- self.solids = []
- self.solid_rotations = []
- self.solid_displacements = []
-
- def add_solid(self, solid, rotation=None, displacement=None):
- """
- Add the solid `solid` to the geometry. When building the final triangle
- mesh, `solid` will be placed by rotating it with the rotation matrix
- `rotation` and displacing it by the vector `displacement`.
- """
-
- if rotation is None:
- rotation = np.identity(3)
- else:
- rotation = np.asarray(rotation, dtype=np.float32)
-
- if rotation.shape != (3,3):
- raise ValueError('rotation matrix has the wrong shape.')
-
- self.solid_rotations.append(rotation.astype(np.float32))
-
- if displacement is None:
- displacement = np.zeros(3)
- else:
- displacement = np.asarray(displacement, dtype=np.float32)
-
- if displacement.shape != (3,):
- raise ValueError('displacement vector has the wrong shape.')
-
- self.solid_displacements.append(displacement)
-
- self.solids.append(solid)
-
- return len(self.solids)-1
-
- def build(self, bits=11, shift=3, use_cache=True):
- """
- Build the bounding volume hierarchy, material/surface code arrays, and
- color array for this geometry. If the bounding volume hierarchy is
- cached, load the cache instead of rebuilding, else build and cache it.
-
- Args:
- - bits: int, *optional*
- The number of bits to quantize each linear dimension with when
- morton ordering the triangle centers for building the bounding
- volume hierarchy. Defaults to 8.
- - shift: int, *optional*
- The number of bits to shift the zvalue of each node when
- building the next layer of the bounding volume hierarchy.
- Defaults to 3.
- - use_cache: bool, *optional*
- If true, the on-disk cache in ~/.chroma/ will be checked for
- a previously built version of this geometry, otherwise the
- BVH will be computed and saved to the cache. If false,
- the cache is ignored and also not updated.
- """
- nv = np.cumsum([0] + [len(solid.mesh.vertices) for solid in self.solids])
- nt = np.cumsum([0] + [len(solid.mesh.triangles) for solid in self.solids])
-
- vertices = np.empty((nv[-1],3), dtype=np.float32)
- triangles = np.empty((nt[-1],3), dtype=np.uint32)
-
- for i, solid in enumerate(self.solids):
- vertices[nv[i]:nv[i+1]] = \
- np.inner(solid.mesh.vertices, self.solid_rotations[i]) + self.solid_displacements[i]
- triangles[nt[i]:nt[i+1]] = solid.mesh.triangles + nv[i]
-
- self.mesh = Mesh(vertices, triangles)
-
- self.colors = np.concatenate([solid.color for solid in self.solids])
-
- self.solid_id = np.concatenate([np.fromiter(repeat(i, len(solid.mesh.triangles)), dtype=np.uint32) for i, solid in enumerate(self.solids)])
-
- self.unique_materials = list(np.unique(np.concatenate([solid.unique_materials for solid in self.solids])))
-
- material_lookup = dict(zip(self.unique_materials, range(len(self.unique_materials))))
-
- self.material1_index = np.fromiter(imap(material_lookup.get, chain(*[solid.material1 for solid in self.solids])), dtype=np.int32)
-
- self.material2_index = np.fromiter(imap(material_lookup.get, chain(*[solid.material2 for solid in self.solids])), dtype=np.int32)
-
- self.unique_surfaces = list(np.unique(np.concatenate([solid.unique_surfaces for solid in self.solids])))
-
- surface_lookup = dict(zip(self.unique_surfaces, range(len(self.unique_surfaces))))
-
- self.surface_index = np.fromiter(imap(surface_lookup.get, chain(*[solid.surface for solid in self.solids])), dtype=np.int32)
-
- try:
- self.surface_index[self.surface_index == surface_lookup[None]] = -1
- except KeyError:
- pass
-
- checksum = md5(str(bits))
- checksum.update(str(shift))
- checksum.update(self.mesh.vertices)
- checksum.update(self.mesh.triangles)
-
- cache_dir = os.path.expanduser('~/.chroma')
- cache_file = checksum.hexdigest()
- cache_path = os.path.join(cache_dir, cache_file)
-
- if use_cache:
- try:
- f = gzip.GzipFile(cache_path, 'rb')
- except IOError:
- pass
- else:
- #print 'loading cache.'
- data = pickle.load(f)
-
- reorder = data.pop('reorder')
- self.mesh.triangles = self.mesh.triangles[reorder]
- self.material1_index = self.material1_index[reorder]
- self.material2_index = self.material2_index[reorder]
- self.surface_index = self.surface_index[reorder]
- self.colors = self.colors[reorder]
- self.solid_id = self.solid_id[reorder]
-
- for key, value in data.iteritems():
- setattr(self, key, value)
- f.close()
- return
-
- zvalues_mesh = morton_order(self.mesh, bits)
- reorder = np.argsort(zvalues_mesh)
- zvalues_mesh = zvalues_mesh[reorder]
-
- if (np.diff(zvalues_mesh) < 0).any():
- raise Exception('zvalues_mesh out of order.')
-
- self.mesh.triangles = self.mesh.triangles[reorder]
-
- self.material1_index = self.material1_index[reorder]
- self.material2_index = self.material2_index[reorder]
- self.surface_index = self.surface_index[reorder]
- self.colors = self.colors[reorder]
- self.solid_id = self.solid_id[reorder]
-
- unique_zvalues = np.unique(zvalues_mesh)
-
- while unique_zvalues.size > zvalues_mesh.size/np.e:
- zvalues_mesh = zvalues_mesh >> shift
- unique_zvalues = np.unique(zvalues_mesh)
-
- self.lower_bounds = np.empty((unique_zvalues.size,3), dtype=np.float32)
- self.upper_bounds = np.empty((unique_zvalues.size,3), dtype=np.float32)
-
- assembled_mesh = self.mesh.assemble(group=False)
- self.node_map = np.searchsorted(zvalues_mesh, unique_zvalues)
- self.node_map_end = np.searchsorted(zvalues_mesh, unique_zvalues, side='right')
-
- for i, (zi1, zi2) in enumerate(izip(self.node_map, self.node_map_end)):
- self.lower_bounds[i] = assembled_mesh[zi1*3:zi2*3].min(axis=0)
- self.upper_bounds[i] = assembled_mesh[zi1*3:zi2*3].max(axis=0)
-
- self.layers = np.zeros(unique_zvalues.size, dtype=np.uint32)
- self.first_node = unique_zvalues.size
-
- begin_last_layer = 0
-
- for layer in count(1):
- bit_shifted_zvalues = unique_zvalues >> shift
- unique_zvalues = np.unique(bit_shifted_zvalues)
-
- i0 = begin_last_layer + bit_shifted_zvalues.size
-
- self.node_map.resize(self.node_map.size+unique_zvalues.size)
- self.node_map[i0:] = np.searchsorted(bit_shifted_zvalues, unique_zvalues) + begin_last_layer
- self.node_map_end.resize(self.node_map_end.size+unique_zvalues.size)
- self.node_map_end[i0:] = np.searchsorted(bit_shifted_zvalues, unique_zvalues, side='right') + begin_last_layer
-
- self.layers.resize(self.layers.size+unique_zvalues.size)
- self.layers[i0:] = layer
-
- self.lower_bounds.resize((self.lower_bounds.shape[0]+unique_zvalues.size,3))
- self.upper_bounds.resize((self.upper_bounds.shape[0]+unique_zvalues.size,3))
-
- for i, zi1, zi2 in izip(count(i0), self.node_map[i0:], self.node_map_end[i0:]):
- self.lower_bounds[i] = self.lower_bounds[zi1:zi2].min(axis=0)
- self.upper_bounds[i] = self.upper_bounds[zi1:zi2].max(axis=0)
-
- begin_last_layer += bit_shifted_zvalues.size
-
- if unique_zvalues.size == 1:
- break
-
- self.start_node = self.node_map.size - 1
-
- if use_cache:
- #print 'Writing BVH to cache directory...'
- sys.stdout.flush()
-
- if not os.path.exists(cache_dir):
- os.makedirs(cache_dir)
-
- with gzip.GzipFile(cache_path, 'wb', compresslevel=1) as f:
- data = {}
- for key in ['lower_bounds', 'upper_bounds', 'node_map', 'node_map_end', 'layers', 'first_node', 'start_node']:
- data[key] = getattr(self, key)
- data['reorder'] = reorder
- pickle.dump(data, f, -1)