def main(): # Variables to be set. wavelength = 0.5*pow(10,-6) pixeltom = 6*pow(10,-6) distance = 10.0 propagation_type = 'Fraunhofer' k = wavenumber(wavelength) sample_field = np.zeros((150,150),dtype=np.complex64) sample_field = np.zeros((150,150),dtype=np.complex64) sample_field[ 65:85, 65:85 ] = 1 sample_field = add_random_phase(sample_field) hologram = propagate_beam( sample_field, k, distance, pixeltom, wavelength, propagation_type ) if propagation_type == 'Fraunhofer': # Uncomment if you want to match the physical size of hologram and input field. #from odak.wave import fraunhofer_equal_size_adjust #hologram = fraunhofer_equal_size_adjust(hologram,distance,pixeltom,wavelength) propagation_type = 'Fraunhofer Inverse' distance = np.abs(distance) reconstruction = propagate_beam( hologram, k, -distance, pixeltom, wavelength, propagation_type ) #from odak.visualize.plotly import detectorshow #detector = detectorshow() #detector.add_field(sample_field) #detector.show() #detector = detectorshow() #detector.add_field(hologram) #detector.show() #detector = detectorshow() #detector.add_field(reconstruction) #detector.show() assert True==True
def main(): n = [100, 100] ranges = [[500., 3000.], [10., 100.]] x, y = np.mgrid[0:n[0], 0:n[1]] focals = x * (ranges[0][1] - ranges[0][0]) / n[0] + ranges[0][0] apertures = y * (ranges[1][1] - ranges[1][0]) / n[1] + ranges[1][0] wavelength = 0.0005 resolutions = np.zeros((n[0], n[1], 3)) for i in range(0, n[0]): for j in range(0, n[1]): resolutions[i, j, 0] = focals[i, j] resolutions[i, j, 1] = apertures[i, j] resolutions[i, j, 2] = odak.wave.rayleigh_resolution( diameter=resolutions[i, j, 1], focal=resolutions[i, j, 0], wavelength=wavelength) * 1000 # Conversion to um. figure = odak.visualize.surfaceshow( title='Spatial resolution', labels=[ 'Throw distance (mm)', 'Aperture size (mm) ', 'Spatial resolution (um)' ], types=['log', 'log', 'log'], font_size=16, tick_no=[2, 2, 4], ) figure.add_surface(data_x=resolutions[:, :, 0], data_y=resolutions[:, :, 1], data_z=resolutions[:, :, 2], contour=False) figure.show() assert True == True
def main(): # Variables to be set. wavelength = 0.5 * pow(10, -6) pixeltom = 6 * pow(10, -6) distance = 0.2 propagation_type = 'IR Fresnel' k = wavenumber(wavelength) sample_field = np.zeros((500, 500), dtype=np.complex64) sample_field[240:260, 240:260] = 1000 random_phase = np.pi * np.random.random(sample_field.shape) sample_field = sample_field * np.cos( random_phase) + 1j * sample_field * np.sin(random_phase) sample_field = torch.from_numpy(sample_field) hologram = propagate_beam_torch(sample_field, k, distance, pixeltom, wavelength, propagation_type) reconstruction = propagate_beam_torch(hologram, k, -distance, pixeltom, wavelength, propagation_type) # from odak.visualize.plotly import detectorshow # detector = detectorshow() # detector.add_field(sample_field) # detector.show() # detector.add_field(hologram) # detector.show() # detector.add_field(reconstruction) # detector.show() assert True == True
def box_volume_sample(no=[10, 10, 10], size=[100., 100., 100.], center=[0., 0., 0.], angles=[0., 0., 0.]): """ Definition to generate samples in a box volume. Parameters ---------- no : list Number of samples. size : list Physical size of the volume. center : list Center location of the volume. angles : list Tilt of the volume. Returns ---------- samples : ndarray Samples generated. """ samples = np.zeros((no[0], no[1], no[2], 3)) x, y, z = np.mgrid[0:no[0], 0:no[1], 0:no[2]] step = [size[0] / no[0], size[1] / no[1], size[2] / no[2]] samples[:, :, :, 0] = x * step[0] + step[0] / 2. - size[0] / 2. samples[:, :, :, 1] = y * step[1] + step[1] / 2. - size[1] / 2. samples[:, :, :, 2] = z * step[2] + step[2] / 2. - size[2] / 2. samples = samples.reshape( (samples.shape[0] * samples.shape[1] * samples.shape[2], samples.shape[3])) samples = rotate_points(samples, angles=angles, offset=center) return samples
def sphere_sample(no=[10, 10], radius=1., center=[0., 0., 0.], k=[1, 2]): """ Definition to generate a regular sample set on the surface of a sphere using polar coordinates. Parameters ---------- no : list Number of samples. radius : float Radius of a sphere. center : list Center of a sphere. k : list Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere. Returns ---------- samples : ndarray Samples generated. """ samples = np.zeros((no[0], no[1], 3)) psi, teta = np.mgrid[0:no[0], 0:no[1]] psi = k[0] * np.pi / no[0] * psi teta = k[1] * np.pi / no[1] * teta samples[:, :, 0] = center[0] + radius * np.sin(psi) * np.cos(teta) samples[:, :, 1] = center[0] + radius * np.sin(psi) * np.sin(teta) samples[:, :, 2] = center[0] + radius * np.cos(psi) samples = samples.reshape((no[0] * no[1], 3)) return samples
def compare(): wavelength = 0.5 * pow(10, -6) pixeltom = 6 * pow(10, -6) distance = 0.2 propagation_type = 'IR Fresnel' k = wavenumber(wavelength) sample_field = np.zeros((500, 500), dtype=np.complex64) sample_field[240:260, 240:260] = 1000 random_phase = np.pi * np.random.random(sample_field.shape) sample_field = sample_field * np.cos( random_phase) + 1j * sample_field * np.sin(random_phase) sample_field_torch = torch.from_numpy(sample_field) ## Propagate and reconstruct using torch. hologram_torch = propagate_beam_torch(sample_field_torch, k, distance, pixeltom, wavelength, propagation_type) reconstruction_torch = propagate_beam_torch(hologram_torch, k, -distance, pixeltom, wavelength, propagation_type) ## Propagate and reconstruct using np. hologram = propagate_beam(sample_field, k, distance, pixeltom, wavelength, propagation_type) reconstruction = propagate_beam(hologram, k, -distance, pixeltom, wavelength, propagation_type) np.testing.assert_array_almost_equal(hologram_torch.numpy(), hologram, 3)
def get_triangle_normal(triangle, triangle_center=None): """ Definition to calculate surface normal of a triangle. Parameters ---------- triangle : ndarray Set of points in X,Y and Z to define a planar surface (3,3). It can also be list of triangles (mx3x3). triangle_center : ndarray Center point of the given triangle. See odak.raytracing.center_of_triangle for more. In many scenarios you can accelerate things by precomputing triangle centers. Returns ---------- normal : ndarray Surface normal at the point of intersection. """ triangle = np.asarray(triangle) if len(triangle.shape) == 2: triangle = triangle.reshape((1, 3, 3)) normal = np.zeros((triangle.shape[0], 2, 3)) direction = np.cross(triangle[:, 0] - triangle[:, 1], triangle[:, 2] - triangle[:, 1]) if type(triangle_center) == type(None): normal[:, 0] = center_of_triangle(triangle) else: normal[:, 0] = triangle_center normal[:, 1] = direction / np.sum(direction, axis=1)[0] if normal.shape[0] == 1: normal = normal.reshape((2, 3)) return normal
def read_PLY_point_cloud(filename): """ Definition to read a PLY file as a point cloud. Parameters ---------- filename : str Filename of a PLY file. Returns ---------- point_cloud : ndarray An array filled with poitns from the PLY file. """ plydata = PlyData.read(filename) if np.__name__ != 'numpy': import numpy as np_ply point_cloud = np_ply.zeros((plydata['vertex'][:].shape[0],3)) point_cloud[:,0] = np_ply.asarray(plydata['vertex']['x'][:]) point_cloud[:,1] = np_ply.asarray(plydata['vertex']['y'][:]) point_cloud[:,2] = np_ply.asarray(plydata['vertex']['z'][:]) point_cloud = np.asarray(point_cloud) else: point_cloud = np.zeros((plydata['vertex'][:].shape[0],3)) point_cloud[:,0] = np.asarray(plydata['vertex']['x'][:]) point_cloud[:,1] = np.asarray(plydata['vertex']['y'][:]) point_cloud[:,2] = np.asarray(plydata['vertex']['z'][:]) return point_cloud
def grid_sample(no=[10, 10], size=[100., 100.], center=[0., 0., 0.], angles=[0., 0., 0.]): """ Definition to generate samples over a surface. Parameters ---------- no : list Number of samples. size : list Physical size of the surface. center : list Center location of the surface. angles : list Tilt of the surface. Returns ---------- samples : ndarray Samples generated. """ samples = np.zeros((no[0], no[1], 3)) step = [size[0] / (no[0] - 1), size[1] / (no[1] - 1)] x, y = np.mgrid[0:no[0], 0:no[1]] samples[:, :, 0] = x * step[0] - size[0] / 2. samples[:, :, 1] = y * step[1] - size[1] / 2. samples = samples.reshape( (samples.shape[0] * samples.shape[1], samples.shape[2])) 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 create_ray_from_angles(point, angles, mode='XYZ'): """ Definition to create a ray from a point and angles. Parameters ---------- point : ndarray Point in X,Y and Z. angles : ndarray Angles with X,Y,Z axes in degrees. All zeros point Z axis. mode : str Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ ,ZXY and ZYX modes. Returns ---------- ray : ndarray Created ray. """ if len(point.shape) == 1: point = point.reshape((1, 3)) new_point = np.zeros(point.shape) new_point[:, 2] += 5. new_point = rotate_points(new_point, angles, mode=mode, offset=point[:, 0]) ray = create_ray_from_two_points(point, new_point) if ray.shape[0] == 1: ray = ray.reshape((2, 3)) return ray
def circular_sample(no=[10, 10], radius=10., center=[0., 0., 0.], angles=[0., 0., 0.]): """ Definition to generate samples inside a circle over a surface. 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.zeros((no[0] + 1, no[1] + 1, 3)) r_angles, r = np.mgrid[0:no[0] + 1, 0:no[1] + 1] r = r / np.amax(r) * radius r_angles = r_angles / np.amax(r_angles) * np.pi * 2 samples[:, :, 0] = r * np.cos(r_angles) samples[:, :, 1] = r * np.sin(r_angles) samples = samples[1:no[0] + 1, 1:no[1] + 1, :] samples = samples.reshape( (samples.shape[0] * samples.shape[1], samples.shape[2])) samples = rotate_points(samples, angles=angles, offset=center) return samples
def test(): from odak import np import torch from odak.learn.wave import gerchberg_saxton, produce_phase_only_slm_pattern, calculate_amplitude from odak.tools import save_image wavelength = 0.000000532 dx = 0.0000064 distance = 0.2 input_field = np.zeros((500, 500), dtype=np.complex64) input_field[0::50, :] += 1 iteration_number = 3 if np.__name__ == 'cupy': input_field = np.asnumpy(input_field) input_field = torch.from_numpy(input_field) hologram, reconstructed = gerchberg_saxton(input_field, iteration_number, distance, dx, wavelength, np.pi * 2, 'IR Fresnel') # hologram = produce_phase_only_slm_pattern( # hologram, # 2*np.pi # ) # amplitude = calculate_amplitude(reconstructed) # amplitude = amplitude.numpy() # save_image( # 'output_amplitude_torch.png', # amplitude, # cmin=0, # cmax=np.amax(amplitude) # ) assert True == True
def test(): from odak import np from odak.wave import gerchberg_saxton, adjust_phase_only_slm_range, produce_phase_only_slm_pattern, calculate_amplitude from odak.tools import save_image wavelength = 0.000000532 dx = 0.0000064 distance = 0.2 input_field = np.zeros((500, 500), dtype=np.complex64) input_field[0::50, :] += 1 iteration_number = 3 hologram, reconstructed = gerchberg_saxton(input_field, iteration_number, distance, dx, wavelength, np.pi * 2, 'IR Fresnel') # hologram = produce_phase_only_slm_pattern( # hologram, # 2*np.pi, # 'output_hologram.png' # ) # amplitude = calculate_amplitude(reconstructed) # save_image( # 'output_amplitude.png', # amplitude, # cmin=0, # cmax=np.amax(amplitude) # ) assert True == True
def main(): # Variables to be set. wavelength = 0.5 * pow(10, -6) pixeltom = 6 * pow(10, -6) distance = 0.1 propagation_type = 'Fraunhofer' k = wavenumber(wavelength) sample_field = np.zeros((150, 150), dtype=np.complex64) sample_field[40:60, 40:60] = 10 sample_field = add_random_phase(sample_field) hologram = propagate_beam(sample_field, k, distance, pixeltom, wavelength, propagation_type) if propagation_type == 'Fraunhofer': distance = np.abs(distance) reconstruction = propagate_beam(hologram, k, distance, pixeltom, wavelength, 'Fraunhofer Inverse') from odak.visualize.plotly import detectorshow detector = detectorshow() detector.add_field(sample_field) detector.show() detector.add_field(hologram) detector.show() detector.add_field(reconstruction / np.amax(np.abs(reconstruction))) detector.show() assert True == True
def test(): no0 = [100, 100] no1 = [100, 100] size0 = [10., 10.] size1 = [10., 10.] wavelength = 0.005 surface_location_0 = [0., 0., 0.] surface_location_1 = [0., 0., 10.] wave_number = wave.wavenumber(wavelength) samples_surface_0 = tools.grid_sample(no=no0, size=size0, center=surface_location_0) samples_surface_1 = tools.grid_sample(no=no1, size=size1, center=surface_location_1) field_0 = np.zeros((no0[0], no0[1])) field_0[50, 50] = 1. field_0[0, 20] = 2. field_0 = field_0.reshape((no0[0] * no0[1])) # Propagates to a specific plane. field_1 = wave.propagate_field(samples_surface_0, samples_surface_1, field_0, wave_number, direction=1) # Reconstruction: propagates back from that specific plane to start plane. field_2 = wave.propagate_field(samples_surface_1, samples_surface_0, field_1, wave_number, direction=-1) assert True == True
def generate_bandlimits(size=[512, 512], levels=9): """ A definition to calculate octaves used in bandlimiting frequencies in the frequency domain. Parameters ---------- size : list Size of each mask in octaves. Returns ---------- masks : ndarray Masks (Octaves). """ masks = np.zeros((levels, size[0], size[1])) cx = int(size[0] / 2) cy = int(size[1] / 2) for i in range(0, masks.shape[0]): deltax = int((size[0]) / (2**(i + 1))) deltay = int((size[1]) / (2**(i + 1))) masks[i, cx - deltax:cx + deltax, cy - deltay:cy + deltay] = 1. masks[i, int(cx - deltax / 2.):int(cx + deltax / 2.), int(cy - deltay / 2.):int(cy + deltay / 2.)] = 0. masks = np.asarray(masks) return masks
def main(): # Variables to be set. wavelength = 0.5*pow(10,-6) pixeltom = 6*pow(10,-6) distance = 0.2 propagation_type = 'Bandlimited Angular Spectrum' k = wavenumber(wavelength) sample_field = np.zeros((500,500),dtype=np.complex64) sample_field[ 240:260, 240:260 ] = 1000 random_phase = np.pi*np.random.random(sample_field.shape) sample_field = sample_field*np.cos(random_phase)+1j*sample_field*np.sin(random_phase) criterion = torch.nn.MSELoss() if np.__name__ == 'cupy': sample_field = np.asnumpy(sample_field) sample_field = torch.from_numpy(sample_field) hologram = propagate_beam_torch( sample_field, k, distance, pixeltom, wavelength, propagation_type ) hologram.requires_grad = True reconstruction = propagate_beam_torch( hologram, k, -distance, pixeltom, wavelength, propagation_type ) loss = criterion(torch.abs(sample_field), torch.abs(reconstruction)) loss.backward() print(hologram.grad) print('backward successfully') #from odak.visualize.plotly import detectorshow #detector = detectorshow() #detector.add_field(sample_field) #detector.show() #detector.add_field(hologram) #detector.show() #detector.add_field(reconstruction) #detector.show() assert True==True
def create_ray_from_two_points(x0y0z0, x1y1z1): """ Definition to create a ray from two given points. Note that both inputs must match in shape. Parameters ---------- x0y0z0 : list List that contains X,Y and Z start locations of a ray (3). It can also be a list of points as well (mx3). This is the starting point. x1y1z1 : list List that contains X,Y and Z ending locations of a ray (3). It can also be a list of points as well (mx3). This is the end point. Returns ---------- ray : ndarray Array that contains starting points and cosines of a created ray. """ x0y0z0 = np.asarray(x0y0z0, dtype=np.float) x1y1z1 = np.asarray(x1y1z1, dtype=np.float) if len(x0y0z0.shape) == 1: x0y0z0 = x0y0z0.reshape((1, 3)) if len(x1y1z1.shape) == 1: x1y1z1 = x1y1z1.reshape((1, 3)) xdiff = x1y1z1[:, 0] - x0y0z0[:, 0] ydiff = x1y1z1[:, 1] - x0y0z0[:, 1] zdiff = x1y1z1[:, 2] - x0y0z0[:, 2] s = np.sqrt(xdiff**2 + ydiff**2 + zdiff**2) s[s == 0] = np.NaN cosines = np.zeros((xdiff.shape[0], 3)) cosines[:, 0] = xdiff / s cosines[:, 1] = ydiff / s cosines[:, 2] = zdiff / s ray = np.zeros((xdiff.shape[0], 2, 3), dtype=np.float) ray[:, 0] = x0y0z0 ray[:, 1] = cosines if ray.shape[0] == 1: ray = ray.reshape((2, 3)) return ray
def plot_diffuser(self, figure): """ Definition to plot diffuser to a odak.visualize.plotly.rayshow(). Parameters ---------- figure : odak.visualize.plotly.rayshow() Figure to plot the diffuser. """ points = grid_sample(no=[10, 10], size=self.settings["shape"], center=self.settings["center"], angles=self.settings["angles"]) points = points.reshape((10, 10, 3)) figure.add_surface(data_x=points[:, :, 0], data_y=points[:, :, 1], data_z=points[:, :, 2], surface_color=np.zeros(points[:, :, 2].shape), opacity=0.5, contour=False)
def __init__(self,field=None,resolution=[1000,1000],shape=[10.,10.],center=[0.,0.,0.],angles=[0.,0.,0.],name='detector'): """ Class to represent a simple planar detector. Parameters ---------- field : ndarray Initial field to be loaded. resolution : list Resolution of the detector. shape : list Shape of the detector. center : list Center of the detector. angles : list Rotation angles of the detector. """ self.settings = { 'name' : name, 'resolution' : resolution, 'center' : center, 'angles' : angles, 'rotation mode' : 'XYZ', 'shape' : shape, } self.plane = define_plane( self.settings['center'], angles=self.settings['angles'] ) if type(field) == type(None): self.field = np.zeros( ( self.settings['resolution'][0], self.settings['resolution'][1], 1 ), dtype=np.complex64 ) self.clear_detector()
def test(): from odak import np from odak.wave import gerchberg_saxton, adjust_phase_only_slm_range, produce_phase_only_slm_pattern, calculate_amplitude from odak.tools import save_image wavelength = 0.000000532 dx = 0.0000064 distance = 2.0 input_field = np.zeros((500, 500), dtype=np.complex64) input_field[0::50, :] += 1 iteration_number = 200 hologram, reconstructed = gerchberg_saxton(input_field, iteration_number, distance, dx, wavelength, np.pi * 2, 'Bandlimited Angular Spectrum') hologram = produce_phase_only_slm_pattern(hologram, 2 * np.pi, 'output_hologram.png') amplitude = calculate_amplitude(reconstructed) save_image('output_amplitude.png', amplitude, cmin=0, cmax=np.amax(amplitude)) assert True == True
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 band_limited_angular_spectrum(field,k,distance,dx,wavelength): """ A definition to calculate bandlimited angular spectrum based beam propagation. For more Matsushima, Kyoji, and Tomoyoshi Shimobaba. "Band-limited angular spectrum method for numerical simulation of free-space propagation in far and near fields." Optics express 17.22 (2009): 19662-19673. Parameters ---------- field : np.complex Complex field (MxN). k : odak.wave.wavenumber Wave number of a wave, see odak.wave.wavenumber for more. distance : float Propagation distance. dx : float Size of one single pixel in the field grid (in meters). wavelength : float Wavelength of the electric field. Returns ======= result : np.complex Final complex field (MxN). """ nv,nu = field.shape x = np.linspace(-nu/2*dx,nu/2*dx,nu) y = np.linspace(-nv/2*dx,nv/2*dx,nv) X,Y = np.meshgrid(x,y) Z = X**2+Y**2 h = 1./(1j*wavelength*distance)*np.exp(1j*k*(distance+Z/2/distance)) h = np.fft.fft2(np.fft.fftshift(h))*dx**2 flimx = np.ceil(1/(((2*distance*(1./(nu)))**2+1)**0.5*wavelength)) flimy = np.ceil(1/(((2*distance*(1./(nv)))**2+1)**0.5*wavelength)) mask = np.zeros((nu,nv),dtype=np.complex64) mask = (np.abs(X)<flimx) & (np.abs(Y)<flimy) mask = set_amplitude(h,mask) U1 = np.fft.fft2(np.fft.fftshift(field)) U2 = mask*U1 result = np.fft.ifftshift(np.fft.ifft2(U2)) return result
def rayleigh_sommerfeld(field,k,distance,dx,wavelength): """ Definition to compute beam propagation using Rayleigh-Sommerfeld's diffraction formula (Huygens-Fresnel Principle). For more see Section 3.5.2 in Goodman, Joseph W. Introduction to Fourier optics. Roberts and Company Publishers, 2005. Parameters ---------- field : np.complex Complex field (MxN). k : odak.wave.wavenumber Wave number of a wave, see odak.wave.wavenumber for more. distance : float Propagation distance. dx : float Size of one single pixel in the field grid (in meters). wavelength : float Wavelength of the electric field. Returns ======= result : np.complex Final complex field (MxN). """ nv,nu = field.shape x = np.linspace(-nv*dx/2,nv*dx/2,nv) y = np.linspace(-nu*dx/2,nu*dx/2,nu) X,Y = np.meshgrid(x,y) Z = X**2+Y**2 result = np.zeros(field.shape,dtype=np.complex64) direction = int(distance/np.abs(distance)) for i in range(nu): for j in range(nv): if field[i,j] != 0: r01 = np.sqrt(distance**2+(X-X[i,j])**2+(Y-Y[i,j])**2)*direction cosnr01 = np.cos(distance/r01) result += field[i,j]*np.exp(1j*k*r01)/r01*cosnr01 result *= 1./(1j*wavelength) return result
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
def point_wise(field,distances,k,dx,wavelength,lens_method='ideal',propagation_method='Bandlimited Angular Spectrum',n_iteration=3): """ Point-wise hologram calculation method. For more Maimone, Andrew, Andreas Georgiou, and Joel S. Kollin. "Holographic near-eye displays for virtual and augmented reality." ACM Transactions on Graphics (TOG) 36.4 (2017): 1-16. Parameters ---------- field : ndarray Complex input field to be converted into a hologram. distances : ndarray Depth map of the input field. k : odak.wave.wavenumber Wave number of a wave, see odak.wave.wavenumber for more. dx : float Pixel pitch. wavelength : float Wavelength of the light. lens_model : str Method to calculate the lens patterns. propagation_mode : str Beam propagation method to be used if the lens_model is not equal to `ideal`. n_iteration : int Number of iterations. Returns ---------- hologram : ndarray Generated complex hologram. """ hologram = np.zeros(field.shape,dtype=np.complex64) nx,ny = field.shape cx = int(nx/2) cy = int(ny/2) non_zeros = np.asarray((np.abs(field)>0).nonzero()) unique_dist = np.unique(distances) unique_dist = unique_dist[unique_dist!=0] target = np.zeros((nx,ny),dtype=np.complex64) target[cx,cy] = 1. lenses = [] for distance in unique_dist: if lens_method == 'ideal': new_lens = quadratic_phase_function(nx,ny,k,focal=distance,dx=dx) lenses.append(new_lens) elif lens_method == 'Gerchberg-Saxton': new_lens,_ = gerchberg_saxton( target, n_iteration, distance, dx, wavelength, np.pi*2, propagation_method ) lenses.append(new_lens) for m in tqdm(range(non_zeros.shape[1])): i = int(non_zeros[0,m]) j = int(non_zeros[1,m]) lens_id = int(np.argwhere(unique_dist==distances[i,j])) lens = lenses[lens_id] lens = np.roll(lens,i-cx,axis=0) lens = np.roll(lens,j-cy,axis=1) hologram += lens*field[i,j] return hologram
def clear_detector(self): """ A definition to clear the field accumulated on the detector. """ self.field = np.zeros(self.field.shape,dtype=np.complex64)