Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Registration of a Stack of Images

Image Tools


Registration of a Stack of Images

OpenInColab

by

Gerd Duscher

Materials Science & Engineering
Joint Institute of Advanced Materials
The University of Tennessee, Knoxville

Content

It is advantageous to acquire a stack of scanned images instead of one with rather slow scan rate.

The noise is reduced and sample drift can be derived and removed from the image.

Note:

We us this notebook only for a stack of images.

Install pyTEMlib

If you have not done so in the Introduction Notebook, please install pyTEMlib with the code cell below.

done

Import the usual libraries

You’ll need at least pyTEMlib version 0.2026.1.0

You can install that library with the code cell above.

Here we are using the following modules of pyTEMlib.

  • file_tools # File input/ output library

  • pyTEMlib.image_tools

  • pyTEMlib.probe_tools

  • pyTEMlib.atom_tools

pyTEM version:  0.2026.9.0

Load an image stack :

Please, load an image stack.

A stack of images is used to reduce noise, but for an added image the images have to be aligned to compensate for drift and other microscope instabilities.

You select here (with the Select Main button,) a file of your image.

Note that the Add should only used for reference data

Loading...
Loading...
Loading...
Loading...
Loading...

Some of the metadata is available in the metadata attribute of the dataset but all information can be found in original metadata.

Loading...
((247, 247), 4090)
c:\Users\gduscher\OneDrive - University of Tennessee\GitHub\pyTEMlib\.venv\Lib\site-packages\matplotlib\colors.py:3747: RuntimeWarning: invalid value encountered in cast
  i = (h * 6.0).astype(int)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [0.0..86.82016988013577].
Loading...

Complete Registration

Takes a while, depending on your computer between 1 and 10 minutes

Rigid Registration

Using sub-pixel accuracy registration determination method of:

Manuel Guizar-Sicairos, Samuel T. Thurman, and James R. Fienup, “Efficient subpixel image registration algorithms,” Optics Letters 33, 156-158 (2008). DOI:10.1364/OL.33.000156

as implemented in phase_cross_correlation function by scikit-image in the registration package.

Loading...
np.float64(66.16749164585893)
array([[-2., -4.], [-2., -3.], [-2., -2.], [-2., -2.], [-2., -2.], [-2., -2.], [-1., -1.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 2.], [ 0., 2.], [ 0., 2.], [ 0., 2.], [ 0., 2.], [ 0., 2.]])
Loading...
Loading...

Non-Rigid Registration

Here we use the Diffeomorphic Demon Non-Rigid Registration as provided by simpleITK.

Please Cite:

Loading...
:-)
You have successfully completed Diffeomorphic Demons Registration
Loading...
Loading...
Loading...
Loading...

Or do it all together

Rigid_Registration
Stack contains  10  images, each with 1024  pixels in x-direction and  1024  pixels in y-direction
Loading...
sidpy.Dataset of type IMAGE_STACK with:
 dask.array<array, shape=(10, 1023, 1022), dtype=float64, chunksize=(10, 1023, 1022), chunktype=numpy.ndarray>
 data contains: intensity (counts)
 and Dimensions: 
frame:  time (frame) of size (10,)
x:  Length (nm) of size (1023,)
y:  Length (nm) of size (1022,)
 with metadata: ['analysis', 'drift', 'input_crop', 'input_shape', 'experiment']
Non-Rigid_Registration
Loading...
:-)
You have successfully completed Diffeomorphic Demons Registration
Loading...
Loading...

You will need to zoom in to see that the images are changing.

Compare this to the rigid registered stack

Loading...
Loading...

Check Drift

array([[-1., -6.], [ 0., -5.], [ 0., -4.], [ 0., -4.], [-2., -4.], [-4., -4.], [-4., -3.], [-5., -3.], [-4., -2.], [-2., -1.], [ 0., 0.], [ 1., 1.], [ 1., 1.], [ 1., 1.], [ 0., 1.], [ 0., 1.], [ 0., 2.], [ 0., 2.], [ 0., 3.], [ 0., 4.]])
Loading...

Find Atom Positions

Lucy -Richardson Deconvolution

Lucy - Richardson Deconvolution removes noise and convolutes the intensity back into the atom (columns).

Here we use a slightly modified Lucy - Richardson Deconvolution which stops when converged.

Ideally the atom_size should be as large as the atoms in the image.

A good Lucy-Richardson Deconvolution should result in an image with atoms of a radius of about 2 pixels.

The number of steps to convergence should be less than 300 for a good approximation of atom_size.

we use the non-rigid registered datset

1.3313608508711974
1.3313608508711974 0.08
Deconvolution of  HAADF
Loading...
converged in 39 iterations

 Lucy-Richardson deconvolution converged in 39  iterations
Loading...
Loading...
fft :
	smoothing : 3
	minimum_intensity : 12.735340729817992
	maximum_intensity : 18.906446933063556
source:  sum_aggregate_Non-Rigid Registration
Loading...

Atom Detection

Choose threshold and atom size so that all atoms or at least all bright atoms of an unit cell are found

Loading...

Log Atom Positions

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[76], line 3
      1 out_tags  =  {}
      2 out_tags['analysis']= 'Atom Positions'
----> 3 out_tags['notebook']= __notebook__ 
      4 out_tags['notebook_version']= __notebook_version__
      6 out_tags['atoms'] = blobs

NameError: name '__notebook__' is not defined
[[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], [25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25], [36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36]]
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 20 out of 20 | elapsed:    0.3s finished
0 [0 1 2] (0, 0)
3 [3 4 5] (0, 1)
6 [6 7 8] (1, 0)
9 [ 9 10 11] (1, 1)
array([6, 7, 8])
array([[[[[ 0, 1, 2], [ 3, 4, 5]], [[ 0, 1, 2], [ 3, 4, 5]]], [[[ 6, 7, 8], [ 9, 10, 11]], [[ 6, 7, 8], [ 9, 10, 11]]]], [[[[ 0, 1, 2], [ 3, 4, 5]], [[ 6, 7, 8], [ 9, 10, 11]]], [[[ 0, 1, 2], [ 3, 4, 5]], [[ 6, 7, 8], [ 9, 10, 11]]]]])
Atoms(symbols='Cu495', pbc=True, cell=[7.060444952883329, 7.0003560596673005, 1.0])

Refine All Atom Positions

Fitting of a Gaussian into the center of an atom

There will be convergence errors if the atom_radius value is too large or too small

Loading...
sidpy.Dataset of type IMAGE_STACK with:
 dask.array<sub, shape=(512, 512), dtype=float64, chunksize=(512, 512), chunktype=numpy.ndarray>
 data contains: generic (generic)
 and Dimensions: 
y:  distance (nm) of size (512,)
x:  distance (nm) of size (512,)
 with metadata: ['metadata', 'original_metadata']
using radius  3 pixels
Loading...
Loading...
Loading...
{'Channel_000': sidpy.Dataset of type IMAGE_STACK with: dask.array<array, shape=(20, 512, 512), dtype=float64, chunksize=(20, 512, 512), chunktype=numpy.ndarray> data contains: intensity (counts) and Dimensions: frame: time (frame) of size (20,) x: distance (nm) of size (512,) y: distance (nm) of size (512,) with metadata: ['experiment', 'filename'], 'Channel_001': sidpy.Dataset of type IMAGE_STACK with: dask.array<array, shape=(20, 512, 512), dtype=float64, chunksize=(20, 512, 512), chunktype=numpy.ndarray> data contains: intensity (counts) and Dimensions: frame: time (frame) of size (20,) x: distance (nm) of size (512,) y: distance (nm) of size (512,) with metadata: ['experiment', 'filename'], 'Channel_002': sidpy.Dataset of type IMAGE_STACK with: dask.array<array, shape=(20, 512, 512), dtype=float64, chunksize=(20, 512, 512), chunktype=numpy.ndarray> data contains: intensity (counts) and Dimensions: frame: time (frame) of size (20,) x: distance (nm) of size (512,) y: distance (nm) of size (512,) with metadata: ['experiment', 'filename'], 'Channel_003': sidpy.Dataset of type IMAGE_STACK with: dask.array<array, shape=(20, 512, 512), dtype=float64, chunksize=(20, 512, 512), chunktype=numpy.ndarray> data contains: intensity (counts) and Dimensions: frame: time (frame) of size (20,) x: distance (nm) of size (512,) y: distance (nm) of size (512,) with metadata: ['experiment', 'filename']}

Close File

Close file when finished and everyhting went well.

C:/Users/gduscher/Documents/Github/Image_Distortion\Recording of SuperScan (HAADF)-3.hf5

Appendix

Demon Registration

Here we use the Diffeomorphic Demon Non-Rigid Registration as provided by simpleITK.

Please Cite:

This Non-Rigid Registration consists of the following steps:

  • determine reference image

    • For this we use the average of the rigid registered stack

    • this averaged stack is then smeared with a Gaussian of sigma 2 pixel to reduce noise

    • under the assumption that high frequency scan distortions cancel out over several images, we, therefore, obtained the center of mass of the atoms.

  • perform the demon registration filter to determine a distortion matrix

    • each single image of a stack is first smeared with a Gaussian of sigma of 2pixels

    • then the deformation matrix is determined for these images

    • the deformation matrix is a matrix where each pixel has a vector ( x, and y value) for the relative shift of this pixel.

  • This deformation matrix is used to transform the image

    • The transformation is performed on the original image.

    • Important, here, is to set the interpolator method, (the image needs to be interpolated because the new pixels are not on an integer grid.)

Let’s see what the different interpolators do.

MethodRMS contrastStandardMean
original0.19658060.077641140.3949583
Linear0.201593150.0794703660.39421165
BSpline0.201626060.07948310.39421043
Gaussian0.143105820.0564143020.39421389
Hamming0.201632930.079486720.39421496

The Gaussian interpolator as the only one seems to smear the signal.

We will use the Bspline method a fast and simple method that does not introduce spurious features and does not smear the signal.

Full Code of Demon registration