Plotting for publications

Please consider citing Atomap: a new software tool for the automated analysis of atomic resolution images using two-dimensional Gaussian fitting if you publish work where you have used Atomap as a tool.

Figures for publications are often customized, and here are a few tips on how to extract the data you wish to plot in a fancy plot.

Saving specific data

When making advanced figures containing specific data for publication, it can be a good idea to save this data for example in separate numpy files. This makes it quick to load the data when using for example matplotlib to make figures.

>>> import numpy as np
>>> np.savez("datafile.npz", x=sublattice_A.x_position, y=sublattice_A.y_position, e=sublattice_A.ellipticity) 

Alternatively, the data can be saved in comma-separated values (CSV) file, which can be opened in spreadsheet software:

>>> np.savetxt("datafile.csv", (sublattice_A.x_position, sublattice_A.y_position, sublattice_A.sigma_x, sublattice_A.sigma_y, sublattice_A.ellipticity), delimiter=',') 

Signals can be saved by using the inbuilt save function.

>>> s_monolayer.save("monolayer_distances.hdf5", overwrite=True) 

Here, we will first save analysis data for the fantasite dummy data atom lattice. It can be a good idea to save analysis results as numpy, csv or hyperspy signals, as the runtime of the analysis can sometimes be lengthy. Making nice figures often require a lot of tweaking, trial and error, so it is nice to have the data readily available without having to re-run the full analysis. First, the analysis results are generated, and then saved.

import os
import numpy as np
from hyperspy.signals import Signal2D
from atomap.dummy_data import get_fantasite_atom_lattice

my_path = os.path.join(os.path.dirname(__file__), 'make_nice_figures')
if not os.path.exists(my_path):
    os.makedirs(my_path)

# First we will analyse and save the structural data of interest
# Here, we use the fantasite atom_lattice dummy data
atom_lattice = get_fantasite_atom_lattice()

# Saving atom positions and ellipticity
sublattice_A = atom_lattice.sublattice_list[0]
np.savez(
        os.path.join(my_path, 'sublattice_A.npz'), x=sublattice_A.x_position,
        y=sublattice_A.y_position, e=sublattice_A.ellipticity)
sublattice_B = atom_lattice.sublattice_list[1]
np.savez(
        os.path.join(my_path, 'sublattice_B.npz'), x=sublattice_B.x_position,
        y=sublattice_B.y_position, e=sublattice_B.ellipticity)

# Saving distance difference map
sublattice_A.construct_zone_axes()
zone = sublattice_A.zones_axis_average_distances[0]
s_dd = sublattice_A.get_atom_distance_difference_map([zone])
s_dd.save(os.path.join(my_path, 'distance_difference_map.hspy'),
          overwrite=True)

# Saving the synthetic ADF-image.
im = atom_lattice.image
s_adf = Signal2D(im)
s_adf.save(os.path.join(my_path, 'ADF_image.hspy'), overwrite=True)

# Saving the line profile
z1 = sublattice_A.zones_axis_average_distances[0]
z2 = sublattice_A.zones_axis_average_distances[1]
plane = sublattice_A.atom_planes_by_zone_vector[z2][23]
s_dd_line = sublattice_A.get_atom_distance_difference_line_profile(z1, plane)
s_dd_line.save(os.path.join(my_path, 'dd_line.hspy'), overwrite=True)

Matplotlib

The saved results can then be loaded into the script that is making nice figures. The below code block will create this figure.

_images/Atom_lattice.png
import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar
import matplotlib.font_manager as fm
import matplotlib.patheffects as patheffects
import hyperspy.api as hs

my_path = os.path.join(os.path.dirname(__file__), 'make_nice_figures')
if not os.path.exists(my_path):
    os.makedirs(my_path)

# Load the atomic resolution image
s_adf = hs.load(os.path.join(my_path, 'ADF_image.hspy'))

# Load the structural data
atoms_A = np.load(os.path.join(my_path, 'sublattice_A.npz'))
atoms_B = np.load(os.path.join(my_path, 'sublattice_B.npz'))
dd_map = hs.load(os.path.join(my_path, 'distance_difference_map.hspy'))
dd_line = hs.load(os.path.join(my_path, 'dd_line.hspy'))

# Scaling the data
scale = 0.142
s_adf.axes_manager[0].scale = scale
s_adf.axes_manager[1].scale = scale
# dd_map has twice the amount of pixels, so the scale is half
dd_map.axes_manager[0].scale = scale/2
dd_map.axes_manager[1].scale = scale/2

# Crop images
s_adf = s_adf.isig[40:460, 40:460]
dd_map = dd_map.isig[80:920, 80:920]

# Make a figure with 3 sub-figures, of difference sizes
fig = plt.figure(figsize=(4.3, 2))  # in inches
gs = gridspec.GridSpec(1, 5)
ax_adf = plt.subplot(gs[:2])
ax_al = plt.subplot(gs[2:4])
ax_lp = plt.subplot(gs[4])

# Plot ADF-image
cax_adf = ax_adf.imshow(
        np.rot90(s_adf.data), interpolation='nearest',
        origin='upper', extent=s_adf.axes_manager.signal_extent)

# Make scalebar on ADF-image
fontprops = fm.FontProperties(size=12)
scalebar0 = AnchoredSizeBar(
        ax_adf.transData,
        20, '2 nm', 4,
        pad=0.1,
        color='white',
        frameon=False,
        label_top=True,
        size_vertical=2,
        fontproperties=fontprops)
# The next line is needed due to a bug in matplotlib 2.0
scalebar0.size_bar.get_children()[0].fill = True
ax_adf.add_artist(scalebar0)

# Add markers for atom positions
for idx, x in enumerate(atoms_A['x']):
    y = atoms_A['y'][idx]
    if (240 < x < 350) and (96 < y < 200):
        ax_adf.scatter(y*scale, x*scale, color='r', s=0.5)

for idx, x in enumerate(atoms_B['x']):
    y = atoms_B['y'][idx]
    if (240 < x < 350) and (96 < y < 200):
        ax_adf.scatter(y*scale, x*scale, color='b', s=0.5)

# Plot distance difference map
cax_al = ax_al.imshow(
        np.rot90(dd_map.data),
        interpolation='nearest',
        origin='upper',
        extent=dd_map.axes_manager.signal_extent,
        cmap='viridis')

scalebar1 = AnchoredSizeBar(
        ax_al.transData,
        20, '2 nm', 4,
        pad=0.1,
        color='white',
        frameon=False,
        label_top=True,
        size_vertical=2,
        fontproperties=fontprops)
# The next line is needed due to a bug in matplotlib 2.0
scalebar1.size_bar.get_children()[0].fill = True
ax_al.add_artist(scalebar1)

# Remove ticks for images
for ax in [ax_adf, ax_al]:
    ax.set_xticks([])
    ax.set_yticks([])

# Plot line profile
x_line_profile = dd_line.metadata.line_profile_data.x_list*scale/10
y_line_profile = dd_line.metadata.line_profile_data.y_list*scale
ax_lp.plot(y_line_profile, x_line_profile)
ax_lp.set_xlabel("Distance difference, [Å]", fontsize=7)
ax_lp.set_ylabel("Distance from interface, [nm]", fontsize=7)
ax_lp.tick_params(axis='both', which='major', labelsize=6)
ax_lp.tick_params(axis='both', which='minor', labelsize=6)
ax_lp.yaxis.set_label_position('right')
ax_lp.yaxis.set_ticks_position('right')
ax_lp.set_ylim(-1.5, 4.5)

# Add annotation
path_effects = [
        patheffects.withStroke(linewidth=2, foreground='black',
                               capstyle="round")]
ax_adf.text(
        0.015, 0.90, "a", fontsize=12, color='white',
        path_effects=path_effects,
        transform=ax_adf.transAxes)
ax_al.text(
        0.015, 0.90, "b", fontsize=12, color='white',
        path_effects=path_effects,
        transform=ax_al.transAxes)
ax_lp.text(
        0.05, 0.90, "c", fontsize=12, color='w',
        path_effects=path_effects,
        transform=ax_lp.transAxes)

# Adjust space between subplots and margins
gs.update(left=0.01, wspace=0.05, top=0.95, bottom=0.2, right=0.89)

# Save
fig.savefig(os.path.join(my_path, 'Atom_lattice.png'), dpi=300)