Пример #1
0
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
Пример #2
0
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
Пример #3
0
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
Пример #4
0
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
Пример #5
0
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
Пример #6
0
def calculate_intensity(field):
    """
    Definition to calculate intensity of a single or multiple given electric field(s).
    Parameters
    ----------
    field        : ndarray.complex or complex
                   Electric fields or an electric field.
    Returns
    ----------
    intensity    : float
                   Intensity or intensities of electric field(s).
    """
    intensity = np.abs(field)**2
    return intensity
Пример #7
0
def calculate_amplitude(field):
    """ 
    Definition to calculate amplitude of a single or multiple given electric field(s).
    Parameters
    ----------
    field        : ndarray.complex or complex
                   Electric fields or an electric field.
    Returns
    ----------
    amplitude    : float
                   Amplitude or amplitudes of electric field(s).
    """
    amplitude = np.abs(field)
    return amplitude
Пример #8
0
def intersect_parametric(ray,
                         parametric_surface,
                         surface_function,
                         surface_normal_function,
                         target_error=0.00000001,
                         iter_no_limit=100000):
    """
    Definition to intersect a ray with a parametric surface.

    Parameters
    ----------
    ray                     : ndarray
                              Ray.
    parametric_surface      : ndarray
                              Parameters of the surfaces.
    surface_function        : function
                              Function to evaluate a point against a surface.
    surface_normal_function : function
                              Function to calculate surface normal for a given point on a surface.
    target_error            : float
                              Target error that defines the precision.  
    iter_no_limit           : int
                              Maximum number of iterations.

    Returns
    ----------
    distance                : float
                              Propagation distance.
    normal                  : ndarray
                              Ray that defines a surface normal for the intersection.
    """
    if len(ray.shape) == 2:
        ray = ray.reshape((1, 2, 3))
    error = [150, 100]
    distance = [0, 0.1]
    iter_no = 0
    while np.abs(np.max(np.asarray(error[1]))) > target_error:
        error[1], point = intersection_kernel_for_parametric_surfaces(
            distance[1], ray, parametric_surface, surface_function)
        distance, error = propagate_parametric_intersection_error(
            distance, error)
        iter_no += 1
        if iter_no > iter_no_limit:
            return False, False
        if np.isnan(np.sum(point)):
            return False, False
    normal = surface_normal_function(point, parametric_surface)
    return distance[1], normal
Пример #9
0
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
Пример #10
0
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
Пример #11
0
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
Пример #12
0
def propagate_parametric_intersection_error(distance, error):
    """
    Definition to propagate the error in parametric intersection to find the next distance to try.

    Parameters
    ----------
    distance     : list
                   List that contains the new and the old distance.
    error        : list
                   List that contains the new and the old error.
    
    Returns
    ----------
    distance     : list
                   New distance.
    error        : list
                   New error.
    """
    new_distance = distance[1] - error[1] * (distance[1] - distance[0]) / (
        error[1] - error[0])
    distance[0] = distance[1]
    distance[1] = np.abs(new_distance)
    error[0] = error[1]
    return distance, error
Пример #13
0
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