import numpy as np
import skimage.restoration as skr
import scipy.ndimage as scnd
import matplotlib as mpl
import matplotlib.pyplot as plt
import stemtool as st
import matplotlib.offsetbox as mploff
import matplotlib.gridspec as mpgs
import matplotlib_scalebar.scalebar as mpss
import numba
[docs]def phase_diff(angle_image):
"""
Differentiate a complex phase image while
ensuring that phase wrapping doesn't
distort the differentiation.
Parameters
----------
angle_image: ndarray
Wrapped phase image
Returns
-------
diff_x: ndarray
X differential of the phase image
diff_y: ndarray
Y differential of the phase image
Notes
-----
The basic idea of this is that we differentiate the
complex exponential of the phase image, and then obtain the
differentiation result by multiplying the differential with
the conjugate of the complex phase image.
Reference
---------
.. [1] Hÿtch, M. J., E. Snoeck, and R. Kilaas. "Quantitative measurement
of displacement and strain fields from HREM micrographs."
Ultramicroscopy 74.3 (1998): 131-146.
"""
imaginary_image = np.exp(1j * angle_image)
diff_imaginary_x = np.zeros(imaginary_image.shape, dtype="complex_")
diff_imaginary_x[:, 0:-1] = np.diff(imaginary_image, axis=1)
diff_imaginary_y = np.zeros(imaginary_image.shape, dtype="complex_")
diff_imaginary_y[0:-1, :] = np.diff(imaginary_image, axis=0)
conjugate_imaginary = np.conj(imaginary_image)
diff_complex_x = np.multiply(conjugate_imaginary, diff_imaginary_x)
diff_complex_y = np.multiply(conjugate_imaginary, diff_imaginary_y)
diff_x = np.imag(diff_complex_x)
diff_y = np.imag(diff_complex_y)
return diff_x, diff_y
[docs]def phase_subtract(matrix_1, matrix_2):
"""
Subtract one complex phase image from
another without causing phase wrapping.
Parameters
----------
matrix_1: ndarray
First phase image
matrix_2: ndarray
Second phase image
Returns
-------
: ndarray
Difference of the phase images
Notes
-----
The basic idea of this is that we subtract the
phase images from each other, then transform
that to a complex phase, and take the angle of
the complex image.
"""
return np.angle(np.exp(1j * (matrix_1 - matrix_2)))
[docs]def circ_to_G(circ_pos, image):
"""
Convert a pixel position to g vectors in
Fourier space.
Parameters
----------
circ_pos: tuple
First phase image
image: ndarray
The image matrix
Returns
-------
g_vec: ndarray
Shape is (2, 1) which is the
corresponding g-vector in inverse pixels
See Also
--------
G_to_circ
"""
g_vec = np.zeros(2)
g_vec[0:2] = np.divide(np.flip(np.asarray(circ_pos)), np.asarray(image.shape)) - 0.5
return g_vec
[docs]def G_to_circ(g_vec, image):
"""
Convert g vectors in Fourier space to
pixel positions in real space.
Parameters
----------
g_vec: ndarray
Shape is (2, 1) which is the G
vector in Fourier space in inverse pixels
image: ndarray
The image matrix
Returns
-------
circ_pos: ndarray
Shape is (2, 1) which is the
corresponding pixel position in
real space.
See Also
--------
circ_to_G
"""
circ_pos = np.zeros(2)
circ_pos[1] = (g_vec[0] * image.shape[0]) + (0.5 * image.shape[0])
circ_pos[0] = (g_vec[1] * image.shape[1]) + (0.5 * image.shape[1])
return circ_pos
[docs]def g_matrix(g_vector, image):
"""
Multiply g vector with Fourier coordinates
to generate a corresponding phase matrix
Parameters
----------
g_vec: ndarray
Shape is (2, 1) which is the G
vector in Fourier space in inverse pixels
image: ndarray
The image matrix
Returns
-------
G_r: ndarray
Same size as the image originally
and gives the phase map for a given
g vector
"""
r_y = np.arange(start=-image.shape[0] / 2, stop=image.shape[0] / 2, step=1)
r_x = np.arange(start=-image.shape[1] / 2, stop=image.shape[1] / 2, step=1)
R_x, R_y = np.meshgrid(r_x, r_y)
G_r = 2 * np.pi * ((R_x * g_vector[1]) + (R_y * g_vector[0]))
return G_r
[docs]def phase_matrix(gvec, image, circ_size=0, g_blur=True):
"""
Use the g vector in Fourier coordinates
to select only the subset of phases
associated with that diffraction spot,
a.k.a. the lattice parameter.
Parameters
----------
g_vec: ndarray
Shape is (2, 1) which is the G
vector in Fourier space in inverse pixels
image: ndarray
The image matrix
circ_size: float, optional
Size of the circle in pixels
g_blur: bool, optional
Returns
-------
P_matrix: ndarray
Same size as the image originally
and gives a real space phase matrix
for a given real image and a g vector
Notes
-----
We put an aperture around a single diffraction
spot, given by the g vector that generates the
phase matrix associated with that diffraction
spot. If the g vector is already refined, then
in the reference region, the difference between
this phase matrix and that given by `g_matrix`
should be zero.
See Also
--------
g_matrix
"""
imshape = np.asarray(np.shape(image))
if circ_size == 0:
circ_rad = np.amin(0.01 * np.asarray(imshape))
else:
circ_rad = circ_size
yy, xx = np.mgrid[0 : imshape[0], 0 : imshape[1]]
circ_pos = np.multiply(np.flip(gvec), imshape) + (0.5 * imshape)
circ_mask = (
st.util.make_circle(imshape, circ_pos[0], circ_pos[1], circ_rad)
).astype(bool)
ham = np.sqrt(np.outer(np.hamming(imshape[0]), np.hamming(imshape[1])))
if g_blur:
sigma2 = np.sum((0.5 * gvec * imshape) ** 2)
zz = (
((yy[circ_mask] - circ_pos[1]) ** 2) + ((xx[circ_mask] - circ_pos[0]) ** 2)
) / sigma2
four_mask = np.zeros_like(yy, dtype=np.float)
four_mask[circ_mask] = np.exp((-0.5) * zz)
P_matrix = np.angle(
np.fft.ifft2(four_mask * np.fft.fftshift(np.fft.fft2(image * ham)))
)
else:
P_matrix = np.angle(
np.fft.ifft2(circ_mask * np.fft.fftshift(np.fft.fft2(image * ham)))
)
return P_matrix
@numba.jit(cache=True, parallel=True)
def numba_strain_P(P_1, P_2, a_matrix):
"""
Use the refined phase matrices and lattice matrix
to calculate the strain matrices.
Parameters
----------
P_1: ndarray
Refined Phase matrix from first lattice spot
P_2: ndarray
Refined Phase matrix from first lattice spot
a_matrix: ndarray
ndarray of shape (2, 2) that represents
the lattice parameters in real space
Returns
-------
e_xx: ndarray
Strain along X direction
e_yy: ndarray
Strain along Y direction
e_th: ndarray
Rotational strain
e_dg: ndarray
Diagonal Strain
Notes
-----
This is a numba accelerated JIT compiled
version of the method `gen_strain()` in the
where a for loop is used to refine the strain
at every pixel position.
See Also
--------
phase_diff
GPA.gen_strain()
"""
P1_x, P1_y = phase_diff(P_1)
P2_x, P2_y = phase_diff(P_2)
P_shape = np.shape(P_1)
yy, xx = np.mgrid[0 : P_shape[0], 0 : P_shape[1]]
yy = np.ravel(yy)
xx = np.ravel(xx)
P_mat = np.zeros((2, 2), dtype=np.float)
e_xx = np.zeros_like(P_1)
e_xy = np.zeros_like(P_1)
e_yx = np.zeros_like(P_1)
e_yy = np.zeros_like(P_1)
for ii in range(len(yy)):
ypos = yy[ii]
xpos = xx[ii]
P_mat[0, 0] = P1_x[ypos, xpos]
P_mat[0, 1] = P1_y[ypos, xpos]
P_mat[1, 0] = P2_x[ypos, xpos]
P_mat[1, 1] = P2_y[ypos, xpos]
e_mat = ((1) / (2 * np.pi)) * np.matmul(a_matrix, P_mat)
e_xx[ypos, xpos] = e_mat[0, 0]
e_xy[ypos, xpos] = e_mat[0, 1]
e_yx[ypos, xpos] = e_mat[1, 0]
e_yy[ypos, xpos] = e_mat[1, 1]
e_th = 0.5 * (e_xy - e_yx)
e_dg = 0.5 * (e_xy + e_yx)
return e_xx, e_yy, e_th, e_dg
[docs]class GPA(object):
"""
Use Geometric Phase Analysis (GPA) to measure strain in an
electron micrograph by locating the diffraction spots and
identifying a reference region
Parameters
----------
image: ndarray
The image from which the strain will
be measured from
calib: float
Size of an individual pixel
calib_units: str
Unit of calibration
ref_iter: int, optional
Number of iterations to run for refining
the G vectors and the phase matrixes.
Default is 20.
use_blur: bool, optional
Use a Gaussian blur to generate the
phase matrix from a g vector. Default is True
References
----------
.. [1] Hÿtch, M. J., E. Snoeck, and R. Kilaas. "Quantitative measurement
of displacement and strain fields from HREM micrographs."
Ultramicroscopy 74.3 (1998): 131-146.
Examples
--------
Run as:
>>> im_gpa = gpa(image=imageDC, calib=calib1, calib_units= calib1_units)
Then to check the image you just loaded
>>> im_gpa.show_image()
Then, select the diffraction spots in inverse units that you
want to be used for GPA. They must not be collinear.
>>> im_gpa.find_spots((5, 0), (0, -5))
where (5, 0) and (0, -5) are two diffraction spot
locations. You can run the `find_spots` method manually
multiple times till you locate the spots closely. After
you have located the spots, you need to define a reference
region for the image - with respect to which the strain
will be calculated.
>>> im_gpa.define_reference((6.8, 6.9), (10.1, 6.8), (10.2, 9.5), (7.0, 9.6))
where (6.8, 6.9), (10.1, 6.8), (10.2, 9.5) and (7.0, 9.6) are
the corners of the reference region you are defining.
>>> im_gpa.refine_phase()
>>> e_xx, e_yy, e_theta, e_diag = im_gpa.get_strain()
To plot the obtained strain maps:
>>> im_gpa.plot_gpa_strain()
"""
def __init__(
self, image, calib, calib_units, ref_iter=20, use_blur=True, max_strain=0.4
):
self.image = image
self.calib = calib
self.calib_units = calib_units
self.blur = use_blur
self.ref_iter = int(ref_iter)
self.imshape = np.asarray(image.shape)
inv_len = 1 / (self.calib * self.imshape)
if inv_len[0] == inv_len[1]:
self.inv_calib = np.mean(inv_len)
else:
raise RuntimeError("Please ensure that the image is a square image")
self.circ_0 = 0.5 * self.imshape
self.inv_cal_units = "1/" + calib_units
self.max_strain = max_strain
self.spots_check = False
self.reference_check = False
self.refining_check = False
[docs] def show_image(self, imsize=(15, 15), colormap="inferno"):
"""
Parameters
----------
imsize: tuple, optional
Size in inches of the image with the
diffraction spots marked. Default is (15, 15)
colormap: str, optional
Colormap of the image. Default is inferno
"""
plt.figure(figsize=imsize)
plt.imshow(self.image, cmap=colormap)
scalebar = mpss.ScaleBar(self.calib, self.calib_units)
scalebar.location = "lower right"
scalebar.box_alpha = 1
scalebar.color = "k"
plt.gca().add_artist(scalebar)
plt.axis("off")
[docs] def find_spots(self, circ1, circ2, circ_size=15, imsize=(10, 10)):
"""
Locate the diffraction spots visually.
Parameters
----------
circ1: ndarray
Position of the first beam in
the Fourier pattern
circ2: ndarray
Position of the second beam in
the Fourier pattern
circ_size: float
Size of the circle in pixels
imsize: tuple, optional
Size in inches of the image with the
diffraction spots marked. Default is
(10, 10)
Notes
-----
Put circles in red(central), y(blue) and x(green)
on the diffraction pattern to approximately know
the positions. We also convert the circle locations
to G vectors by calling the static method `circ_to_G`.
We use the G vector locations to also generate the
initial phase matrices.
See Also
--------
circ_to_G
phase_matrix
"""
self.circ_1 = (self.imshape / 2) + (np.asarray(circ1) / self.inv_calib)
self.circ_2 = (self.imshape / 2) + (np.asarray(circ2) / self.inv_calib)
self.circ_size = circ_size
self.ham = np.sqrt(
np.outer(np.hamming(self.imshape[0]), np.hamming(self.imshape[1]))
)
self.image_ft = np.fft.fftshift(np.fft.fft2(self.image * self.ham))
log_abs_ft = scnd.filters.gaussian_filter(np.log10(np.abs(self.image_ft)), 3)
pixel_list = np.arange(
-0.5 * self.inv_calib * self.imshape[0],
0.5 * self.inv_calib * self.imshape[0],
self.inv_calib,
)
no_labels = 9
step_x = int(self.imshape[0] / (no_labels - 1))
x_positions = np.arange(0, self.imshape[0], step_x)
x_labels = np.round(pixel_list[::step_x], 1)
_, ax = plt.subplots(figsize=imsize)
circ_0_im = plt.Circle(self.circ_0, self.circ_size, color="red", alpha=0.75)
circ_1_im = plt.Circle(self.circ_1, self.circ_size, color="blue", alpha=0.75)
circ_2_im = plt.Circle(self.circ_2, self.circ_size, color="green", alpha=0.75)
ax.imshow(log_abs_ft, cmap="gray")
ax.add_artist(circ_0_im)
ax.add_artist(circ_1_im)
ax.add_artist(circ_2_im)
plt.xticks(x_positions, x_labels)
plt.yticks(x_positions, x_labels)
plt.xlabel("Distance along X-axis (" + self.inv_cal_units + ")")
plt.ylabel("Distance along Y-axis (" + self.inv_cal_units + ")")
self.gvec_1_ini = st.gpa.circ_to_G(self.circ_1, self.image)
self.gvec_2_ini = st.gpa.circ_to_G(self.circ_2, self.image)
self.P_matrix1_ini = st.gpa.phase_matrix(
self.gvec_1_ini, self.image, self.circ_size, self.blur
)
self.P_matrix2_ini = st.gpa.phase_matrix(
self.gvec_2_ini, self.image, self.circ_size, self.blur
)
self.spots_check = True
[docs] def define_reference(self, A_pt, B_pt, C_pt, D_pt, imsize=(10, 10), tColor="k"):
"""
Locate the reference image.
Parameters
----------
A_pt: tuple
Top left position of reference region in (x, y)
B_pt: tuple
Top right position of reference region in (x, y)
C_pt: tuple
Bottom right position of reference region in (x, y)
D_pt: tuple
Bottom left position of reference region in (x, y)
imsize: tuple, optional
Size in inches of the image with the
diffraction spots marked. Default is
(10, 10)
tColor: str, optional
Color of the text on the image
Notes
-----
Locates a reference region bounded by the four points given in
length units. Choose the points in a clockwise fashion.
"""
if not self.spots_check:
raise RuntimeError(
"Please locate the diffraction spots first as find_spots()"
)
A = np.asarray(A_pt) / self.calib
B = np.asarray(B_pt) / self.calib
C = np.asarray(C_pt) / self.calib
D = np.asarray(D_pt) / self.calib
yy, xx = np.mgrid[0 : self.imshape[0], 0 : self.imshape[1]]
yy = np.ravel(yy)
xx = np.ravel(xx)
ptAA = np.asarray((xx, yy)).transpose() - A
ptBB = np.asarray((xx, yy)).transpose() - B
ptCC = np.asarray((xx, yy)).transpose() - C
ptDD = np.asarray((xx, yy)).transpose() - D
angAABB = np.arccos(
np.sum(ptAA * ptBB, axis=1)
/ (
((np.sum(ptAA ** 2, axis=1)) ** 0.5)
* ((np.sum(ptBB ** 2, axis=1)) ** 0.5)
)
)
angBBCC = np.arccos(
np.sum(ptBB * ptCC, axis=1)
/ (
((np.sum(ptBB ** 2, axis=1)) ** 0.5)
* ((np.sum(ptCC ** 2, axis=1)) ** 0.5)
)
)
angCCDD = np.arccos(
np.sum(ptCC * ptDD, axis=1)
/ (
((np.sum(ptCC ** 2, axis=1)) ** 0.5)
* ((np.sum(ptDD ** 2, axis=1)) ** 0.5)
)
)
angDDAA = np.arccos(
np.sum(ptDD * ptAA, axis=1)
/ (
((np.sum(ptDD ** 2, axis=1)) ** 0.5)
* ((np.sum(ptAA ** 2, axis=1)) ** 0.5)
)
)
angsum = ((angAABB + angBBCC + angCCDD + angDDAA) / (2 * np.pi)).reshape(
self.image.shape
)
self.ref_reg = np.isclose(angsum, 1)
self.ref_reg = np.flipud(self.ref_reg)
pixel_list = np.arange(0, self.calib * self.imshape[0], self.calib)
no_labels = 10
step_x = int(self.imshape[0] / (no_labels - 1))
x_positions = np.arange(0, self.imshape[0], step_x)
x_labels = np.round(pixel_list[::step_x], 1)
fsize = int(1.5 * np.mean(np.asarray(imsize)))
print(
"Choose your points in a clockwise fashion, or else you will get a wrong result"
)
plt.figure(figsize=imsize)
plt.imshow(
np.flipud(st.util.image_normalizer(self.image) + 0.33 * self.ref_reg),
cmap="magma",
origin="lower",
)
plt.annotate(
"A=" + str(A_pt),
A / self.imshape,
textcoords="axes fraction",
size=fsize,
color=tColor,
)
plt.annotate(
"B=" + str(B_pt),
B / self.imshape,
textcoords="axes fraction",
size=fsize,
color=tColor,
)
plt.annotate(
"C=" + str(C_pt),
C / self.imshape,
textcoords="axes fraction",
size=fsize,
color=tColor,
)
plt.annotate(
"D=" + str(D_pt),
D / self.imshape,
textcoords="axes fraction",
size=fsize,
color=tColor,
)
plt.scatter(A[0], A[1], c="r")
plt.scatter(B[0], B[1], c="r")
plt.scatter(C[0], C[1], c="r")
plt.scatter(D[0], D[1], c="r")
plt.xticks(x_positions, x_labels, fontsize=fsize)
plt.yticks(x_positions, x_labels, fontsize=fsize)
plt.xlabel("Distance along X-axis (" + self.calib_units + ")", fontsize=fsize)
plt.ylabel("Distance along Y-axis (" + self.calib_units + ")", fontsize=fsize)
self.reference_check = True
[docs] def refine_phase(self):
"""
Refine the phase matrices and the G vectors
from their initial values using the reference
region location.
Notes
-----
Iteratively refine the G vector and the phase matrices,
so that the phase variation in the reference region is
minimized.
See Also
--------
phase_diff
phase_matrix
"""
if not self.reference_check:
raise RuntimeError(
"Please locate the reference region first as define_reference()"
)
ry = np.arange(start=-self.imshape[0] / 2, stop=self.imshape[0] / 2, step=1)
rx = np.arange(start=-self.imshape[1] / 2, stop=self.imshape[1] / 2, step=1)
R_x, R_y = np.meshgrid(rx, ry)
self.gvec_1_fin = self.gvec_1_ini
self.gvec_2_fin = self.gvec_2_ini
self.P_matrix1_fin = self.P_matrix1_ini
self.P_matrix2_fin = self.P_matrix2_ini
for _ in range(int(self.ref_iter)):
G1_x, G1_y = st.gpa.phase_diff(self.P_matrix1_fin)
G2_x, G2_y = st.gpa.phase_diff(self.P_matrix2_fin)
g1_r = (G1_x + G1_y) / (2 * np.pi)
g2_r = (G2_x + G2_y) / (2 * np.pi)
del_g1 = np.asarray(
(
np.median(g1_r[self.ref_reg] / R_y[self.ref_reg]),
np.median(g1_r[self.ref_reg] / R_x[self.ref_reg]),
)
)
del_g2 = np.asarray(
(
np.median(g2_r[self.ref_reg] / R_y[self.ref_reg]),
np.median(g2_r[self.ref_reg] / R_x[self.ref_reg]),
)
)
self.gvec_1_fin += del_g1
self.gvec_2_fin += del_g2
self.P_matrix1_fin = st.gpa.phase_matrix(
self.gvec_1_fin, self.image, self.circ_size, self.blur
)
self.P_matrix2_fin = st.gpa.phase_matrix(
self.gvec_2_fin, self.image, self.circ_size, self.blur
)
self.refining_check = True
[docs] def get_strain(self):
"""
Use the refined phase matrix and g vectors to calculate
the strain matrices.
Returns
-------
e_xx: ndarray
Strain along X direction
e_yy: ndarray
Strain along Y direction
e_th: ndarray
Rotational strain
e_dg: ndarray
Diagonal Strain
Notes
-----
Use the refined G vectors to generate a matrix
of the lattice parameters, which is stored as the
class attribute `a_matrix`. This is multiplied by the
refined phase matrix, and the multiplicand is subsequently
differentiated to get the strain parameters.
See Also
--------
phase_diff
"""
if not self.reference_check:
raise RuntimeError(
"Please refine the phase and g vectors first as refine_phase()"
)
g_matrix = np.zeros((2, 2), dtype=np.float64)
g_matrix[0, :] = np.flip(np.asarray(self.gvec_1_fin))
g_matrix[1, :] = np.flip(np.asarray(self.gvec_2_fin))
self.a_matrix = np.linalg.inv(np.transpose(g_matrix))
P1 = skr.unwrap_phase(self.P_matrix1_fin)
P2 = skr.unwrap_phase(self.P_matrix1_fin)
rolled_p = np.asarray((np.reshape(P1, -1), np.reshape(P2, -1)))
u_matrix = np.matmul(self.a_matrix, rolled_p)
u_x = np.reshape(u_matrix[0, :], P1.shape)
u_y = np.reshape(u_matrix[1, :], P2.shape)
self.e_xx, e_xy = st.gpa.phase_diff(u_x)
e_yx, self.e_yy = st.gpa.phase_diff(u_y)
self.e_th = 0.5 * (e_xy - e_yx)
self.e_dg = 0.5 * (e_xy + e_yx)
self.e_yy -= np.median(self.e_yy[self.ref_reg])
self.e_dg -= np.median(self.e_dg[self.ref_reg])
self.e_th -= np.median(self.e_th[self.ref_reg])
self.e_xx -= np.median(self.e_xx[self.ref_reg])
if self.max_strain > 0:
self.e_yy[self.e_yy > self.max_strain] = self.max_strain
self.e_yy[self.e_yy < -self.max_strain] = -self.max_strain
self.e_dg[self.e_dg > self.max_strain] = self.max_strain
self.e_dg[self.e_dg < -self.max_strain] = -self.max_strain
self.e_th[self.e_th > self.max_strain] = self.max_strain
self.e_th[self.e_th < -self.max_strain] = -self.max_strain
self.e_xx[self.e_xx > self.max_strain] = self.max_strain
self.e_xx[self.e_xx < -self.max_strain] = -self.max_strain
return self.e_xx, self.e_yy, self.e_th, self.e_dg
[docs] def plot_gpa_strain(self, mval=0, imwidth=15):
"""
Use the calculated strain matrices to plot the strain maps
Parameters
----------
mval: float, optional
The maximum strain value that will be plotted.
Default is 0, upon which the maximum strain
percentage will be calculated, which will be used
for plotting.
imwidth: int, optional
Size in inches of the image with the
diffraction spots marked. Default is 15
Notes
-----
Uses `matplotlib.gridspec` to plot the strain maps of the
four types of strain calculated through geometric phase
analysis.
"""
fontsize = int(imwidth)
if mval == 0:
vm = 100 * np.amax(
np.abs(
np.concatenate((self.e_yy, self.e_xx, self.e_dg, self.e_th), axis=1)
)
)
else:
vm = mval
sc_font = {"weight": "bold", "size": fontsize}
mpl.rc("font", **sc_font)
imsize = (int(imwidth), int(imwidth * 1.1))
plt.figure(figsize=imsize)
gs = mpgs.GridSpec(11, 10)
ax1 = plt.subplot(gs[0:5, 0:5])
ax2 = plt.subplot(gs[0:5, 5:10])
ax3 = plt.subplot(gs[5:10, 0:5])
ax4 = plt.subplot(gs[5:10, 5:10])
ax5 = plt.subplot(gs[10:11, :])
ax1.imshow(-100 * self.e_xx, vmin=-vm, vmax=vm, cmap="RdBu_r")
scalebar = mpss.ScaleBar(self.calib, self.calib_units)
scalebar.location = "lower right"
scalebar.box_alpha = 1
scalebar.color = "k"
ax1.add_artist(scalebar)
at = mploff.AnchoredText(
r"$\mathrm{\epsilon_{xx}}$",
prop=dict(size=fontsize),
frameon=True,
loc="upper left",
)
at.patch.set_boxstyle("round, pad= 0., rounding_size= 0.2")
ax1.add_artist(at)
ax1.axis("off")
ax2.imshow(-100 * self.e_dg, vmin=-vm, vmax=vm, cmap="RdBu_r")
scalebar = mpss.ScaleBar(self.calib, self.calib_units)
scalebar.location = "lower right"
scalebar.box_alpha = 1
scalebar.color = "k"
ax2.add_artist(scalebar)
at = mploff.AnchoredText(
r"$\mathrm{\epsilon_{xy}}$",
prop=dict(size=fontsize),
frameon=True,
loc="upper left",
)
at.patch.set_boxstyle("round, pad= 0., rounding_size= 0.2")
ax2.add_artist(at)
ax2.axis("off")
ax3.imshow(-100 * self.e_th, vmin=-vm, vmax=vm, cmap="RdBu_r")
scalebar = mpss.ScaleBar(self.calib, self.calib_units)
scalebar.location = "lower right"
scalebar.box_alpha = 1
scalebar.color = "k"
ax3.add_artist(scalebar)
at = mploff.AnchoredText(
r"$\mathrm{\epsilon_{\theta}}$",
prop=dict(size=fontsize),
frameon=True,
loc="upper left",
)
at.patch.set_boxstyle("round, pad= 0., rounding_size= 0.2")
ax3.add_artist(at)
ax3.axis("off")
ax4.imshow(-100 * self.e_yy, vmin=-vm, vmax=vm, cmap="RdBu_r")
scalebar = mpss.ScaleBar(self.calib, self.calib_units)
scalebar.location = "lower right"
scalebar.box_alpha = 1
scalebar.color = "k"
ax4.add_artist(scalebar)
at = mploff.AnchoredText(
r"$\mathrm{\epsilon_{yy}}$",
prop=dict(size=fontsize),
frameon=True,
loc="upper left",
)
at.patch.set_boxstyle("round, pad= 0., rounding_size= 0.2")
ax4.add_artist(at)
ax4.axis("off")
sb = np.zeros((10, 1000), dtype=np.float)
for ii in range(10):
sb[ii, :] = np.linspace(-vm, vm, 1000)
ax5.imshow(sb, cmap="RdBu_r")
ax5.yaxis.set_visible(False)
no_labels = 9
x1 = np.linspace(0, 1000, no_labels)
ax5.set_xticks(x1)
ax5.set_xticklabels(np.round(np.linspace(-vm, vm, no_labels), 4))
for axis in ["top", "bottom", "left", "right"]:
ax5.spines[axis].set_linewidth(2)
ax5.spines[axis].set_color("black")
ax5.xaxis.set_tick_params(width=2, length=6, direction="out", pad=10)
ax5.set_title("Strain (%)", **sc_font)
plt.autoscale()