Holography demo with real data

We'll start by importing the usual packages

The data from our microscope is in the proprietary .dm3 format from Gatan, the maker of the microscope. Usually this would be opened in something like Gatan Digital Micrograph, but luckily the OpenNCEM project helps us open it in Python (look here if using Matlab)

There is a wealth of metadata available about the microscope conditions embedded in the .dm3 files, but the most relevant to us is simply the scale, i.e. how many nanometers does each pixel correspond to in the image?

Select sideband and do inverse transform

In the fourier transformed image we see the two distinct sidebands. fftshift is used to center the main peak in the image, as it would otherwise be split in the corners.

Note that since the fourier image consists of complex numbers, we visualize the absolute value of the image, i.e. $\sqrt{\mathrm{Re}^2+\mathrm{Im}^2}$ using numpy.abs(). The central peak is much more intense than the sidebands, so we also calculate the logarithm of the image. In this way the lower values get more pronounced and easier to distinguish.

To select a sideband, we find coordinates of the peaks in the image using peak_local_max from the scikit-image package as in this example. The central peak will always be the most intense followed by the two sidebands. We just find the second strongest peak and check if it's a positive or negative phase change, otherwise we take the third strongest.

We make this into a function as we'll do it a few times

We'll also make a function to extract sidebands from an FFT image

Let's extract a sideband and see it up close

We can now calculate the amplitude and phase of the image with numpy's abs and angle methods

There are two important things to notice here. First of all the numpy implementation of np.angle uses the arctan2 function, a numerical implementation of $\tan^{-1}$. There is a nice description of the reasons in the Wikipedia article on atan2. For us, this means that the angle, or phase, we calculate has values as $-\pi<\phi\leq\pi$.

Second, and much more obvious is: the phase image contains sudden jumps in intensity. This happens when the phase "wraps around" with a period of $2 \pi$ (or 360 degrees). In other words the phase angle $\phi=0$ is equivalent to $\phi=2\pi$. Thus we need to "unwrap" our phase image. We will use the scikit-image phase unwrapping implementation

Correcting with a reference phase shift

There is some significant background variations in the phase shift across the image due to imperfections in the biprism, e.g. the bottom left corner is quite dark while the bottom right is lighter. This can be negated with a reference image taken somewhere with a constant background, like an area with an even thickness of carbon substrate:

This is the reference phase shift we correct with

Now we subtract the reference from our image

Subtracting phase image with reversed magnetization

So far so good! We have now extracted a phase image of a magnetic nanoparticle. In order to extract just the magnetic contribution $\phi_m$ to the phase shift we need an image of the same particle where the particle's magnetization, and thus $\phi_m$, is reversed as described in the blog post:

$\phi = \phi_e+\phi_m - (\phi_e - \phi_m) = 2\phi_m$

The magnetic reversal was done by applying a strong magnetic field inside the microscope in the opposite direction of the previous used to magnetize the particle in the first place, using one of the magnetic lenses. We load in the image and subtract a reference phase image just like before:

As expected it looks very similar to the first phase image as $\phi_e$ is dominating the signal. Note that the particle is slightly shifted relative to the first image and subtrating the two directly would not help much. We need to figure out the relative displacement of the two by matching the images.

Matching two images to correct for translation

We will use a numeric method phase cross correlation to find the translation difference between the two images. To start with, we apply a bandpass filter using gaussian blur to remove high and low frequencies, as this can mess up the matching

In the earlier FFT images you might have noticed horizontal and vertical streaks. These come from the fact that our images are finite with an abrupt cutoff at the edges, which can lead to artifacts (sometimes termed spectral leakage). The borders of the above phase images also show large positive and negative values in some places around the edges, which is related to this effect.

These artifacts are detrimental to our image matching, so to avoid this we apply a 2D window function with radial symmetry, in our case a Hann window, before trying to match the images.

Since the images are very similar with negligible rotation we can calculate the shift directly from the phase correlation. Knowing this shift we can ensure the images have a common center and easily subtract one from the other

The magnetic phase shift has revealed itself! Note how there is a lighter region on one side of the particle and dark on the other. We'll enhance it by cutting out the relevant part and filter high frequency noise with a simple gaussian blur

Now we can plot the cleaned up image for a nice view of the phase image. We also add a scalebar with matplotlib-scalebar

Magnetic field from phase shift

A profile plot across the particle to shows the value of the phase change. Now is also a good time to ensure the axes on the image match the physical size of the picture

We can now visualize the magnetic field lines by displaying contour lines of the phase image (i.e. lines where the phase has the same value). By using a divergent colormap 'RdBu' which goes from red to blue with white for central values we can also easily distinguish values above and below the central values.

It can also be useful to overlay contourlines onto the bright field image to illustrate the magnetic field lines coming from the particle

Now that is looking like proper magnetic field lines!

We can also overlay the contours on our high resolution original image, we just need to careful to rescale and crop everything the same way. We will also overlay a scalebar for a nice, final image.