def linearpolarizer(field, rotation=0): """ Definition that represents a linear polarizer. Parameters ---------- field : ndarray Polarization vector of an input beam. rotation : float Represents rotation of the polarizer along propagation direction in angles (couter-clockwise). Returns ---------- result : ndarray Polarization vector of an output beam. """ rotation = np.radians(rotation) rotmat = np.array([[float(np.cos(rotation)), float(np.sin(rotation))], [float(-np.sin(rotation)), float(np.cos(rotation))]]) linearpolarizer = np.array([[1, 0], [0, 0]]) linearpolarizer = np.dot(rotmat.transpose(), np.dot(linearpolarizer, rotmat)) result = np.dot(linearpolarizer, field) return result
def intersect(self,ray): """ A definition to find out which surface to intersect a ray(s). Parameters ---------- ray : ndarray Ray(s) to be intersected. """ convex_normal,convex_distance = intersect_w_sphere( ray, self.convex_surface ) plane_normal,plane_distance = intersect_w_circle( ray, self.plane_surface ) test_normal = convex_normal if len(test_normal.shape) < 3: test_normal = convex_normal[0] is_it_in_lens = same_side( test_normal, self.convex_point, self.plane_surface[0][1], self.plane_surface[0][2] ) surface_normals = np.array([convex_normal,plane_normal],dtype=np.float) surface_distances = np.array([convex_distance,plane_distance],dtype=np.float) which_surface = np.amin(surface_distances,axis=0) ids = np.where(surface_distances==which_surface) ids = np.asarray(ids) # ids[0] = ids[0] | is_it_in_lens normal = surface_normals[ids[0],ids[1]] distance = surface_distances[ids[0],ids[1]] return normal,distance
def define_geometry(self): """ A definition to define geometry of a plano-convex lens. """ self.center = np.array([ 0., 0., self.radius-self.thickness ]) self.center,_,_,_ = rotate_point( self.center, angles=self.rotation, offset=self.location ) self.plane_center = np.array([0.,0.,0.])+self.location self.convex_point = self.plane_center-self.thickness self.convex_surface = define_sphere( self.center, self.radius ) self.plane_surface = define_circle( self.plane_center, self.diameter, self.rotation )
def calculate_intersection_of_two_rays(ray0, ray1): """ Definition to calculate the intersection of two rays. Parameters ---------- ray0 : ndarray A ray. ray1 : ndarray A ray. Returns ---------- point : ndarray Point in X,Y,Z. distances : ndarray Distances. """ A = np.array([[float(ray0[1][0]), float(ray1[1][0])], [float(ray0[1][1]), float(ray1[1][1])], [float(ray0[1][2]), float(ray1[1][2])]]) B = np.array([ ray0[0][0] - ray1[0][0], ray0[0][1] - ray1[0][1], ray0[0][2] - ray1[0][2] ]) distances = np.linalg.lstsq(A, B)[0] if np.allclose(np.dot(A, distances), B) == False: distances = np.array([0, 0]) distances = distances[np.argsort(-distances)] point = propagate_a_ray(ray0, distances[0])[0] return point, distances
def create_ray(x0y0z0, abg): """ Definition to create a ray. Parameters ---------- x0y0z0 : list List that contains X,Y and Z start locations of a ray. abg : list List that contaings angles in degrees with respect to the X,Y and Z axes. Returns ---------- ray : ndarray Array that contains starting points and cosines of a created ray. """ # Due to Python 2 -> Python 3. x0, y0, z0 = x0y0z0 alpha, beta, gamma = abg # Create a vector with the given points and angles in each direction point = np.array([x0, y0, z0], dtype=np.float) alpha = np.cos(np.radians(alpha)) beta = np.cos(np.radians(beta)) gamma = np.cos(np.radians(gamma)) # Cosines vector. cosines = np.array([alpha, beta, gamma], dtype=np.float) ray = np.array([point, cosines], dtype=np.float) return ray
def test(): start0 = np.array([0., 5., 0.]) start1 = np.array([0., -5., 0.]) intersection = np.array([0., 0., 100.]) ray0 = raytracer.create_ray_from_two_points(start0, intersection) ray1 = raytracer.create_ray_from_two_points(start1, intersection) c0, c1 = raytracer.find_nearest_points(ray0, ray1) print(c0, c1) assert True == True
def batch_of_rays(entry, exit): """ Definition to generate a batch of rays with given entry point(s) and exit point(s). Note that the mapping is one to one, meaning nth item in your entry points list will exit from nth item in your exit list and generate that particular ray. Note that you can have a combination like nx3 points for entry or exit and 1 point for entry or exit. But if you have multiple points both for entry and exit, the number of points have to be same both for entry and exit. Parameters ---------- entry : ndarray Either a single point with size of 3 or multiple points with the size of nx3. exit : ndarray Either a single point with size of 3 or multiple points with the size of nx3. Returns ---------- rays : ndarray Generated batch of rays. """ norays = np.array([0, 0]) if len(entry.shape) == 1: entry = entry.reshape((1, 3)) if len(exit.shape) == 1: exit = exit.reshape((1, 3)) norays = np.amax(np.asarray([entry.shape[0], exit.shape[0]])) if norays > exit.shape[0]: exit = np.repeat(exit, norays, axis=0) elif norays > entry.shape[0]: entry = np.repeat(entry, norays, axis=0) rays = [] norays = int(norays) for i in range(norays): rays.append(create_ray_from_two_points(entry[i], exit[i])) rays = np.asarray(rays) return rays
def circular_uniform_random_sample(no=[10, 50], radius=10., center=[0., 0., 0.], angles=[0., 0., 0.]): """ Definition to generate sample inside a circle uniformly but randomly. Parameters ---------- no : list Number of samples. radius : float Radius of the circle. center : list Center location of the surface. angles : list Tilt of the surface. Returns ---------- samples : ndarray Samples generated. """ samples = np.empty((0, 3)) rs = np.sqrt(np.random.uniform(0, 1, no[0])) angs = np.random.uniform(0, 2 * np.pi, no[1]) for i in rs: for angle in angs: r = radius * i point = np.array( [float(r * np.cos(angle)), float(r * np.sin(angle)), 0]) samples = np.vstack((samples, point)) samples = rotate_points(samples, angles=angles, offset=center) return samples
def circular_uniform_sample(no=[10, 50], radius=10., center=[0., 0., 0.], angles=[0., 0., 0.]): """ Definition to generate sample inside a circle uniformly. Parameters ---------- no : list Number of samples. radius : float Radius of the circle. center : list Center location of the surface. angles : list Tilt of the surface. Returns ---------- samples : ndarray Samples generated. """ samples = np.empty((0, 3)) for i in range(0, no[0]): r = i / no[0] * radius ang_no = no[1] * i / no[0] for j in range(0, int(no[1] * i / no[0])): angle = j / ang_no * 2 * np.pi point = np.array( [float(r * np.cos(angle)), float(r * np.sin(angle)), 0]) samples = np.vstack((samples, point)) samples = rotate_points(samples, angles=angles, offset=center) return samples
def reflect(input_ray, normal): """ Definition to reflect an incoming ray from a surface defined by a surface normal. Used method described in G.H. Spencer and M.V.R.K. Murty, "General Ray-Tracing Procedure", 1961. Parameters ---------- input_ray : ndarray A vector/ray (2x3). It can also be a list of rays (nx2x3). normal : ndarray A surface normal (2x3). It also be a list of normals (nx2x3). Returns ---------- output_ray : ndarray Array that contains starting points and cosines of a reflected ray. """ input_ray = np.asarray(input_ray) normal = np.asarray(normal) if len(input_ray.shape) == 2: input_ray = input_ray.reshape((1, 2, 3)) if len(normal.shape) == 2: normal = normal.reshape((1, 2, 3)) mu = 1 div = normal[:, 1, 0]**2 + normal[:, 1, 1]**2 + normal[:, 1, 2]**2 a = mu * (input_ray[:, 1, 0] * normal[:, 1, 0] + input_ray[:, 1, 1] * normal[:, 1, 1] + input_ray[:, 1, 2] * normal[:, 1, 2]) / div n = np.int(np.amax(np.array([normal.shape[0], input_ray.shape[0]]))) output_ray = np.zeros((n, 2, 3)) output_ray[:, 0] = normal[:, 0] output_ray[:, 1] = input_ray[:, 1] - 2 * a * normal[:, 1] if output_ray.shape[0] == 1: output_ray = output_ray.reshape((2, 3)) return output_ray
def rotate_points(points,angles=[0,0,0],mode='XYZ',origin=[0,0,0],offset=[0,0,0]): """ Definition to rotate points. Parameters ---------- points : ndarray Points. angles : list Rotation angles in degrees. mode : str Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes. origin : list Reference point for a rotation. offset : list Shift with the given offset. Returns ---------- result : ndarray Result of the rotation """ points = np.asarray(points) if angles[0] == 0 and angles[1] == 0 and angles[2] ==0: result = np.array(offset) + points return result points -= np.array(origin) rotx = rotmatx(angles[0]) roty = rotmaty(angles[1]) rotz = rotmatz(angles[2]) if mode == 'XYZ': result = np.dot(rotz,np.dot(roty,np.dot(rotx,points.T))).T elif mode == 'XZY': result = np.dot(roty,np.dot(rotz,np.dot(rotx,points.T))).T elif mode == 'YXZ': result = np.dot(rotz,np.dot(rotx,np.dot(roty,points.T))).T elif mode == 'ZXY': result = np.dot(roty,np.dot(rotx,np.dot(rotz,points.T))).T elif mode == 'ZYX': result = np.dot(rotx,np.dot(roty,np.dot(rotz,points.T))).T result += np.array(origin) result += np.array(offset) return result
def test(): detector_location = [2., 0., 0.] triangle = np.array([[10., 10., 10.], [0., 10., 10.], [0., 0., 10.]]) circle_center = [0., 0., 0.] circle_angles = [0., 0., 0.] circle_radius = 15. circle = raytracer.define_circle(angles=circle_angles, center=circle_center, radius=circle_radius) opl = detector_to_light_source(detector_location, triangle, circle) assert True == True
def add_line(self,point_start,point_end,row=1,column=1,color='red'): """ Definition to add a ray to the figure. Parameters ---------- point_start : ndarray Starting point(s). point_end : ndarray Ending point(s). row : int Row number of the figure. column : int Column number of the figure. color : str Color of the lune to be drawn. """ if np.__name__ == 'cupy': point_start = np.asnumpy(point_start) point_end = np.asnumpy(point_end) if len(point_start.shape) == 1: point_start = point_start.reshape((1,3)) if len(point_end.shape) == 1: point_end = point_end.reshape((1,3)) if point_start.shape != point_end.shape: print('Size mismatch in line plot. Sizes are {} and {}.'.format(point_start.shape,point_end.shape)) sys.exit() for point_id in range(0,point_start.shape[0]): points = np.array( [ point_start[point_id], point_end[point_id] ] ) points = points.reshape((2,3)) if np.__name__ == 'cupy': points = np.asnumpy(points) self.fig.add_trace( go.Scatter3d( x=points[:,0], y=points[:,1], z=points[:,2], mode='lines', line=dict( width=self.settings["line width"], color=color, ), opacity=self.settings["opacity"] ), row=row, col=column )
def load_image(fn): """ Definition to load an image from a given location as a Numpy array. Parameters ---------- fn : str Filename. Returns ---------- image : ndarray Image loaded as a Numpy array. """ image = Image.open(fn) return np.array(image)
def electricfield(px, py): """ Definition to create an electric field vector (polarization vector). Parameters ---------- px : float Amplitude of the electric field along X axis. py : float Amplitude of the electric field along Y axis. Returns ---------- field : ndarray An electric field vector (polarization vector). """ field = np.array([[px], [py]]) return field
def cross_product(vector1, vector2): """ Definition to cross product two vectors and return the resultant vector. Used method described under: http://en.wikipedia.org/wiki/Cross_product Parameters ---------- vector1 : ndarray A vector/ray. vector2 : ndarray A vector/ray. Returns ---------- ray : ndarray Array that contains starting points and cosines of a created ray. """ angle = np.cross(vector1[1].T, vector2[1].T) angle = np.asarray(angle) ray = np.array([vector1[0], angle], dtype=np.float) return ray
def rotmatz(angle): """ Definition to generate a rotation matrix along Z axis. Parameters ---------- angles : list Rotation angles in degrees. Returns ---------- rotz : ndarray Rotation matrix along Z axis. """ angle = np.radians(angle) rotz = np.array([ [ math.cos(angle), -math.sin(angle), 0.], [ math.sin(angle), math.cos(angle), 0.], [ 0., 0., 1.] ],dtype=np.float) return rotz
def intersect_w_surface(ray, points): """ Definition to find intersection point inbetween a surface and a ray. For more see: http://geomalgorithms.com/a06-_intersect-2.html Parameters ---------- ray : ndarray A vector/ray. points : ndarray Set of points in X,Y and Z to define a planar surface. Returns ---------- normal : ndarray Surface normal at the point of intersection. distance : float Distance in between starting point of a ray with it's intersection with a planar surface. """ points = np.asarray(points) normal = get_triangle_normal(points) if len(ray.shape) == 2: ray = ray.reshape((1, 2, 3)) if len(points) == 2: points = points.reshape((1, 3, 3)) if len(normal.shape) == 2: normal = normal.reshape((1, 2, 3)) f = normal[:, 0] - ray[:, 0] distance = np.dot(normal[:, 1], f.T) / np.dot(normal[:, 1], ray[:, 1].T) n = np.int(np.amax(np.array([ray.shape[0], normal.shape[0]]))) normal = np.zeros((n, 2, 3)) normal[:, 0] = ray[:, 0] + distance.T * ray[:, 1] distance = np.abs(distance) if normal.shape[0] == 1: normal = normal.reshape((2, 3)) distance = distance.reshape((1)) if distance.shape[0] == 1 and len(distance.shape) > 1: distance = distance.reshape((distance.shape[1])) return normal, distance
def raytrace(self,ray,field=None,channel=0): """ A definition to calculate the intersection between given ray(s) and the detector. If a ray contributes to the detector, field will be taken into account in calculating the field over the planar detector. Parameters ---------- ray : ndarray Ray(s) to be intersected. field : ndarray Field(s) to be used for calculating contribution of rays to the detector. channel : list Which color channel to contribute to in the detector plane. Default is zero. One can use a list to select multiple channels separately. Returns ---------- normal : ndarray Normal for each intersection point. distance : ndarray Distance for each ray. """ normal,distance = intersect_w_surface(ray,self.plane) points = bring_plane_to_origin( normal[:,0], self.plane, shape=self.settings["shape"], center=self.settings["center"], angles=self.settings["angles"], mode=self.settings["rotation mode"] ) if points.shape[0] == 3: points = points.reshape((1,3)) # This could improve with a bilinear filter. Basically removing int with a filter. detector_ids = np.array( [ (points[:,0]+self.settings["shape"][0]/2.)/self.settings["shape"][0]*self.settings["resolution"][0]+1, (points[:,1]+self.settings["shape"][1]/2.)/self.settings["shape"][1]*self.settings["resolution"][1]+1 ], dtype=int ) detector_ids[0,:] = (detector_ids[0,:]>=1)*detector_ids[0,:] detector_ids[1,:] = (detector_ids[1,:]>=1)*detector_ids[1,:] detector_ids[0,:] = (detector_ids[0,:]<self.settings["resolution"][0]+1)*detector_ids[0,:] detector_ids[1,:] = (detector_ids[1,:]<self.settings["resolution"][1]+1)*detector_ids[1,:] cache = np.zeros( ( self.settings["resolution"][0]+1, self.settings["resolution"][1]+1, self.field.shape[2] ), dtype=np.complex64 ) ################################################################## # This solution is far from ideal. There has to be a better way. # ################################################################## for detector_id in range(0,detector_ids.shape[1]): x = detector_ids[0,detector_id] y = detector_ids[1,detector_id] r2 = distance[detector_id]**2 if type(field) == type(None): cache[x,y] += 1000./r2 else: cache[x,y] += field[x,y]/r2 ################################################################## # This solution isn't working at all as two same ids are # # interpretted as one. # ################################################################## #cache[ # detector_ids[0], # detector_ids[1], # channel # ] += field ################################################################## self.field += cache[1::,1::,:] return normal,distance