diff options
| -rw-r--r-- | __init__.py | 3 | ||||
| -rw-r--r-- | camera.py | 27 | ||||
| -rw-r--r-- | detectors/lbne.py | 136 | ||||
| -rw-r--r-- | geometry.py | 162 | ||||
| -rw-r--r-- | gpu.py | 2 | ||||
| -rw-r--r-- | materials.py | 8 | ||||
| -rw-r--r-- | photon.py | 3 | ||||
| -rw-r--r-- | src/intersect.cu (renamed from src/kernel.cu) | 44 | ||||
| -rw-r--r-- | stl.py | 1 | ||||
| -rw-r--r-- | transform.py | 6 | ||||
| -rw-r--r-- | vector.py | 13 | ||||
| -rwxr-xr-x[-rw-r--r--] | view.py | 143 | 
12 files changed, 390 insertions, 158 deletions
diff --git a/__init__.py b/__init__.py index 2d61de9..409c3ea 100644 --- a/__init__.py +++ b/__init__.py @@ -2,3 +2,6 @@ import geometry  import materials  import transform  import stl +import view +import photon +import gpu @@ -1,22 +1,33 @@  import numpy as np +from itertools import product  class Camera(object): -    def __init__(self, size = (800, 600), film_size = (0.035, 0.024), focal_length=0.05): -        width, height = size +    """ +    Pinhole camera object. -        grid = [] -        for i, x in enumerate(np.linspace(-film_size[0]/2, film_size[0]/2, width)): -            for j, z in enumerate(np.linspace(-film_size[1]/2, film_size[1]/2, height)): -                grid.append((x,0,z)) +    Args: +        - size: tuple, *optional* +            Pixel array shape. +        - film_size: tuple, *optional* +            Physical size of photographic film. Defaults to 35mm film size. +        - focal_length: float, *optional* +            Focal length of camera. +    """ +    def __init__(self, size = (800, 600), film_size = (0.035, 0.024), \ +                     focal_length=0.05): +        x = np.linspace(-film_size[0]/2, film_size[0]/2, size[0]) +        z = np.linspace(-film_size[1]/2, film_size[1]/2, size[1]) -        self.grid = np.array(grid) -        self.grid += (0,focal_length,0) +        self.grid = np.array(tuple(product(x,[0],z))) +        self.grid += (0,focal_length,0)          self.focal_point = np.zeros(3)      def position(self, position): +        """Translate the camera to `position`."""          self.grid += position          self.focal_point += position      def get_rays(self): +        """Return the position and direction for each pixel ray."""          return self.grid, self.focal_point-self.grid diff --git a/detectors/lbne.py b/detectors/lbne.py index 4473084..cac737a 100644 --- a/detectors/lbne.py +++ b/detectors/lbne.py @@ -2,43 +2,129 @@ import os  import numpy as np  import pickle  from chroma import * +from copy import deepcopy +from histogram import *  models_directory = os.path.split(os.path.realpath(__file__))[0] + '/../models' -strings = 10 +strings = 20  pmts_per_string = 10  radius = 10.0  height = 20.0 -def build_lbne(bits=4): -    lbne = geometry.Geometry() +grid_spacing = height/pmts_per_string -    sphere_mesh = stl.read_stl(models_directory + '/hamamatsu_12inch.stl') -    sphere_mesh /= 1000.0 +block_size = 64 -    solids = [] -    for i in range(pmts_per_string): -        for j in range(strings): -            sphere = np.copy(sphere_mesh) -            sphere += (-radius,0,i*(height/pmts_per_string)) -            sphere = transform.rotate(sphere, j*2*np.pi/strings, (0,0,1)) -            lbne.add_solid(geometry.Solid(sphere, materials.vacuum, materials.h2o)) -    lbne.build(bits) +class LBNE(geometry.Geometry): +    def __init__(self): +        super(LBNE, self).__init__() -    return lbne +        pmt_mesh = stl.read_stl(models_directory + '/hamamatsu_12inch.stl') +        pmt_mesh /= 1000.0 -def cache_lbne(filename): -    lbne = build_lbne(bits=8) -    f = open(filename, 'wb') -    pickle.dump(lbne, f) -    f.close() +        apmt = geometry.Solid(pmt_mesh, materials.glass, materials.h2o) -def load_lbne(filename): -    f =  open(filename, 'rb') -    lbne = pickle.load(f) -    f.close() -    return lbne +        self.pmt_index = [] +        self.pmt_local_axes = [] +        self.pmt_positions = [] + +        self.pmt_hits = [] + +        for i in range(pmts_per_string): +            for j in range(strings): +                pmt = deepcopy(apmt) +                pmt.mesh += (-radius,0,i*(height/pmts_per_string)) +                pmt.mesh = transform.rotate(pmt.mesh, j*2*np.pi/strings, (0,0,1)) +                self.add_solid(pmt) +                self.pmt_hits.append(Histogram(10000, (-0.5, 9999.5))) + +        for x in np.arange(-radius, radius, grid_spacing): +            for y in np.arange(-radius, radius, grid_spacing): +                if np.sqrt(x**2+y**2) <= radius: +                    pmt = deepcopy(apmt) +                    pmt.mesh = transform.rotate(pmt.mesh, np.pi/2, (0,1,0)) +                    pmt.mesh += (x,y,0) +                    self.add_solid(pmt) +                    self.pmt_hits.append(Histogram(10000, (-0.5, 9999.5))) + +        for x in np.arange(-radius, radius, grid_spacing): +            for y in np.arange(-radius, radius, grid_spacing): +                if np.sqrt(x**2+y**2) <= radius: +                    pmt = deepcopy(apmt) +                    pmt.mesh = transform.rotate(pmt.mesh, -np.pi/2, (0,1,0)) +                    pmt.mesh += (x,y,height) +                    self.add_solid(pmt) +                    self.pmt_hits.append(Histogram(10000, (-0.5, 9999.5))) + +        self.build(bits=4) + +        self.npmts = len(self.pmt_hits) + +        self.gpu = gpu.GPU() +        self.gpu.load_geometry(self) + +    def throw_photon_bomb(self, z_position, nphotons=100000): +        origin = np.zeros((nphotons,3)) + (0,0,z_position) + +        direction = photon.uniform_sphere(nphotons) + +        origin_gpu = gpu.cuda.to_device(gpu.make_vector(origin)) +        direction_gpu = gpu.cuda.to_device(gpu.make_vector(direction)) + +        pixels = np.empty(nphotons, dtype=np.int32) +        states = np.empty(nphotons, dtype=np.int32) + +        pixels_gpu = gpu.cuda.to_device(pixels) +        states_gpu = gpu.cuda.to_device(states) + +        gpu_kwargs = {'block': (block_size,1,1), 'grid': (nphotons//block_size+1,1)} +        self.gpu.call(np.int32(nphotons), origin_gpu, direction_gpu, np.int32(self.first_leaf), states_gpu, pixels_gpu, **gpu_kwargs) + +        gpu.cuda.memcpy_dtoh(states, states_gpu) + +        pmt_indices = self.solid_index[states[(states != -1)]] + +        bin_count = np.bincount(pmt_indices) + +        bin_count = np.append(bin_count, np.zeros(self.npmts-bin_count.size)) + +        return bin_count + +    def generate_event(self, z_position): +        self.bin_count = self.throw_photon_bomb(z_position) + +    def get_likelihood(self, z_position, calls=1000): +        if not hasattr(self, 'bin_count'): +            raise Exception('must call generate_event() first') + +        for pmt_hit in self.pmt_hits: +            pmt_hit.reset() + +        for i in range(calls): +            print 'throwing bomb %i' % i +            bin_count = self.throw_photon_bomb(z_position) + +            for i, count in enumerate(bin_count): +                self.pmt_hits[i].fill(count) + +        for pmt_hit in self.pmt_hits: +            pmt_hit.normalize() + +        likelihood = 0.0 +        for i in range(self.npmts): +            probability = self.pmt_hits[i].eval(self.bin_count[i]) + +            if probability == 0.0: +                print 'calculating likelihood from pmt %i' % i +                print 'bin count =', self.bin_count[i] +                print self.pmt_hits[i].hist + +            likelihood -= np.log(self.pmt_hits[i].eval(self.bin_count[i])) + +        return likelihood  if __name__ == '__main__': -    build_lbne() +    lbne = LBNE() +    view.view(lbne) diff --git a/geometry.py b/geometry.py index cf92d53..2c136a3 100644 --- a/geometry.py +++ b/geometry.py @@ -2,9 +2,33 @@ import numpy as np  from itertools import chain  def compress(data, selectors): +    """ +    Make an iterator that filters elements from `data` returning only those +    that have a corresponding element in `selectors` that evaluates to True. +    Stops when either the `data` or `selectors` iterables has been +    exhausted. + +    From Python 2.7 itertools. +    """      return (d for d, s in zip(data, selectors) if s)  class Leaf(object): +    """ +    Leaf object represents the last layer in the bounding volume hierarchy +    which points to a subset of the triangle mesh. + +    Args: +        - mesh: array, +            A subset of the triangle mesh. +        - mesh_idx: int, +            The index of the first triangle in the global mesh. +        - zvlaue: int, *optional* +            The zvalue associated with this leaf. + +    .. note:: +        The `mesh` passed in the constructor is not actually stored in the +        leaf. It is simply used to construct the leaf's bounding box. +    """      def __init__(self, mesh, mesh_idx, zvalue=None):          self.zvalue = zvalue          self.mesh_idx = mesh_idx @@ -15,6 +39,16 @@ class Leaf(object):          self.size = mesh.shape[0]  class Node(object): +    """ +    Node object represents a node in the bounding volume hierarchy which +    contains a list of child nodes. + +    Args: +        - children: list, +            List of child nodes/leafs. +        - zvalue: int, *optional* +            The zvalue associated with this node. +    """      def __init__(self, children, zvalue=None):          self.zvalue = zvalue @@ -29,15 +63,29 @@ class Node(object):          self.children = children  def interleave(arr): +    """ +    Interleave the bits of quantized three-dimensional points in space. + +    Example +        >>> interleave(np.identity(3, dtpe=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(arr.dtype.itemsize*8): -        z |= (arr[:,2] & 1 << i) << (2*i) | (arr[:,1] & 1 << i) << (2*i+1) | (arr[:,0] & 1 << i) << (2*i+2) +        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=3): +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 = np.array([np.min(mesh[:,:,0]), np.min(mesh[:,:,1]), np.min(mesh[:,:,2])])      upper_bound = np.array([np.max(mesh[:,:,0]), np.max(mesh[:,:,1]), np.max(mesh[:,:,2])]) @@ -51,63 +99,115 @@ def morton_order(mesh, bits=3):      return interleave(mean_positions)  class Solid(object): -    def __init__(self, mesh, inside, outside): +    """ +    Object which stores a closed triangle mesh associated with a physically +    distinct object. + +    Args: +        - mesh, array +            A closed triangle mesh. +        - material1, Material +            The material inside within the mesh. +        - material2, Material +            The material outside the mesh. +        - surface1, Surface, +            The surface on the inside of the mesh. +        - surface2, Surface, +            The surface on the outside of the mesh. + +    .. warning:: +        It is possible to define logically inconsistent geometries unless +        you are careful. For example, solid A may define its inside material +        as water but contain solid B which defines its outside material as air. +        In this case, a photon traveling out of solid B will reflect/refract +        assuming it's going into air, but upon reaching solid A's boundary will +        calculate attenuation factors assuming it just traveled through water. +    """ +    def __init__(self, mesh, material1, material2, \ +                     surface1=None, surface2=None):          if len(mesh.shape) != 3 or mesh.shape[1] != 3 or mesh.shape[2] != 3: -            raise Exception('shape mismatch') +            raise Exception('shape mismatch; mesh must be a triangle mesh')          self.mesh = mesh -        self.inside = inside -        self.outside = outside +        self.material1 = material1 +        self.material2 = material2 +        self.surface1 = surface1 +        self.surface2 = surface2      def __len__(self):          return self.mesh.shape[0]  class Geometry(object): -    """ -    Geometry object. -    """ +    """Object which stores the global mesh for a geometry."""      def __init__(self):          self.solids = []          self.materials = [] +        self.surfaces = []      def add_solid(self, solid): +        """Add a solid to the geometry."""          self.solids.append(solid) -        if solid.inside not in self.materials: -            self.materials.append(solid.inside) +        if solid.material1 not in self.materials: +            self.materials.append(solid.material1) -        if solid.outside not in self.materials: -            self.materials.append(solid.outside) +        if solid.material2 not in self.materials: +            self.materials.append(solid.material2) -    def build(self, bits=3): -        self.mesh = np.concatenate([solid.mesh for solid in self.solids]) -        self.solid_index = np.concatenate([np.tile(self.solids.index(solid), len(solid)) for solid in self.solids]) -        self.inside_index = np.concatenate([np.tile(self.materials.index(solid.outside), len(solid)) for solid in self.solids]) -        self.outside_index = np.concatenate([np.tile(self.materials.index(solid.outside), len(solid)) for solid in self.solids]) +        if solid.surface1 not in self.surfaces: +            self.surfaces.append(solid.surface1) -        zvalues = morton_order(self.mesh, bits) +        if solid.surface2 not in self.surfaces: +            self.surfaces.append(solid.surface1) +    def build(self, bits=3): +        """Build the bounding volume hierarchy of the geometry.""" +        mesh = np.concatenate([solid.mesh for solid in self.solids]) + +        # lookup solid/material/surface index from triangle index +        solid_index = \ +            np.concatenate([np.tile(self.solids.index(solid), \ +                                        len(solid)) for solid in self.solids]) +        material1_index = \ +            np.concatenate([np.tile(self.materials.index(solid.material1), \ +                                        len(solid)) for solid in self.solids]) +        material2_index = \ +            np.concatenate([np.tile(self.materials.index(solid.material2), \ +                                        len(solid)) for solid in self.solids]) +        surface1_index = \ +            np.concatenate([np.tile(self.surfaces.index(solid.surface1), \ +                                        len(solid)) for solid in self.solids]) +        surface2_index = \ +            np.concatenate([np.tile(self.surfaces.index(solid.surface2), \ +                                        len(solid)) for solid in self.solids]) + +        # array of zvalue for each triangle in mesh +        zvalues = morton_order(mesh, bits) + +        # mesh indices in order of increasing zvalue          order = np.array(zip(*sorted(zip(zvalues, range(zvalues.size))))[-1]) -        zvalues = zvalues[order] -        self.mesh = self.mesh[order] -        self.solid_index = self.solid_index[order] -        self.inside_index = self.inside_index[order] -        self.outside_index = self.outside_index[order] +        # reorder all arrays ordered by triangle index +        self.zvalues = zvalues[order] +        self.mesh = mesh[order] +        self.solid_index = solid_index[order] +        self.material1_index = material1_index[order] +        self.material2_index = material2_index[order] +        self.surface1_index = surface1_index[order] +        self.surface2_index = surface2_index[order]          leafs = [] -        for z in sorted(set(zvalues)): -            mask = (zvalues == z) +        for z in sorted(set(self.zvalues)): +            mask = (self.zvalues == z)              leafs.append(Leaf(self.mesh[mask], np.where(mask)[0][0], z))          layers = []          layers.append(leafs)          while True: -            zvalues = np.array([node.zvalue for node in layers[-1]]) - -            bit_shifted_zvalues = zvalues >> 1 +            bit_shifted_zvalues = \ +                np.array([node.zvalue for node in layers[-1]]) >> 1              nodes = []              for z in sorted(set(bit_shifted_zvalues)): @@ -131,7 +231,7 @@ class Geometry(object):          self.upper_bound = np.empty((len(nodes),3))          self.child_map = np.empty(len(nodes))          self.child_len = np.empty(len(nodes)) -        self.first_leaf = -1 +        self.first_leaf = None          for i, node in enumerate(nodes):              self.lower_bound[i] = node.lower_bound @@ -148,5 +248,5 @@ class Geometry(object):              if isinstance(node, Leaf):                  self.child_map[i] = node.mesh_idx -                if self.first_leaf == -1: +                if self.first_leaf is None:                      self.first_leaf = i @@ -22,7 +22,7 @@ print 'device %s' % autoinit.device.name()  source_directory = os.path.split(os.path.realpath(__file__))[0] + '/src' -source = open(source_directory + '/kernel.cu').read() +source = open(source_directory + '/intersect.cu').read()  module = SourceModule(source, options=['-I' + source_directory], no_extern_c=True, cache_dir=False)  cuda_intersect = module.get_function('intersect_mesh') diff --git a/materials.py b/materials.py index 4245212..4a8058f 100644 --- a/materials.py +++ b/materials.py @@ -1,4 +1,5 @@  class Material(object): +    """Material optical properties."""      def __init__(self, name='none'):          self.name = name @@ -7,6 +8,13 @@ class Material(object):          self.absorption_length = None          self.scattering_length = None +class Surface(object): +    """Surface optical properties.""" +    def __init__(self, name='none'): +        self.name = name + +        self.wavelength = None +  air = Material('air')  h2o = Material('h2o')  glass = Material('glass') @@ -1,4 +1,3 @@ -import time  import numpy as np  def uniform_sphere(size=None): @@ -13,8 +12,6 @@ def uniform_sphere(size=None):      source: Weisstein, Eric W. "Sphere Point Picking." Mathworld.       """ -    t0 = time.time() -      theta, u = np.random.uniform(0.0, 2*np.pi, size), \          np.random.uniform(-1.0, 1.0, size) diff --git a/src/kernel.cu b/src/intersect.cu index c2b3fb2..1402aa1 100644 --- a/src/kernel.cu +++ b/src/intersect.cu @@ -5,11 +5,14 @@  #include "matrix.h"  #include "rotate.h" +/* flattened triangle mesh */  texture<float4, 1, cudaReadModeElementType> mesh; +/* lower/upper bounds for the bounding box associated with each node/leaf */  texture<float4, 1, cudaReadModeElementType> upper_bound_arr;  texture<float4, 1, cudaReadModeElementType> lower_bound_arr; +/* map to child nodes/triangles and the number of child nodes/triangles */  texture<uint, 1, cudaReadModeElementType> child_map_arr;  texture<uint, 1, cudaReadModeElementType> child_len_arr; @@ -18,6 +21,12 @@ __device__ float3 make_float3(const float4 &a)  	return make_float3(a.x, a.y, a.z);  } +/* Test the intersection between a ray starting from `origin` traveling in the +   direction `direction` and a triangle defined by the vertices `v0`, `v1`, and +   `v2`. If the ray intersects the triangle, set `distance` to the distance +   between `origin` and the intersection and return true, else return false. + +   `direction` must be normalized. */  __device__ bool intersect_triangle(const float3 &origin, const float3 &direction, const float3 &v0, const float3 &v1, const float3 &v2, float &distance)  {  	Matrix m = make_matrix(v1-v0, v2-v0, -direction); @@ -29,26 +38,38 @@ __device__ bool intersect_triangle(const float3 &origin, const float3 &direction  	float3 b = origin-v0; -	float u1 = ((m.a11*m.a22 - m.a12*m.a21)*b.x + (m.a02*m.a21 - m.a01*m.a22)*b.y + (m.a01*m.a12 - m.a02*m.a11)*b.z)/determinant; +	float u1 = ((m.a11*m.a22 - m.a12*m.a21)*b.x + +		    (m.a02*m.a21 - m.a01*m.a22)*b.y + +		    (m.a01*m.a12 - m.a02*m.a11)*b.z)/determinant;  	if (u1 < 0.0f)  		return false; -	float u2 = ((m.a12*m.a20 - m.a10*m.a22)*b.x + (m.a00*m.a22 - m.a02*m.a20)*b.y + (m.a02*m.a10 - m.a00*m.a12)*b.z)/determinant; +	float u2 = ((m.a12*m.a20 - m.a10*m.a22)*b.x + +		    (m.a00*m.a22 - m.a02*m.a20)*b.y + +		    (m.a02*m.a10 - m.a00*m.a12)*b.z)/determinant;  	if (u2 < 0.0f)  		return false; -	float u3 = ((m.a10*m.a21 - m.a11*m.a20)*b.x + (m.a01*m.a20 - m.a00*m.a21)*b.y + (m.a00*m.a11 - m.a01*m.a10)*b.z)/determinant; +	float u3 = ((m.a10*m.a21 - m.a11*m.a20)*b.x + +		    (m.a01*m.a20 - m.a00*m.a21)*b.y + +		    (m.a00*m.a11 - m.a01*m.a10)*b.z)/determinant;  	if (u3 < 0.0f || (1-u1-u2) < 0.0f)  		return false; -	distance = norm(direction*u3); +	distance = u3;  	return true;  } +/* Return the 32 bit color associated with the intersection between a ray +   starting from `origin` traveling in the direction `direction` and the +   plane defined by the points `v0`, `v1`, and `v2` using the cosine of the +   angle between the ray and the plane normal to determine the brightness. + +   `direction` must be normalized. */  __device__ int get_color(const float3 &direction, const float3 &v0, const float3& v1, const float3 &v2)  {  	float scale = 255*dot(normalize(cross(v1-v0,v2-v0)),-direction); @@ -61,7 +82,10 @@ __device__ int get_color(const float3 &direction, const float3 &v0, const float3  	return rgb << 16 | rgb << 8 | rgb;  } - +/* Test the intersection between a ray starting from `origin` traveling in the +   direction `direction` and the axis-aligned box defined by the opposite +   vertices `lower_bound` and `upper_bound`. If the ray intersects the box +   return True, else return False. */  __device__ bool intersect_box(const float3 &origin, const float3 &direction, const float3 &lower_bound, const float3 &upper_bound)  {  	float kmin, kmax, kymin, kymax, kzmin, kzmax; @@ -132,6 +156,9 @@ __device__ bool intersect_box(const float3 &origin, const float3 &direction, con  	return true;  } +/* Test the intersection between a ray starting at `origin` traveling in the +   direction `direction` and the bounding box around node `i`. If the ray +   intersects the bounding box return true, else return false. */  __device__ bool intersect_node(const float3 &origin, const float3 &direction, const int &i)  {  	float3 lower_bound = make_float3(tex1Dfetch(lower_bound_arr, i)); @@ -236,7 +263,7 @@ __global__ void intersect_mesh(int max_idx, float3 *origin_arr, float3 *directio  					if (!hit)  					{  						*pixel = get_color(direction, v0, v1, v2); -						*state = mesh_idx; +						*state = child_map + i;  						min_distance = distance; @@ -247,7 +274,7 @@ __global__ void intersect_mesh(int max_idx, float3 *origin_arr, float3 *directio  					if (distance < min_distance)  					{  						*pixel = get_color(direction, v0, v1, v2); -						*state = mesh_idx; +						*state = child_map + i;  						min_distance = distance;  					} @@ -263,7 +290,10 @@ __global__ void intersect_mesh(int max_idx, float3 *origin_arr, float3 *directio  	while (node != head);  	if (!hit) +	{ +		*state = -1;  		*pixel = 0; +	}  } // intersect mesh @@ -3,6 +3,7 @@ import string  import struct  def read_stl(filename): +    """Return a triangle mesh from `filename`."""      f = open(filename)      buf = f.read(200)      f.close() diff --git a/transform.py b/transform.py index 4e5eb9c..cab23fb 100644 --- a/transform.py +++ b/transform.py @@ -1,6 +1,12 @@  import numpy as np  def rotate(x, phi, n): +    """ +    Rotate an array of points `x` through an angle phi counter-clockwise +    around the axis `n` (when looking towards +infinity). + +    Source: Weisstein, Eric W. "Rotation Formula." Mathworld. +    """      x = np.asarray(x)      n = np.asarray(n) diff --git a/vector.py b/vector.py deleted file mode 100644 index 7b36e07..0000000 --- a/vector.py +++ /dev/null @@ -1,13 +0,0 @@ -import numpy as np -from pycuda import gpuarray - -def make_vector(arr, dtype=gpuarray.vec.float3): -    if len(arr.shape) != 2 or arr.shape[-1] != 3: -        raise Exception('shape mismatch') - -    x = np.empty(arr.shape[0], dtype) -    x['x'] = arr[:,0] -    x['y'] = arr[:,1] -    x['z'] = arr[:,2] - -    return x @@ -1,108 +1,111 @@ +#!/usr/bin/env python  import numpy as np  from stl import *  from geometry import *  from materials import *  from camera import *  from gpu import * - -import sys -import optparse +from transform import *  import pygame  from pygame.locals import * -parser = optparse.OptionParser('%prog filename.stl') -parser.add_option('-b', '--bits', type='int', dest='bits', help='number of bits for z ordering space axes', default=4) -options, args = parser.parse_args() +def view(geometry, name=''): +    mesh = geometry.mesh +    lower_bound = np.array([np.min(mesh[:,:,0]), np.min(mesh[:,:,1]), np.min(mesh[:,:,2])]) +    upper_bound = np.array([np.max(mesh[:,:,0]), np.max(mesh[:,:,1]), np.max(mesh[:,:,2])]) -if len(args) < 1: -    sys.exit(parser.format_help()) +    scale = np.linalg.norm(upper_bound-lower_bound)/10.0 -geometry = Geometry() -geometry.add_solid(Solid(read_stl(args[0]), vacuum, vacuum)) -geometry.build(options.bits) +    gpu = GPU() +    gpu.load_geometry(geometry) -mesh = geometry.mesh -lower_bound = np.array([np.min(mesh[:,:,0]), np.min(mesh[:,:,1]), np.min(mesh[:,:,2])]) -upper_bound = np.array([np.max(mesh[:,:,0]), np.max(mesh[:,:,1]), np.max(mesh[:,:,2])]) +    pygame.init() +    size = width, height = 800, 600 +    screen = pygame.display.set_mode(size) +    pygame.display.set_caption(name) -scale = np.linalg.norm(upper_bound-lower_bound)/10.0 +    camera = Camera(size) +    camera.position((0, upper_bound[1]*5, np.mean([lower_bound[2], upper_bound[2]]))) -gpu = GPU() -gpu.load_geometry(geometry) +    origin, direction = camera.get_rays() -pygame.init() -size = width, height = 800, 600 -screen = pygame.display.set_mode(size) -pygame.display.set_caption(os.path.split(args[0])[-1]) +    pixels = np.empty(width*height, dtype=np.int32) +    states = np.empty(width*height, dtype=np.int32) -camera = Camera(size) -camera.position((0, upper_bound[1]*5, np.mean([lower_bound[2], upper_bound[2]]))) +    origin_gpu = cuda.to_device(make_vector(origin)) +    direction_gpu = cuda.to_device(make_vector(direction)) -origin, direction = camera.get_rays() +    pixels_gpu = cuda.to_device(pixels) +    states_gpu = cuda.to_device(states) -pixels = np.empty(width*height, dtype=np.int32) -states = np.empty(width*height, dtype=np.int32) +    block_size = 64 +    gpu_kwargs = {'block': (block_size,1,1), 'grid': (pixels.size//block_size+1,1)} -origin_gpu = cuda.to_device(make_vector(origin)) -direction_gpu = cuda.to_device(make_vector(direction)) +    def render(): +        gpu.call(np.int32(pixels.size), origin_gpu, direction_gpu, np.int32(geometry.first_leaf), states_gpu, pixels_gpu, **gpu_kwargs) -pixels_gpu = cuda.to_device(pixels) -states_gpu = cuda.to_device(states) +        cuda.memcpy_dtoh(pixels, pixels_gpu) -block_size = 64 -gpu_kwargs = {'block': (block_size,1,1), 'grid': (pixels.size//block_size+1,1)} +        pygame.surfarray.blit_array(screen, pixels.reshape(size)) +        pygame.display.flip() -def render(): -    gpu.call(np.int32(pixels.size), origin_gpu, direction_gpu, np.int32(geometry.first_leaf), states_gpu, pixels_gpu, **gpu_kwargs) +    render() -    cuda.memcpy_dtoh(pixels, pixels_gpu) -     -    pygame.surfarray.blit_array(screen, pixels.reshape(size)) -    pygame.display.flip() +    done = False +    clicked = False +    to_origin = np.array([0,-1,0]) +    while not done: +        for event in pygame.event.get(): +            if event.type == MOUSEBUTTONDOWN: +                if event.button == 4: +                    cuda_translate(np.int32(pixels.size), origin_gpu, gpuarray.vec.make_float3(*(scale*to_origin)), **gpu_kwargs) +                    render() -render() +                if event.button == 5: +                    cuda_translate(np.int32(pixels.size), origin_gpu, gpuarray.vec.make_float3(*(-scale*to_origin)), **gpu_kwargs) +                    render() -from transform import * +                if event.button == 1: +                    clicked = True +                    mouse_position = pygame.mouse.get_rel() -done = False -clicked = False -to_origin = np.array([0,-1,0]) -while not done: -    for event in pygame.event.get(): -        if event.type == MOUSEBUTTONDOWN: -            if event.button == 4: -                cuda_translate(np.int32(pixels.size), origin_gpu, gpuarray.vec.make_float3(*(scale*to_origin)), **gpu_kwargs) -                render() +            if event.type == MOUSEBUTTONUP: +                if event.button == 1: +                    clicked = False -            if event.button == 5: -                cuda_translate(np.int32(pixels.size), origin_gpu, gpuarray.vec.make_float3(*(-scale*to_origin)), **gpu_kwargs) -                render() +            if event.type == MOUSEMOTION and clicked: +                movement = pygame.mouse.get_rel()[0]/float(width) + +                if movement == 0: +                    continue -            if event.button == 1: -                clicked = True -                mouse_position = pygame.mouse.get_rel() +                cuda_rotate(np.int32(pixels.size), origin_gpu, np.float32(movement*2*np.pi), gpuarray.vec.make_float3(0,0,1), **gpu_kwargs) -        if event.type == MOUSEBUTTONUP: -            if event.button == 1: -                clicked = False +                cuda_rotate(np.int32(pixels.size), direction_gpu, np.float32(movement*2*np.pi), gpuarray.vec.make_float3(0,0,1), **gpu_kwargs) -        if event.type == MOUSEMOTION and clicked: -            movement = pygame.mouse.get_rel()[0]/float(width) +                to_origin = rotate(to_origin, movement*2*np.pi, (0,0,1)) -            if movement == 0: -                continue +                render() -            cuda_rotate(np.int32(pixels.size), origin_gpu, np.float32(movement*2*np.pi), gpuarray.vec.make_float3(0,0,1), **gpu_kwargs) +            if event.type == KEYUP or event.type == KEYDOWN: +                if event.key == K_ESCAPE: +                    done = True +                    break -            cuda_rotate(np.int32(pixels.size), direction_gpu, np.float32(movement*2*np.pi), gpuarray.vec.make_float3(0,0,1), **gpu_kwargs) +if __name__ == '__main__': +    import sys +    import optparse -            to_origin = rotate(to_origin, movement*2*np.pi, (0,0,1)) +    parser = optparse.OptionParser('%prog filename.stl') +    parser.add_option('-b', '--bits', type='int', dest='bits', help='number of bits for z ordering space axes', default=4) +    options, args = parser.parse_args() -            render() +    if len(args) < 1: +        sys.exit(parser.format_help()) -        if event.type == KEYUP or event.type == KEYDOWN: -            if event.key == K_ESCAPE: -                done = True -                break +    geometry = Geometry() +    geometry.add_solid(Solid(read_stl(args[0]), vacuum, vacuum)) +    geometry.build(options.bits) +    view(geometry, os.path.split(args[0])[-1])  | 
