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 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 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 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 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_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 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 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 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 produce_phase_only_slm_pattern(hologram,slm_range): """ Definition for producing a pattern for a phase only Spatial Light Modulator (SLM) using a given field. Parameters ========== hologram : torch.cfloat Input holographic field. slm_range : float Range of the phase only SLM in radians for a working wavelength (i.e. two pi). See odak.wave.adjust_phase_only_slm_range() for more. filename : str Optional variable, if provided the patterns will be save to given location. Returns ========== pattern : torch.cfloat Adjusted phase only pattern. """ hologram_phase = calculate_phase(hologram) % (2*np.pi) hologram_phase[hologram_phase>slm_range] = slm_range hologram_phase /= slm_range hologram_phase *= 255 hologram_phase = hologram_phase.int() hologram_phase = hologram_phase.float() hologram_phase *= slm_range/255. return np.cos(hologram_phase)+1j*np.sin(hologram_phase)
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 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 set_amplitude(field, amplitude): """ Definition to keep phase as is and change the amplitude of a given field. Parameters ---------- field : np.complex64 Complex field. amplitude : np.array or np.complex64 Amplitudes. Returns ---------- new_field : np.complex64 Complex field. """ amplitude = calculate_amplitude(amplitude) phase = calculate_phase(field) new_field = amplitude * np.cos(phase) + 1j * amplitude * np.sin(phase) return new_field