Module diffpy.mpdf.visualize3D
Generate 2D slices of 3D data for visualization.
Expand source code
#!/usr/bin/env python
##############################################################################
#
# diffpy.mpdf by Frandsen Group
# Benjamin A. Frandsen benfrandsen@byu.edu
# (c) 2022 Benjamin Allen Frandsen
# All rights reserved
#
# File coded by: Jacob Christensen and Benjamin Frandsen
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
"""Generate 2D slices of 3D data for visualization."""
import numpy as np
from scipy.interpolate import interpn
import matplotlib.pyplot as plt
from matplotlib.pyplot import cm, imshow, contour, clabel, colorbar
class Visualizer:
"""Store and visualize three-dimensional data.
Args:
m (numpy array): signal array (3D)
x (numpy array): x-dimension coordinates (1D)
y (numpy array): y-dimension coordinates (1D)
z (numpy array): z-dimension coordinates (1D)
"""
def __init__(self, m=None, x=None, y=None, z=None, sliceAvailable=False):
self.m = m
self.x = x
self.y = y
self.z = z
self.a = None # these two instance attributes will construct an arbitrary grid
self.b = None
self.slice = None # this will become the data slice
if m is None:
self.m = np.array([])
if x is None:
self.x = np.array([])
if y is None:
self.y = np.array([])
if z is None:
self.z = np.array([])
self.sliceAvailable = sliceAvailable
def three_points(self, p1, p2, p3):
"""find normal vector to the plane created by the three given points
"""
# find two vectors from the three points which lie on the desired plane
vec1 = p2 - p1
vec2 = p3 - p1
# now cross these two vectors to find a vector normal to the plane
normal = np.cross(vec1, vec2)
# now calculate the centroid of the three points given
x_pos = (p1[0] + p2[0] + p3[0]) / 3
y_pos = (p1[1] + p2[1] + p3[1]) / 3
z_pos = (p1[2] + p2[2] + p3[2]) / 3
cen_pt = np.array([x_pos, y_pos, z_pos])
print('Center Point:', cen_pt)
return normal, cen_pt
def make_slice(self, len_a=None, len_b=None, dr=None, use_normal=None,
cen_pt=None, normal=None, p1=None, p2=None, p3=None,
returnSlice=False):
"""generate a 2D slice through the dataset
Args:
len_a, len_b (float): the side lengths in Angstroms of the rectangular
slice to be taken through the data
dr (float): determines the spacing of the grid in Angstroms
use_normal (boolean): when True, will create slice from a normal vector and center point. When
False, will create slice from three points
cen_pt (numpy array): the center of the desired slice. Used when use_normal is True
normal (numpy array): the normal vector to desired plane. Used when use_normal is True
p1, p2, p3 (numpy array): three points in 3D space. The desired plane
goes through these points. Used when use_normal is False.
returnSlice (boolean): If True, this function will return the slice and grid.
Returns:
2D array, along with space arrays, representing slice through 3D dataset
"""
if dr is None:
dr = 1
if use_normal is None:
use_normal = True
if len_a is None:
len_a = 10
if len_b is None:
len_b = 10
if cen_pt is None:
cen_pt = np.array([0, 0, 0])
if normal is None:
normal = np.array([1, 0, 0])
if p1 is None:
p1 = np.array([0, 1, 0])
if p2 is None:
p2 = np.array([1, 0, 0])
if p3 is None:
p3 = np.array([0, 0, 1])
# First check if use_normal is False. If so, access three_points function
# to calculate the normal and cen_pt of the desired plane
if use_normal is False:
normal, cen_pt = self.three_points(p1, p2, p3)
# ensure that our basis vector v1 is not the same as normal
v1 = np.array([1, 0, 0])
if np.allclose(v1, normal):
v1 = np.array([0, 1, 0])
# now make a matrix which will reflect any vector onto the orthogonal
# complement of the normal vec, which is our desired plane
# This is done by subtracting from the vector its component along the normal vector
m_norm = np.eye(3) - (np.outer(normal, normal.T) / normal.T.dot(normal))
# now reflect v1 using m_norm
v1 = m_norm.dot(v1)
# and create a new vector v2 that is orthogonal to both v1 and normal
v2 = np.cross(normal, v1)
# we now have 2 vectors to form our plane
# now create and normalize Q, which will rotate an arbitrary
# slice to the orientation we desire
Q = np.column_stack((v1, v2, np.zeros_like(v1)))
Q[:,:2] /= np.linalg.norm(Q[:,:2], axis = 0)
# now create an arbitrary slice
self.a = np.arange(-len_a / 2, len_a / 2, dr)
self.b = np.arange(-len_b / 2, len_b / 2, dr)
self.a = np.append(self.a, len_a / 2)
self.b = np.append(self.b, len_b / 2)
A,B = np.meshgrid(self.a, self.b)
locations = np.array([A.reshape(-1), B.reshape(-1), np.zeros(A.size)]) # the slice starts on the x-y plane
# now move locations onto our two vectors, and add cen_pt to move slice into position
locations = Q.dot(locations).T + (cen_pt)
# now we need to interpolate our 3Dmpdf function over this slice
points = (self.x, self.y, self.z)
interp = interpn(points, self.m, locations) # list of values of 3Dmpdf at locations
self.slice = interp.reshape(len(self.b),len(self.a))
self.sliceAvailable = True
if returnSlice:
return self.slice, self.a, self.b
def visualize(self):
"""Visualize the current slice.
"""
if self.sliceAvailable:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlabel(r'$\mathdefault{\AA}$')
ax.set_ylabel(r'$\mathdefault{\AA}$')
amin, amax = min(self.a), max(self.a)
bmin, bmax = min(self.b), max(self.b)
im = ax.imshow(self.slice,
extent=[amin, amax, bmin, bmax])
colorbar(im)
plt.tight_layout()
plt.show()
else:
print('No data slice available for visualization.')
print('Please generate a slice using the makeSlice method.')
Classes
class Visualizer (m=None, x=None, y=None, z=None, sliceAvailable=False)
-
Store and visualize three-dimensional data.
Args
m
:numpy array
- signal array (3D)
x
:numpy array
- x-dimension coordinates (1D)
y
:numpy array
- y-dimension coordinates (1D)
z
:numpy array
- z-dimension coordinates (1D)
Expand source code
class Visualizer: """Store and visualize three-dimensional data. Args: m (numpy array): signal array (3D) x (numpy array): x-dimension coordinates (1D) y (numpy array): y-dimension coordinates (1D) z (numpy array): z-dimension coordinates (1D) """ def __init__(self, m=None, x=None, y=None, z=None, sliceAvailable=False): self.m = m self.x = x self.y = y self.z = z self.a = None # these two instance attributes will construct an arbitrary grid self.b = None self.slice = None # this will become the data slice if m is None: self.m = np.array([]) if x is None: self.x = np.array([]) if y is None: self.y = np.array([]) if z is None: self.z = np.array([]) self.sliceAvailable = sliceAvailable def three_points(self, p1, p2, p3): """find normal vector to the plane created by the three given points """ # find two vectors from the three points which lie on the desired plane vec1 = p2 - p1 vec2 = p3 - p1 # now cross these two vectors to find a vector normal to the plane normal = np.cross(vec1, vec2) # now calculate the centroid of the three points given x_pos = (p1[0] + p2[0] + p3[0]) / 3 y_pos = (p1[1] + p2[1] + p3[1]) / 3 z_pos = (p1[2] + p2[2] + p3[2]) / 3 cen_pt = np.array([x_pos, y_pos, z_pos]) print('Center Point:', cen_pt) return normal, cen_pt def make_slice(self, len_a=None, len_b=None, dr=None, use_normal=None, cen_pt=None, normal=None, p1=None, p2=None, p3=None, returnSlice=False): """generate a 2D slice through the dataset Args: len_a, len_b (float): the side lengths in Angstroms of the rectangular slice to be taken through the data dr (float): determines the spacing of the grid in Angstroms use_normal (boolean): when True, will create slice from a normal vector and center point. When False, will create slice from three points cen_pt (numpy array): the center of the desired slice. Used when use_normal is True normal (numpy array): the normal vector to desired plane. Used when use_normal is True p1, p2, p3 (numpy array): three points in 3D space. The desired plane goes through these points. Used when use_normal is False. returnSlice (boolean): If True, this function will return the slice and grid. Returns: 2D array, along with space arrays, representing slice through 3D dataset """ if dr is None: dr = 1 if use_normal is None: use_normal = True if len_a is None: len_a = 10 if len_b is None: len_b = 10 if cen_pt is None: cen_pt = np.array([0, 0, 0]) if normal is None: normal = np.array([1, 0, 0]) if p1 is None: p1 = np.array([0, 1, 0]) if p2 is None: p2 = np.array([1, 0, 0]) if p3 is None: p3 = np.array([0, 0, 1]) # First check if use_normal is False. If so, access three_points function # to calculate the normal and cen_pt of the desired plane if use_normal is False: normal, cen_pt = self.three_points(p1, p2, p3) # ensure that our basis vector v1 is not the same as normal v1 = np.array([1, 0, 0]) if np.allclose(v1, normal): v1 = np.array([0, 1, 0]) # now make a matrix which will reflect any vector onto the orthogonal # complement of the normal vec, which is our desired plane # This is done by subtracting from the vector its component along the normal vector m_norm = np.eye(3) - (np.outer(normal, normal.T) / normal.T.dot(normal)) # now reflect v1 using m_norm v1 = m_norm.dot(v1) # and create a new vector v2 that is orthogonal to both v1 and normal v2 = np.cross(normal, v1) # we now have 2 vectors to form our plane # now create and normalize Q, which will rotate an arbitrary # slice to the orientation we desire Q = np.column_stack((v1, v2, np.zeros_like(v1))) Q[:,:2] /= np.linalg.norm(Q[:,:2], axis = 0) # now create an arbitrary slice self.a = np.arange(-len_a / 2, len_a / 2, dr) self.b = np.arange(-len_b / 2, len_b / 2, dr) self.a = np.append(self.a, len_a / 2) self.b = np.append(self.b, len_b / 2) A,B = np.meshgrid(self.a, self.b) locations = np.array([A.reshape(-1), B.reshape(-1), np.zeros(A.size)]) # the slice starts on the x-y plane # now move locations onto our two vectors, and add cen_pt to move slice into position locations = Q.dot(locations).T + (cen_pt) # now we need to interpolate our 3Dmpdf function over this slice points = (self.x, self.y, self.z) interp = interpn(points, self.m, locations) # list of values of 3Dmpdf at locations self.slice = interp.reshape(len(self.b),len(self.a)) self.sliceAvailable = True if returnSlice: return self.slice, self.a, self.b def visualize(self): """Visualize the current slice. """ if self.sliceAvailable: fig = plt.figure() ax = fig.add_subplot(111) ax.set_xlabel(r'$\mathdefault{\AA}$') ax.set_ylabel(r'$\mathdefault{\AA}$') amin, amax = min(self.a), max(self.a) bmin, bmax = min(self.b), max(self.b) im = ax.imshow(self.slice, extent=[amin, amax, bmin, bmax]) colorbar(im) plt.tight_layout() plt.show() else: print('No data slice available for visualization.') print('Please generate a slice using the makeSlice method.')
Methods
def make_slice(self, len_a=None, len_b=None, dr=None, use_normal=None, cen_pt=None, normal=None, p1=None, p2=None, p3=None, returnSlice=False)
-
generate a 2D slice through the dataset
Args
- len_a, len_b (float): the side lengths in Angstroms of the rectangular
- slice to be taken through the data
dr
:float
- determines the spacing of the grid in Angstroms
use_normal
:boolean
- when True, will create slice from a normal vector and center point. When False, will create slice from three points
cen_pt
:numpy array
- the center of the desired slice. Used when use_normal is True
normal
:numpy array
- the normal vector to desired plane. Used when use_normal is True
- p1, p2, p3 (numpy array): three points in 3D space. The desired plane
- goes through these points. Used when use_normal is False.
returnSlice
:boolean
- If True, this function will return the slice and grid.
Returns
2D array, along with space arrays, representing slice through 3D dataset
Expand source code
def make_slice(self, len_a=None, len_b=None, dr=None, use_normal=None, cen_pt=None, normal=None, p1=None, p2=None, p3=None, returnSlice=False): """generate a 2D slice through the dataset Args: len_a, len_b (float): the side lengths in Angstroms of the rectangular slice to be taken through the data dr (float): determines the spacing of the grid in Angstroms use_normal (boolean): when True, will create slice from a normal vector and center point. When False, will create slice from three points cen_pt (numpy array): the center of the desired slice. Used when use_normal is True normal (numpy array): the normal vector to desired plane. Used when use_normal is True p1, p2, p3 (numpy array): three points in 3D space. The desired plane goes through these points. Used when use_normal is False. returnSlice (boolean): If True, this function will return the slice and grid. Returns: 2D array, along with space arrays, representing slice through 3D dataset """ if dr is None: dr = 1 if use_normal is None: use_normal = True if len_a is None: len_a = 10 if len_b is None: len_b = 10 if cen_pt is None: cen_pt = np.array([0, 0, 0]) if normal is None: normal = np.array([1, 0, 0]) if p1 is None: p1 = np.array([0, 1, 0]) if p2 is None: p2 = np.array([1, 0, 0]) if p3 is None: p3 = np.array([0, 0, 1]) # First check if use_normal is False. If so, access three_points function # to calculate the normal and cen_pt of the desired plane if use_normal is False: normal, cen_pt = self.three_points(p1, p2, p3) # ensure that our basis vector v1 is not the same as normal v1 = np.array([1, 0, 0]) if np.allclose(v1, normal): v1 = np.array([0, 1, 0]) # now make a matrix which will reflect any vector onto the orthogonal # complement of the normal vec, which is our desired plane # This is done by subtracting from the vector its component along the normal vector m_norm = np.eye(3) - (np.outer(normal, normal.T) / normal.T.dot(normal)) # now reflect v1 using m_norm v1 = m_norm.dot(v1) # and create a new vector v2 that is orthogonal to both v1 and normal v2 = np.cross(normal, v1) # we now have 2 vectors to form our plane # now create and normalize Q, which will rotate an arbitrary # slice to the orientation we desire Q = np.column_stack((v1, v2, np.zeros_like(v1))) Q[:,:2] /= np.linalg.norm(Q[:,:2], axis = 0) # now create an arbitrary slice self.a = np.arange(-len_a / 2, len_a / 2, dr) self.b = np.arange(-len_b / 2, len_b / 2, dr) self.a = np.append(self.a, len_a / 2) self.b = np.append(self.b, len_b / 2) A,B = np.meshgrid(self.a, self.b) locations = np.array([A.reshape(-1), B.reshape(-1), np.zeros(A.size)]) # the slice starts on the x-y plane # now move locations onto our two vectors, and add cen_pt to move slice into position locations = Q.dot(locations).T + (cen_pt) # now we need to interpolate our 3Dmpdf function over this slice points = (self.x, self.y, self.z) interp = interpn(points, self.m, locations) # list of values of 3Dmpdf at locations self.slice = interp.reshape(len(self.b),len(self.a)) self.sliceAvailable = True if returnSlice: return self.slice, self.a, self.b
def three_points(self, p1, p2, p3)
-
find normal vector to the plane created by the three given points
Expand source code
def three_points(self, p1, p2, p3): """find normal vector to the plane created by the three given points """ # find two vectors from the three points which lie on the desired plane vec1 = p2 - p1 vec2 = p3 - p1 # now cross these two vectors to find a vector normal to the plane normal = np.cross(vec1, vec2) # now calculate the centroid of the three points given x_pos = (p1[0] + p2[0] + p3[0]) / 3 y_pos = (p1[1] + p2[1] + p3[1]) / 3 z_pos = (p1[2] + p2[2] + p3[2]) / 3 cen_pt = np.array([x_pos, y_pos, z_pos]) print('Center Point:', cen_pt) return normal, cen_pt
def visualize(self)
-
Visualize the current slice.
Expand source code
def visualize(self): """Visualize the current slice. """ if self.sliceAvailable: fig = plt.figure() ax = fig.add_subplot(111) ax.set_xlabel(r'$\mathdefault{\AA}$') ax.set_ylabel(r'$\mathdefault{\AA}$') amin, amax = min(self.a), max(self.a) bmin, bmax = min(self.b), max(self.b) im = ax.imshow(self.slice, extent=[amin, amax, bmin, bmax]) colorbar(im) plt.tight_layout() plt.show() else: print('No data slice available for visualization.') print('Please generate a slice using the makeSlice method.')