Optimizing holograms using Odak¶
This engineering note will give you an idea about how to optimize phase-only holograms using Odak.
We consult the beginners in this matter to Goodman's Introduction to Fourier Optics
(ISBN-13: 978-0974707723) and Principles of optics: electromagnetic theory of propagation, interference and diffraction of light
from Max Born and Emil Wolf (ISBN 0-08-26482-4).
Note that the creators of this documentation are from the Computational Displays
domain.
However, the provided submodules can potentially aid other lines of research as well, such as Computational Imaging
or Computational Microscopy
.
The optimization that is referred to in this document is the one that generates a phase-only hologram that can reconstruct a target image. There are multiple ways in the literature to optimize a phase-only hologram for a single plane, and these include:
Gerchberg-Saxton and Yang-Yu algorithms: - Yang, G. Z., Dong, B. Z., Gu, B. Y., Zhuang, J. Y., & Ersoy, O. K. (1994). Gerchberg–Saxton and Yang–Gu algorithms for phase retrieval in a nonunitary transform system: a comparison. Applied optics, 33(2), 209-218.
Stochastic Gradient Descent based optimization: - Chen, Y., Chi, Y., Fan, J., & Ma, C. (2019). Gradient descent with random initialization: Fast global convergence for nonconvex phase retrieval. Mathematical Programming, 176(1), 5-37.
Odak provides functions to optimize phase-only holograms using Gerchberg-Saxton
algorithm or the Stochastic Gradient Descent
based approach.
The relevant functions here are odak.learn.wave.stochastic_gradient_descent
and odak.learn.wave.gerchberg_saxton
.
We will review both of these definitions in this document.
But first, let's get prepared.
Preparation¶
We first start with imports, here is all you need:
from odak.learn.wave import stochastic_gradient_descent, calculate_amplitude, calculate_phase
import torch
We will also be needing some variables that defines the wavelength of light that we work with:
Pixel pitch and resolution of the phase-only hologram or a phase-only spatial light modulator that we are simulating:
Define the distance that the light will travel from optimized hologram.
We have to set a target image. You can either load a sample image here or paint a white rectangle on a white background like in this example.
Surely, we also have to set the number of iterations and learning rate for our optimizations.
If you want the GPU support, you also have to set the cuda
as True
.
Propagation type has to be defined as well.
In this example, we will use transfer function Fresnel approach.
For more on propagation types, curious readers can consult
Computational Fourier
Optics David Vuelz
(ISBN13:9780819482044).
This step concludes our preparations.
Let's dive into optimizing our phase-only holograms.
Depending on your choice, you can either optimize using Gerchberg-Saxton
approach or the Stochastic Gradient Descent
approach.
This document will only show you Stochastic Gradient Descent
approach as it is the state of art.
However, optimizing a phase-only hologram is as importing:
and almost as easy as replacing stochastic_gradient_descent
with gerchberg_saxton
in the upcoming described hologram routine.
For greater details, consult to documentation of odak.learn.wave.
Stochastic Gradient Descent approach¶
We have prepared a function for you to avoid compiling a differentiable hologram optimizer from scratch.
hologram, reconstructed = stochastic_gradient_descent(
target,
wavelength,
distance,
dx,
resolution,
'TR Fresnel',
iteration_number,
learning_rate=learning_rate,
cuda=cuda
)
Iteration: 99 loss:0.0003
Congratulations! You have just optimized a phase-only hologram that reconstruct your target image at the target depth.
Surely, you want to see what kind of image is being reconstructed with this newly optimized hologram. You can save the outcome to an image file easily. Odak provides tools to save and load images. First, you have to import:
As you can recall, we have created a target image earlier that is normalized between zero and one.
The same is true for our result, reconstructed
.
Therefore, we have to save it correctly by taking that into account.
Note that reconstructed
is the complex field generated by our optimized hologram
variable.
So, we need to save the reconstructed
intensity as humans and cameras capture intensity but not a complex field with phase and amplitude.
reconstructed_intensity = calculate_amplitude(reconstructed)**2
save_image('reconstructed_image.png',reconstructed_intensity,cmin=0.,cmax=1.)
True
To save our hologram as an image so that we can load it to a spatial light modulator, we have to normalize it between zero and 255 (dynamic range of a typical image on a computer).
P.S. Depending on your SLM's calibration and dynamic range things may vary.
slm_range = 2*3.14
dynamic_range = 255
phase_hologram = calculate_phase(hologram)
phase_only_hologram = (phase_hologram%slm_range)/(slm_range)*dynamic_range
It is now time for saving our hologram:
True
In some cases, you may want to add a grating term to your hologram as you will display it on a spatial light modulator. There are various reasons for that, but the most obvious is getting rid of zeroth-order reflections that are not modulated by your hologram. In case you need it is as simple as below:
from odak.learn.wave import linear_grating
grating = linear_grating(resolution[0],resolution[1],axis='y').to(phase_hologram.device)
phase_only_hologram_w_grating = phase_hologram+calculate_phase(grating)
And let's save what we got from this step:
phase_only_hologram_w_grating = (phase_only_hologram_w_grating%slm_range)/(slm_range)*dynamic_range
save_image('phase_only_hologram_w_grating.png',phase_only_hologram_w_grating)
True
See also¶
For more engineering notes, follow: