Finding Polarisation Vectors

There are several methods available in the TEMUL Toolkit and Atomap packages for finding polarisation vectors in atomic resolution images. These are briefly described here, followed by a use-case of each.

Current functions:

  1. Using Atomap’s get_polarization_from_second_sublattice Sublattice method. Great for “standard” polarised structures with two sublattices.
  2. Using the TEMUL find_polarisation_vectors function. Useful for structures that Atomap’s get_polarization_from_second_sublattice can’t handle.
  3. Using the TEMUL atom_deviation_from_straight_line_fit function. Useful for calculating polarisation from a single sublattice, similar to and based off: J. Gonnissen et al, Direct Observation of Ferroelectric Domain Walls in LiNbO3: Wall‐Meanders, Kinks, and Local Electric Charges, 26, 42, 2016, DOI: 10.1002/adfm.201603489.

These methods are also available as python scripts and jupyter notebooks in the TEMUL repository in the “code_tutorials/workflows” folder. You can interact with the workflows without needing any downloads. Just click the button below and navigate to that same folder, where you will find the python scripts and interactive python notebooks:

https://mybinder.org/badge_logo.svg

For standard Polarised Structures (e.g., PTO)

Atomap’s get_polarization_from_second_sublattice Sublattice method will be sufficent for most users when dealing with the classic PTO-style polarisation, wherein the atoms in a sublattice are polarised with respect to a second sublattice.

See the second section of this tutorial on how to plot this in many different ways using plot_polarisation_vectors!

>>> import temul.api as tml
>>> from temul.dummy_data import get_polarisation_dummy_dataset
>>> atom_lattice = get_polarisation_dummy_dataset(image_noise=True)
>>> sublatticeA = atom_lattice.sublattice_list[0]
>>> sublatticeB = atom_lattice.sublattice_list[1]
>>> sublatticeA.construct_zone_axes()
>>> za0, za1 = sublatticeA.zones_axis_average_distances[0:2]
>>> s_p = sublatticeA.get_polarization_from_second_sublattice(
...     za0, za1, sublatticeB)
>>> vector_list = s_p.metadata.vector_list
>>> x, y = [i[0] for i in vector_list], [i[1] for i in vector_list]
>>> u, v = [i[2] for i in vector_list], [i[3] for i in vector_list]
>>> sampling, units =  0.05, 'nm'
>>> tml.plot_polarisation_vectors(x, y, u, v, image=atom_lattice.image,
...                           sampling=sampling, units=units,
...                           unit_vector=False, save=None, scalebar=True,
...                           plot_style='vector', color='r',
...                           overlay=True, monitor_dpi=45)
_images/basic_vectors_polar_dd.png

For nonstandard Polarised Structures (e.g., Boracites)

When the above function can’t isn’t suitable, the TEMUL find_polarisation_vectors function may be an option. It is useful for structures that Atomap’s get_polarization_from_second_sublattice can’t handle. It is a little more involved and requires some extra preparation when creating the sublattices.

See the second section of this tutorial on how to plot this in many different ways using plot_polarisation_vectors!

>>> import temul.api as tml
>>> import atomap.api as am
>>> import numpy as np
>>> from temul.dummy_data import get_polarisation_dummy_dataset_bora
>>> signal = get_polarisation_dummy_dataset_bora(True).signal
>>> atom_positions = am.get_atom_positions(signal, separation=7)
>>> sublatticeA = am.Sublattice(atom_positions, image=signal.data)
>>> sublatticeA.find_nearest_neighbors()
>>> sublatticeA.refine_atom_positions_using_center_of_mass()
>>> sublatticeA.construct_zone_axes()
>>> zone_axis_001 = sublatticeA.zones_axis_average_distances[0]
>>> atom_positions2 = sublatticeA.find_missing_atoms_from_zone_vector(
...     zone_axis_001, vector_fraction=0.5)
>>> sublatticeB = am.Sublattice(atom_positions2, image=signal.data,
...                             color='blue')
>>> sublatticeB.find_nearest_neighbors()
>>> sublatticeB.refine_atom_positions_using_center_of_mass(percent_to_nn=0.2)
>>> atom_positions2_refined = np.array([sublatticeB.x_position,
...                                     sublatticeB.y_position]).T
>>> atom_positions2 = np.asarray(atom_positions2).T

We then use the original (ideal) positions “atom_positions2” and the refined positions “atom_positions2_refined” to calculate and visualise the polarisation in the structure. Don’t forget to save these arrays for further use!

>>> u, v = tml.find_polarisation_vectors(atom_positions2,
...                                      atom_positions2_refined)
>>> x, y = sublatticeB.x_position.tolist(), sublatticeB.y_position.tolist()
>>> sampling, units =  0.1, 'nm'
>>> tml.plot_polarisation_vectors(x, y, u, v, image=signal.data,
...                         sampling=sampling, units=units, scalebar=True,
...                         unit_vector=False, save=None,
...                         plot_style='vector', color='r',
...                         overlay=True, monitor_dpi=45)
_images/find_vectors_polar_dd.png

For single Polarised Sublattices (e.g., LNO)

When dealing with structures in which the polarisation must be extracted from a single sublattice (one type of chemical atomic column, the TEMUL atom_deviation_from_straight_line_fit function may be an option. It is based off the description by J. Gonnissen et al, Direct Observation of Ferroelectric Domain Walls in LiNbO3: Wall‐Meanders, Kinks, and Local Electric Charges, 26, 42, 2016, DOI: 10.1002/adfm.201603489.

See the second section of this tutorial on how to plot this in many different ways using plot_polarisation_vectors!

>>> import temul.api as tml
>>> import temul.dummy_data as dd
>>> sublattice = dd.get_polarised_single_sublattice()
>>> sublattice.construct_zone_axes(atom_plane_tolerance=1)
>>> sublattice.plot_planes()

Choose `n`: how many atom columns should be used to fit the line on each
side of the atom planes. If `n` is too large, the fitting will appear
incorrect.

>>> n = 5
>>> x, y, u, v = tml.atom_deviation_from_straight_line_fit(
...     sublattice, 0, n)
>>> tml.plot_polarisation_vectors(x, y, u, v, image=sublattice.image,
...                               unit_vector=False, save=None,
...                               plot_style='vector', color='r',
...                               overlay=True, monitor_dpi=50)
_images/deviation_zone_0.png _images/deviation_vectors_polar_dd.png

Let’s look at some rotated data

>>> sublattice = dd.get_polarised_single_sublattice_rotated(
...     image_noise=True, rotation=45)
>>> sublattice.construct_zone_axes(atom_plane_tolerance=0.9)
>>> sublattice.plot_planes()
>>> n = 3  # plot the sublattice to see why 3 is suitable here!
>>> x, y, u, v = tml.atom_deviation_from_straight_line_fit(
...     sublattice, 0, n)
>>> tml.plot_polarisation_vectors(x, y, u, v, image=sublattice.image,
...                       vector_rep='angle', save=None, degrees=True,
...                       plot_style='colormap', cmap='cet_coolwarm',
...                       overlay=True, monitor_dpi=50)
_images/deviation_zone_0_rot.png _images/deviation_vectors_polar_dd_rot.png

Plotting Polarisation and Movement Vectors

The temul.polarisation module allows one to visualise the polarisation/movement of atoms in an atomic resolution image. In this tutorial, we will use a dummy dataset to show the different ways the plot_polarisation_vectors function can display data. In future, tutorials on published experimental data will also be available.

To go through the below examples in a live Jupyter Notebook session, click the button below and choose “code_tutorials/polarisation_vectors_tutorial.ipynb” (it may take a few minutes to load).

https://mybinder.org/badge_logo.svg

Prepare and Plot the dummy dataset

>>> from temul.polarisation import plot_polarisation_vectors
>>> from temul.dummy_data import get_polarisation_dummy_dataset
>>> atom_lattice = get_polarisation_dummy_dataset(image_noise=True)
>>> sublatticeA = atom_lattice.sublattice_list[0]
>>> sublatticeB = atom_lattice.sublattice_list[1]
>>> image = sublatticeA.signal
>>> image.plot()
_images/image_uncalibrated.png

It is best when the image is calibrated. Your image may already be calibrated, but if not, use Hyperspy’s axes_manager for calibration.

>>> sampling = 0.1  # example of 0.1 nm/pix
>>> units = 'nm'
>>> image.axes_manager[-1].scale = sampling
>>> image.axes_manager[-2].scale = sampling
>>> image.axes_manager[-1].units = units
>>> image.axes_manager[-2].units = units
>>> image.plot()
_images/image_calibrated.png

Zoom in on the image to see how the atoms look in the different regions.

_images/image_calibrated_labelled_atoms.png

Find the Vector Coordinates using Atomap

Using the Atomap package, we can easily get the polarisation vectors for regular structures.

>>> sublatticeA.construct_zone_axes()
>>> za0, za1 = sublatticeA.zones_axis_average_distances[0:2]
>>> s_p = sublatticeA.get_polarization_from_second_sublattice(
...     za0, za1, sublatticeB, color='blue')
>>> vector_list = s_p.metadata.vector_list
>>> x, y = [i[0] for i in vector_list], [i[1] for i in vector_list]
>>> u, v = [i[2] for i in vector_list], [i[3] for i in vector_list]

Now we can display all of the variations that plot_polarisation_vectors gives us! You can specify sampling (scale) and units, or use a calibrated image so that they are automatically set.

Vector magnitude plot with red arrows:

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=False, save=None,
...                           plot_style='vector', color='r',
...                           overlay=False, title='Vector Arrows',
...                           monitor_dpi=50)
_images/vectors_red.png

Vector magnitude plot with red arrows overlaid on the image, no title:

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=False, save=None,
...                           plot_style='vector', color='r',
...                           overlay=True, monitor_dpi=50)
_images/vectors_red_overlay.png

Vector magnitude plot with colormap viridis:

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=False, save=None,
...                           plot_style='colormap', monitor_dpi=50,
...                           overlay=False, cmap='viridis')
_images/colormap_magnitude.png

Vector angle plot with colormap viridis (vector_rep=’angle’):

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=False, save=None,
...                           plot_style='colormap', monitor_dpi=50,
...                           overlay=False, cmap='cet_colorwheel',
...                           vector_rep="angle", degrees=True)
_images/colormap_angle.png

Colormap arrows with sampling specified in the parameters and with scalebar:

>>> plot_polarisation_vectors(x, y, u, v, image=sublatticeA.image,
...                           sampling=3.0321, units='pm', monitor_dpi=50,
...                           unit_vector=False, plot_style='colormap',
...                           overlay=True, save=None, cmap='viridis',
...                           scalebar=True)
_images/colormap_magnitude_overlay_sb_pm.png

Vector plot with colormap viridis and unit vectors:

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=True, save=None, monitor_dpi=50,
...                           plot_style='colormap', color='r',
...                           overlay=False, cmap='viridis')
_images/colormap_unitvectors.png

Change the vectors to unit vectors on a Matplotlib tricontourf map:

>>> plot_polarisation_vectors(x, y, u, v, image=image, unit_vector=True,
...                           plot_style='contour', overlay=False,
...                           pivot='middle', save=None, monitor_dpi=50,
...                           color='darkgray', cmap='viridis')
_images/contour_magnitude_unitvectors.png

Plot a partly transparent angle tricontourf map with specified colorbar ticks and vector arrows:

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=False, plot_style='contour',
...                           overlay=True, pivot='middle', save=None,
...                           color='red', cmap='cet_colorwheel',
...                           monitor_dpi=50, remove_vectors=False,
...                           vector_rep="angle", alpha=0.5, levels=9,
...                           antialiased=True, degrees=True,
...                           ticks=[180, 90, 0, -90, -180])
_images/contour_angle_trans_overlay_vectors.png

Plot a partly transparent angle tricontourf map with no vector arrows:

>>> plot_polarisation_vectors(x, y, u, v, image=image, remove_vectors=True,
...                           unit_vector=True, plot_style='contour',
...                           overlay=True, pivot='middle', save=None,
...                           cmap='cet_colorwheel', alpha=0.5,
...                           monitor_dpi=50, vector_rep="angle",
...                           antialiased=True, degrees=True)
_images/contour_angle_trans_overlay.png

“colorwheel” plot of the vectors, useful for visualising vortexes:

>>> import colorcet as cc  # can also just use cmap="cet_colorwheel"
>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=True, plot_style="colorwheel",
...                           vector_rep="angle",
...                           overlay=False, cmap=cc.cm.colorwheel,
...                           degrees=True, save=None, monitor_dpi=50)
_images/colorwheel_angle.png

“polar_colorwheel” plot showing a 2D polar color wheel, also useful for vortexes:

>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           plot_style="polar_colorwheel",
...                           unit_vector=False, overlay=False,
...                           save=None, monitor_dpi=50)

# This plot may show the effect of the second dimension more clearly.
# Example taken from Matplotlib's Quiver documentation.
>>> import numpy as np
>>> X, Y = np.meshgrid(np.arange(0, 2 * np.pi, .2), np.arange(0, 2 * np.pi, .2))
>>> image_temp = np.ones_like(X)
>>> U = np.reshape(np.cos(X), 1024)
>>> V = np.reshape(np.sin(Y), 1024)
>>> X, Y = np.reshape(X, 1024), np.reshape(Y, 1024)
>>> ax = plot_polarisation_vectors(X, Y, U, -V, image=image_temp,
...                           plot_style="polar_colorwheel",
...                           overlay=False, invert_y_axis=False,
...                           save=None, monitor_dpi=None)
>>> ax.invert_yaxis()
_images/colorwheel_polar.png _images/quiver_2d_colorwheel.png

Plot with a custom scalebar. In this example, we need it to be dark, see matplotlib-scalebar for more custom features.

>>> scbar_dict = {"dx": 3.0321, "units": "pm", "location": "lower left",
...               "box_alpha":0.0, "color": "black", "scale_loc": "top"}
>>> plot_polarisation_vectors(x, y, u, v, image=sublatticeA.image,
...                           sampling=3.0321, units='pm', monitor_dpi=50,
...                           unit_vector=False, plot_style='colormap',
...                           overlay=False, save=None, cmap='viridis',
...                           scalebar=scbar_dict)
_images/colormap_magnitude_custom_sb.png

Plot a tricontourf for quadrant visualisation using a custom matplotlib cmap:

>>> import temul.signal_plotting as tmlplot
>>> from matplotlib.colors import from_levels_and_colors
>>> zest = tmlplot.hex_to_rgb(tmlplot.color_palettes('zesty'))
>>> zest.append(zest[0])  # make the -180 and 180 degree colour the same
>>> expanded_zest = tmlplot.expand_palette(zest, [1,2,2,2,1])
>>> custom_cmap, _ = from_levels_and_colors(
...     levels=range(9), colors=tmlplot.rgb_to_dec(expanded_zest))
>>> plot_polarisation_vectors(x, y, u, v, image=image,
...                           unit_vector=False, plot_style='contour',
...                           overlay=False, pivot='middle', save=None,
...                           cmap=custom_cmap, levels=9, monitor_dpi=50,
...                           vector_rep="angle", alpha=0.5, color='r',
...                           antialiased=True, degrees=True,
...                           ticks=[180, 90, 0, -90, -180])
_images/contour_angle_custom_cmap_vectors.png