def impulse_response_fresnel(field,k,distance,dx,wavelength): """ A definition to calculate impulse response based Fresnel approximation for beam propagation. 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 = np.exp(1j*k*distance)/(1j*wavelength*distance)*np.exp(1j*k/2/distance*Z) h = np.fft.fft2(np.fft.fftshift(h))*dx**2 U1 = np.fft.fft2(np.fft.fftshift(field)) U2 = h*U1 result = np.fft.ifftshift(np.fft.ifft2(U2)) return result
def fraunhofer_inverse(field,k,distance,dx,wavelength): """ A definition to calculate Inverse Fraunhofer based beam propagation. 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). """ distance = np.abs(distance) nv,nu = field.shape l = nu*dx l2 = wavelength*distance/dx dx2 = wavelength*distance/l fx = np.linspace(-l2/2.,l2/2.,nu) fy = np.linspace(-l2/2.,l2/2.,nv) FX,FY = np.meshgrid(fx,fy) FZ = FX**2+FY**2 c = np.exp(1j*k*distance)/(1j*wavelength*distance)*np.exp(1j*k/(2*distance)*FZ) result = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(field/dx**2/c ))) return result
def prism_phase_function(nx, ny, k, angle, dx=0.001, axis='x'): """ A definition to generate 2D phase function that represents a prism. See Goodman's Introduction to Fourier Optics book for more. Parameters ---------- nx : int Size of the output along X. ny : int Size of the output along Y. k : odak.wave.wavenumber See odak.wave.wavenumber for more. angle : float Tilt angle of the prism in degrees. dx : float Pixel pitch. axis : str Axis of the prism. Returns ---------- prism : ndarray Generated phase function for a prism. """ angle = np.radians(angle) size = [ny, nx] x = np.linspace(-size[0] * dx / 2, size[0] * dx / 2, size[0]) y = np.linspace(-size[1] * dx / 2, size[1] * dx / 2, size[1]) X, Y = np.meshgrid(x, y) if axis == 'y': prism = np.exp(-1j * k * np.sin(angle) * Y) elif axis == 'x': prism = np.exp(-1j * k * np.sin(angle) * X) return prism
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). """ nu,nv = field.shape l = nu*dx l2 = wavelength*distance/dx dx2 = wavelength*distance/l fx = np.linspace(-l2/2.,l2/2.,nu) fy = np.linspace(-l2/2.,l2/2.,nv) FX,FY = np.meshgrid(fx,fy) FZ = FX**2+FY**2 c = 1./(1j*wavelength*distance)*np.exp(1j*k*(2./distance)*FZ) c = np.exp(1j*k*distance)/(1j*wavelength*distance)*np.exp(1j*k/(2*distance)*FZ) result = c*np.fft.ifftshift(np.fft.fft2(np.fft.fftshift(field)))*dx**2 return result
def propagate_beam(field, k, distance, dx, wavelength, propagation_type='IR Fresnel'): """ Definitions for Fresnel impulse respone (IR), Fresnel Transfer Function (TF), Fraunhofer diffraction in accordence with "Computational Fourier Optics" by David Vuelz. 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. propagation_type : str Type of the propagation (IR Fresnel, TR Fresnel, Fraunhofer). Returns ======= result : np.complex Final complex field (MxN). """ nu, nv = field.shape x = np.linspace(-nv * dx, nv * dx, nv) y = np.linspace(-nu * dx, nu * dx, nu) X, Y = np.meshgrid(x, y) Z = X**2 + Y**2 if propagation_type == 'IR Fresnel': h = 1. / (1j * wavelength * distance) * np.exp( 1j * k * 0.5 / distance * Z) h = np.fft.fft2(np.fft.fftshift(h)) * pow(dx, 2) U1 = np.fft.fft2(np.fft.fftshift(field)) U2 = h * U1 result = np.fft.ifftshift(np.fft.ifft2(U2)) elif propagation_type == 'TR Fresnel': h = np.exp(1j * k * distance) * np.exp( -1j * np.pi * wavelength * distance * Z) h = np.fft.fftshift(h) U1 = np.fft.fft2(np.fft.fftshift(field)) U2 = h * U1 result = np.fft.ifftshift(np.fft.ifft2(U2)) elif propagation_type == 'Fraunhofer': c = 1. / (1j * wavelength * distance) * np.exp( 1j * k * 0.5 / distance * Z) result = c * np.fft.ifftshift(np.fft.fft2( np.fft.fftshift(field))) * pow(dx, 2) return result
def transfer_function_fresnel(field,k,distance,dx,wavelength): """ A definition to calculate convolution based Fresnel approximation for beam propagation. 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 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) H = np.exp(1j*k*distance*(1-(FX*wavelength)**2-(FY*wavelength)**2)**0.5) U1 = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(field))) U2 = H*U1 result = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(U2))) return result
def freeform(nx, ny, k, distances, dx=0.001): """ A definition to generate a freeform field pattern from a depth map. Parameters ---------- nx : int Size of the output along X. ny : int Size of the output along Y. k : odak.wave.wavenumber See odak.wave.wavenumber for more. distances : ndarray Depth map. dx : float Pixel pitch. Returns --------- field : ndarray Generated pattern. """ size = [ny, nx] x = np.linspace(-size[0] * dx / 2, size[0] * dx / 2, size[0]) y = np.linspace(-size[1] * dx / 2, size[1] * dx / 2, size[1]) X, Y = np.meshgrid(x, y) Z = X**2 + Y**2 field = np.exp(1j * k * 0.5 * np.sin(Z / distances)) return field
def quadratic_phase_function(nx, ny, k, focal=0.4, dx=0.001): """ A definition to generate 2D quadratic phase function, which is typically use to represent lenses. Parameters ---------- nx : int Size of the output along X. ny : int Size of the output along Y. k : odak.wave.wavenumber See odak.wave.wavenumber for more. focal : float Focal length of the quadratic phase function. dx : float Pixel pitch. Returns --------- function : ndarray Generated quadratic phase function. """ size = [ny, nx] x = np.linspace(-size[0] * dx / 2, size[0] * dx / 2, size[0]) y = np.linspace(-size[1] * dx / 2, size[1] * dx / 2, size[1]) X, Y = np.meshgrid(x, y) Z = X**2 + Y**2 qwf = np.exp(1j * k * 0.5 * np.sin(Z / focal)) return qwf
def angular_spectrum(field,k,distance,dx,wavelength): """ A definition to calculate angular spectrum based beam propagation. 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). """ nu,nv = 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 U1 = np.fft.fft2(np.fft.fftshift(field)) U2 = h*U1 result = np.fft.ifftshift(np.fft.ifft2(U2)) return result
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 plane_tilt(nx, ny, k, focals, dx=0.001, axis='x'): """ A definition to tilt a complex field. Parameters ---------- nx : int Size of the output along X. ny : int Size of the output along Y. k : odak.wave.wavenumber See odak.wave.wavenumber for more. focals : list Focus ranges, two for X and two for Y, in total four numbers. Make sure to pass nonzero numbers. dx : float Pixel pitch. axis : str Which state to tilt, x, y or xy. Returns ---------- field : ndarray Field to tilt a plane. """ size = [ny, nx] x = np.linspace(-size[0] * dx / 2, size[0] * dx / 2, size[0]) y = np.linspace(-size[1] * dx / 2, size[1] * dx / 2, size[1]) X, Y = np.meshgrid(x, y) Z = X**2 + Y**2 if np.all((focals == 0)): raise Exception("Focals must be non zero.") focal_x = np.geomspace(focals[0], focals[1], size[0]) focal_y = np.geomspace(focals[2], focals[3], size[1]) FX, FY = np.meshgrid(focal_x, focal_y) field = np.ones((nx, ny), dtype=np.complex64) if axis == 'x' or axis == 'xy': field *= np.exp(1j * k * 0.5 * np.sin(Z / FX)) if axis == 'y' or axis == 'xy': field *= np.exp(1j * k * 0.5 * np.sin(Z / FY)) return field
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 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