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 adaptive_sampling_angular_spectrum(field,k,distance,dx,wavelength): """ A definition to calculate adaptive sampling angular spectrum based beam propagation. For more Zhang, Wenhui, Hao Zhang, and Guofan Jin. "Adaptive-sampling angular spectrum method with full utilization of space-bandwidth product." Optics Letters 45.16 (2020): 4416-4419. 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). """ raise Exception("Adaptive sampling angular spectrum method is not yet stable. See issue https://github.com/kunguz/odak/issues/13") iflag = -1 eps = 10**(-12) nu,nv = field.shape l = nu*dx x = np.linspace(-l/2,l/2,nu) y = np.linspace(-l/2,l/2,nv) X,Y = np.meshgrid(x,y) fx = np.linspace(-1./2./dx,1./2./dx,nu) fy = np.linspace(-1./2./dx,1./2./dx,nv) FX,FY = np.meshgrid(fx,fy) forig = 1./2./dx fc2 = 1./2*(nu/wavelength/np.abs(distance))**0.5 ss = np.abs(fc2)/forig zc = nu*dx**2/wavelength K = nu/2/np.amax(np.abs(fx)) if np.abs(distance) <= zc*2: nnu2 = nu nnv2 = nv fxn = np.linspace(-1./2./dx,1./2./dx,nnu2) fyn = np.linspace(-1./2./dx,1./2./dx,nnv2) else: nnu2 = nu nnv2 = nv fxn = np.linspace(-fc2,fc2,nnu2) fyn = np.linspace(-fc2,fc2,nnv2) FXN,FYN = np.meshgrid(fxn,fxn) Hn = np.exp(1j*k*distance*(1-(FXN*wavelength)**2-(FYN*wavelength)**2)**0.5) FX = FX/np.amax(FX)*np.pi FY = FY/np.amax(FY)*np.pi t_2 = nufft2(field,FX*ss,FY*ss,size=[nnu2,nnv2],sign=iflag,eps=eps) FX = FXN/np.amax(FXN)*np.pi FY = FYN/np.amax(FYN)*np.pi result = nufft2(Hn*t_2,FX*ss,FY*ss,size=[nu,nv],sign=-iflag,eps=eps) return result
def band_extended_angular_spectrum(field,k,distance,dx,wavelength): """ A definition to calculate bandextended angular spectrum based beam propagation. For more Zhang, Wenhui, Hao Zhang, and Guofan Jin. "Band-extended angular spectrum method for accurate diffraction calculation in a wide propagation range." Optics Letters 45.6 (2020): 1543-1546. 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). """ iflag = -1 eps = 10**(-12) nv,nu = field.shape l = nu*dx x = np.linspace(-l/2,l/2,nu) y = np.linspace(-l/2,l/2,nv) X,Y = np.meshgrid(x,y) Z = X**2+Y**2 fx = np.linspace(-1./2./dx,1./2./dx,nu) fy = np.linspace(-1./2./dx,1./2./dx,nv) FX,FY = np.meshgrid(fx,fy) K = nu/2/np.amax(fx) fcn = 1./2*(nu/wavelength/np.abs(distance))**0.5 ss = np.abs(fcn)/np.amax(np.abs(fx)) zc = nu*dx**2/wavelength if np.abs(distance) < zc: fxn = fx fyn = fy else: fxn = fx*ss fyn = fy*ss FXN,FYN = np.meshgrid(fxn,fyn) Hn = np.exp(1j*k*distance*(1-(FXN*wavelength)**2-(FYN*wavelength)**2)**0.5) X = X/np.amax(X)*np.pi Y = Y/np.amax(Y)*np.pi t_asmNUFT = nufft2(field,X*ss,Y*ss,sign=iflag,eps=eps) result = nuifft2(Hn*t_asmNUFT,X*ss,Y*ss,sign=-iflag,eps=eps) return result
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 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 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 roi(image, location=[0, 100, 0, 100], threshold=[0, 1, 0, 1]): """ Definition to get the lines from a target ROI. Parameters ---------- image : ndarray a 2D image to be sliced (nxm). location : ndarray Locations for taking the ROI. threshold : list Threshold below and above these numbers. Returns ------- line_x : ndarray Line slice. line_y : ndarray Line slice. """ img = image[location[0]:location[1], location[2]:location[3]] if len(img.shape) == 3: img = np.sum(img, axis=2) line_x = img[:, int(img.shape[1] / 2)] line_y = img[int(img.shape[0] / 2), :] line_x = np.asarray(line_x) line_y = np.asarray(line_y) line_x = line_x - np.amin(line_x) line_x = line_x / np.amax(line_x) line_y = line_y - np.amin(line_y) line_y = line_y / np.amax(line_y) line_x[line_x < threshold[0]] = 0 line_x[line_x > threshold[1]] = 1 line_y[line_y < threshold[2]] = 0 line_y[line_y > threshold[3]] = 1 return line_x, line_y, img
def fourier_transform_1d(line): """ Definition to take the 1D fourier transform. This is used only for modulation transfer function calculations. Parameters ---------- line : ndarray 1D array. Returns ---------- result : ndarray Positive side of the fourier transform of the given line. """ result = np.fft.fft(line) #/len(der_x) result /= np.amax(result) result = result[np.arange(0, int(line.shape[0] / 2))] return result
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