Wavefront Calibration

Thus far, our examples have made two important assumptions:

  • The SLM is perfectly in the Fourier domain without aberration, connected to the imaging domain via Fourier transform, and

  • The optical amplitude of the source beam covers the SLM uniformly.

Generally, in practical experiments, both assumptions are false.

  • Beamline misalignment, defocus, or imperfect optics will cause optical aberration.

  • The source beam will generally be Gaussian, and cannot cover the SLM uniformly without significant loss.

In this example, we will make use of functions and calibrations built into slmsuite to:

  • Correct for optical aberration, such that phase profiles can be displayed with compensation, and

  • Measure the sourced amplitude, such that GS-type optimization algorithms use a better base approximation the system (see slmsuite.holography.algorithms).

Initialization

To start, we initialize our system consisting of a camera and a SLM separated by a Fourier transform.

[2]:
slm = Santec(slm_number=1, display_number=2, wav_um=.633, settle_time_s=.5); print()
cam = AlliedVision(serial="02C5V", verbose=True, fliplr=True)
fs = FourierSLM(cam, slm)
Santec slm_number=1 initializing... success
Looking for display_number=2... success
Opening LCOS-SLM,SOC,8001,2018021001... success

vimba initializing... success
Looking for cameras... success
vimba sn 02C5V initializing... success

We also make a helper function which we will use to display results:

[3]:
def plot(title=""):
    _, axs = plt.subplots(1, 3, figsize=(16,4))

    if slm.phase_correction is None:
        correction = 0 * slm.phase
    else:
        correction = np.mod(slm.phase_correction, 2*np.pi)

    axs[0].set_title("Phase Correction")
    axs[0].imshow(
        correction,
        vmin=0,
        vmax=2*np.pi,
        interpolation="none",
        cmap="twilight"
    )

    axs[1].set_title("Displayed Phase")
    axs[1].imshow(
        np.mod(slm.phase, 2*np.pi),
        vmin=0,
        vmax=2*np.pi,
        interpolation="none",
        cmap="twilight"
    )

    axs[2].set_title("Camera Result")
    axs[2].imshow(cam.get_image())

    plt.suptitle(title)

    plt.show()

Without Wavefront Calibration

An uncalibrated SLM will generally not achieve diffraction-limited performance. For instance, some SLMs possess inherent curvature, which must be calibrated for ideal operation. We can take a look at this aberration, as seen below on a Santec SLM:

[4]:
cam.set_exposure(2e-4)
slm.phase_correction = None

slm.write(None, settle=True)
plot(title="No Correction, No Blaze")

slm.write(toolbox.phase.blaze(grid=slm, vector=(-.003, -.003)), settle=True)
plot(title="No Correction, With Blaze")
../_images/_examples_wavefront_calibration_6_0.png
../_images/_examples_wavefront_calibration_6_1.png

Vendor Phase Calibration

Some vendors provide phase calibration data for the wavefront of their SLMs, usually acquired by Shack-Hartmann wavefront sensing. Subclasses implementing a vendor’s SDK can also support the SLM.load_vendor_phase_calibration() function to interpret the file provided by the vendor (.csv in the case of Santec):

[5]:
slm.load_vendor_phase_correction(
    file_path='Wavefront_correction_Data_221136000005(830nm).csv',  # Update this path to load your calibration
    smooth=True
)

slm.write(None, settle=True)
plot(title="Correction, No Blaze")

slm.write(toolbox.phase.blaze(grid=slm, vector=(-.003, -.003)), settle=True)
plot(title="Correction, With Blaze")
../_images/_examples_wavefront_calibration_8_0.png
../_images/_examples_wavefront_calibration_8_1.png

Immediately, we see an improvement: rather than observing a distorted spot, we see one that is approximately diffraction limited. If a user knows the approximate focal length of a curved SLM, but does not have a calibration, a toolbox.lens phase pattern can be loaded to the .phase_calibration attribute of the SLM as an analytic proxy.

Still, we can do better. The vendor-provided data only corrects for aberration on the SLM, but knows nothing of other beamline aberrations related to our setup. Additionally, information regarding source amplitude is valuable for holography. Calibrating these values, as aforementioned, is the goal of FourierSLM.wavefront_calibrate().

Fourier Calibration

The automated wavefront calibration routines which are part of slmsuite are conducted through camera feedback. For this to work, we first need to calibrate the camera’s Fourier domain (see Experimental Holography). Note that in this case, it is critical to apply some form of phase correction before calibrating, as otherwise the spots in the Fourier calibration grid would be sufficiently diffraction limited to resolve a grid.

[6]:
cam.set_exposure(.05)

fs.fourier_calibrate(
    array_shape=[30, 20],           # Size of the calibration grid (Nx, Ny) [knm]
    array_pitch=[30, 40],           # Pitch of the calibration grid (x, y) [knm]
    plot=True
)
100%|██████████| 50/50 [00:01<00:00, 41.81it/s]
../_images/_examples_wavefront_calibration_11_1.png
../_images/_examples_wavefront_calibration_11_2.png
../_images/_examples_wavefront_calibration_11_3.png
../_images/_examples_wavefront_calibration_11_4.png
[6]:
{'M': array([[28817.40307096, -1172.18154049],
        [ 1182.81690498, 28847.21274725]]),
 'b': array([[723.95711382],
        [554.54353184]]),
 'a': array([[-1.16424394e-20],
        [ 7.45116124e-19]])}

Testing Wavefront Calibration

Now we are ready for wavefront calibration. This is a serial process where ‘superpixels’ on the SLM of size superpixel_size\(\times\)superpixel_size pixels are tested one after another. We are looking to measure, as mentioned in the introduction, two things:

  • A local phase correction factor about each superpixel, to counteract optical aberration, and

  • A measurement of the optical amplitude at each superpixel.

We can measure these by:

  • Interfering a given superpixel with some reference superpixel (chosen by default to be at the center of the SLM). A fit to the resulting interference fringes yields a measurement of the phase correction needed to bring the given superpixel into the same Fourier plane as the reference superpixel.

  • Integrating the power (amplitude squared) confined in the diffraction order corresponding to the superpixel.

FourierSLM.wavefront_calibrate() is designed with a testing feature, such that when the test_superpixel keyword is passed, the user is provided with helper plots to make sure that calibration is working at the superpixel corresponding to test_superpixel. Shown below is the result for the superpixel with index (16, 16), where superpixels are zero-indexed from the origin coordinate of the SLM. If one looks closely at the phase, small square superpixels can be seen where power is diffracted into a different order. Note the titles of the plots which explain the purpose of each step in this process. The final returned dictionary contains the data that will be gathered from each superpixel on the SLM.

[7]:
cam.set_exposure(.2)
movie = fs.wavefront_calibrate(
    interference_point=(900, 400),
    field_point=(.25, 0),
    field_point_units="freq",
    superpixel_size=50,
    test_superpixel=(16, 16),           # Testing mode
    autoexposure=False,
    plot=3                              # Special mode to generate a phase .gif
)

# Generate a phase .gif
from IPython.display import Image
import imageio
imageio.mimsave('wavefront.gif', movie)
Image(filename="wavefront.gif")
../_images/_examples_wavefront_calibration_13_0.png
../_images/_examples_wavefront_calibration_13_1.png
../_images/_examples_wavefront_calibration_13_2.png
../_images/_examples_wavefront_calibration_13_3.png

../_images/_examples_wavefront_calibration_13_5.png
../_images/_examples_wavefront_calibration_13_6.png
[7]:
<IPython.core.display.Image object>

The diffracted spot resulting from these rectangular superpixels matches their Fourier transform: the amplitude of a rectangular sinc function.

Note that this function returns the result of calibration on a given superpixel, where 'power', 'normalization, and 'background' deal with parameters relevant to amplitude calibration, and other parameters—especially 'phase', 'kx', and 'ky'—are relevant for phase calibration.

Testing allows the user to hone parameters for more ideal calibration, specifically:

  • interference_point, the point in the camera’s "ij" basis where interference takes place.

    • The interference_point should be far from sources of noise, especially the ordered diffraction peaks of the field which contain the vast majority of the optical power in the system.

    • Wavefront calibration works best in a region around the interference_point. The extent of this region depends on the parameters of the optical system and the nature of the aberrations.

  • field_point, the point in the camera’s "ij" basis where the field (power not in a superpixel) is deflected towards.

    • This is useful because it reduces the power in the 0th order. This should be chosen such that higher diffraction orders from the field are far away from the interference_point.

  • Camera exposure.

    • If the user does not use the autoexposure parameter (which sets the maximum exposure of the camera to be 10% of the dynamic range), then the user should make sure that calibration does not overexposure the camera during the calibration process. This might involve trying different test_superpixel superpixels to test superpixels with higher or lower power.

  • superpixel_size, the size in SLM pixels of each superpixel.

    • In general, this should be chosen to be a small as reasonable within SNR and time constraints.

    • Ideally, it would also be a divisor of both the width and height of the SLM, so there are no cropped superpixels.

  • test_superpixel, the superpixel used to test (described above).

    • The user should be sure to try a few test across the SLM domain, to make sure that calibration is working well for a variety of conditions. Especial care—as mentioned in the camera exposure section—should be taken to avoid overexposure, so the user should be sure to test bright test_superpixel superpixels to probe these regimes.

Wavefront Calibration

With calibration working on this test case, we proceed to do this process over the full SLM. Removing the test_superpixel keyword disables testing mode and begins a long (roughly two hours, in this example) serial process of testing each superpixel. tqdm bars monitor progress for user feedback (though these are not visible in the final notebook, as the leave keyword is used).

[8]:
fs.wavefront_calibrate(
    interference_point=(1000, 700),
    field_point=(.25, 0),
    field_point_units="freq",
    superpixel_size=50,
    autoexposure=False
);

We should save the data immediately after this this two hour measurement.

[9]:
fs.save_wavefront_calibration()
[9]:
'c:\\Users\\Experiment\\Documents\\GitHub\\slmsuite\\docs\\slmsuite-examples\\examples\\02C5V-2018021001-wavefront-calibration_00001.h5'

Processing Wavefront Calibration

The raw data from wavefront calibration is stored and saved in a condensed form, where a few numbers for each superpixel is recorded. To use this data, we need to process the information on a per-pixel basis, rather than per-superpixel. This is done by FourierSLM.process_wavefront_calibration(). There are a few options available to the user for honing the calibration processing.

  • r2_threshold sets the threshold for which bad fits are ignored. Notice below that there is significant clipping in the domain of the SLM. Within the clipped region, the fits are good, but outside it’s clear that it’s just noise. The threshold r2_threshold can be adjusted to omit or include superpixels at the user’s preference. There are a few adaptive features to guess what the phase would look like in noise superpixels close to superpixels that are above threshold. Noise pixels outside of this are given a flat phase propagating out from the center.

  • smooth adds blurring to the final result to smooth out the discretization caused by the superpixel approach. This will yield better results in practice as it avoids discontinuities between superpixels.

[10]:
# Without smoothing
fs.process_wavefront_calibration(r2_threshold=.9, smooth=False, plot=True);
../_images/_examples_wavefront_calibration_20_0.png
[11]:
# With smoothing
fs.process_wavefront_calibration(r2_threshold=.9, smooth=True, plot=True);
../_images/_examples_wavefront_calibration_21_0.png
[ ]: