I have a large set of vectors, which orientations I want to analyze by looking at the polar angles theta and phi. So I start with a 2D array of those angles.
I want to visualize it by plotting a histogram, or preferentially the kernel density onto a sphere (or the 2D projection, since I only need the show half a sphere).
If I use the polar angles for the histogram, it will be completely wrong, since - to give an example - the points theta = 1°, phi = 0° & 180° are closer than theta = 30°, phi = 0° & 180°. The binning fails.
If I use 2D cartesian coordinates of the projection for the histogram, I run into the same problem: points on x = 0, y = 0 & 0.1 are closer than x = 0, y = 0.5 & 0.6.
Right now I'm using a kernel density estimate with 3D cartesian coordinates:
from scipy import stats
X, Y, Z = [], [], []
for i in range(300):
for j in range(500):
X.append(np.sin(i / 300 * pi / 2) * np.cos(j / 500 * 2 * pi))
Y.append(np.sin(i / 300 * pi / 2) * np.sin(j / 500 * 2 * pi))
Z.append(np.cos(i / 300 * pi / 2))
X, Y, Z = np.array(X), np.array(Y), np.array(Z)
positions = np.vstack([X, Y, Z])
# x, y and z for my data: sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)
values = np.vstack([np.sin(data[:, 1]) * np.cos(data[:, 2]), np.sin(data[:, 1]) * np.sin(data[:, 2]), np.cos(data[:, 1])])
fig, ax = subplots(1, figsize=(7, 7))
kernel = stats.gaussian_kde(values)
density = kernel(positions)
ax.tricontourf(X, Y, density, levels=np.linspace(0, max(density), 50))
plt.box('off')
ax.set_ylim((-1.01, 1.01))
ax.set_xlim((-1.01, 1.01))
This gives me:
Polar density plot
I don't know if a) this gives the correct result and b) if there isn't a better solution for this.
Related
I am working on a bioinformatics project, where my biological object is a filled sphere.
The package that I am using provides a function that allows us to construct a sphere of points, but the points are only at the surface of the sphere.
Is there, a function, or a method that could help me construction a filled sphere (evenly placed points inside and at the surface of a spherical object).
Here is the code I was working with :
def construct_sphere(num_locations):
num_pts = num_locations
indices = np.arange(0, num_pts, dtype=float) + 0.5
phi = np.arccos(1 - 2*indices/num_pts)
indices = np.arange(0, num_pts, dtype=float) + 0.5
phi = np.arccos(1 - 2*indices/num_pts)
theta = math.pi * (1 + 5**0.5) * indices
x, y, z = np.cos(theta) * np.sin(phi), np.sin(theta) * np.sin(phi), np.cos(phi);
locations = np.array(list(zip(x, y, z)))
return locations
Here is a Hopf torus made in Python with PyVista:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
# Display the mesh
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
The color is not entirely smooth: on the lobe at the bottom right, you can see a line which separates pale gray and dark gray. How to get rid of this line?
I think what's going on here is that there's no connectivity information where the two ends of your structured grid meet. One way to fix this is to turn your grid into a PolyData using the extract_geometry() method, and then using clean with a larger tolerance. This will force pyvista to realise that there's a seam in the mesh where points are doubled, causing the points to be merged and the seam closed:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
# Display the mesh, show seam
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
# convert to PolyData and clean to remove the seam
cleaned_poly = grid.extract_geometry().clean(tolerance=1e-6)
cleaned_poly.plot(smooth_shading=True)
Your mileage for the tolerance parameter may vary.
Just as a piece of trivia, we can visualize the original seam by extracting the feature edges of your original grid:
grid.extract_feature_edges().plot()
These curves correspond to the open edges in your original grid:
>>> grid.extract_surface().n_open_edges
1196
Since your surface is closed and watertight, it should have 0 open edges:
>>> cleaned_poly.n_open_edges
0
This is a code for generating random sized spheres with mayavi,
I want to make the spheres to be connected with each other by the surface or with a bond line:
Spheres must be at random positions in 3D space
Spheres must be with the same radius
from mayavi import mlab
import numpy as np
[phi,theta] = np.mgrid[0:2*np.pi:12j,0:np.pi:12j]
x = np.cos(phi)*np.sin(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(theta)
def plot_sphere(p):
r,a,b,c = p
r=1
return mlab.mesh(r*x+a, r*y+b, r*z )
for k in range(8):
c = np.random.rand(4)
c[0] /= 10.
plot_sphere(c)
mlab.show()
From sphere equation:
So when passing arguments to mlab.mesh we would like to set [x_0, y_0, z_0] for each sphere such as they are at different positions from the axis.
The problem was that the numbers generated by np.random.rand(4) are random, but not distinct.
Let's modify so that the arguments [x_0, y_0, z_0] are random and distinct:
We use sample to get distinct index numbers in a cube
We convert using index_to_3d the index to an (x, y, z) coordinates
The radius, r, can be adjusted to have more or less spacing between the spheres.
Spheres at 3D space
Code:
import random
from itertools import product
from mayavi import mlab
import numpy as np
[phi, theta] = np.mgrid[0:2 * np.pi:12j, 0:np.pi:12j]
x = np.cos(phi) * np.sin(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(theta)
def plot_sphere(x_0, y_0, z_0):
r = 0.5
return mlab.mesh(r * x + x_0, r * y + y_0, r * z + z_0)
SPHERES_NUMBER = 200
CUBE_SIZE = 10
def index_to_3d(i, SIZE):
z = i // (SIZE * SIZE)
i -= (z * SIZE * SIZE)
y = i // SIZE
x = i % SIZE
return x, y, z
random_tuples = [index_to_3d(i, CUBE_SIZE) for i in random.sample(range(CUBE_SIZE ** 3), SPHERES_NUMBER)]
for k in range(SPHERES_NUMBER):
x_0, y_0, z_0 = random_tuples[k]
plot_sphere(x_0, y_0, z_0)
mlab.show()
Output:
Spheres cluster
Let's utilize gauss to create coordinates for the cluster points.
Code:
import random
from itertools import product
from mayavi import mlab
import numpy as np
[phi, theta] = np.mgrid[0:2 * np.pi:12j, 0:np.pi:12j]
x = np.cos(phi) * np.sin(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(theta)
def plot_sphere(x_0, y_0, z_0):
r = 0.5
return mlab.mesh(r * x + x_0, r * y + y_0, r * z + z_0)
SPHERES_NUMBER = 200
def create_cluster(CLUSTER_SIZE):
means_and_deviations = [(1, 1.5), (1, 1.5), (1, 1.5)]
def generate_point(means_and_deviations):
return tuple(random.gauss(mean, deviation) for mean, deviation in means_and_deviations)
cluster_points = set()
while len(cluster_points) < CLUSTER_SIZE:
cluster_points.add(generate_point(means_and_deviations))
return list(cluster_points)
cluster_points = create_cluster(SPHERES_NUMBER)
for k in range(SPHERES_NUMBER):
x_0, y_0, z_0 = cluster_points[k]
plot_sphere(x_0, y_0, z_0)
mlab.show()
Output:
What about just using the mayavi points3d function? By default the mode parameter is set to sphere and you can set the diameter by using the scale_factor parameter. You can also increase the resolution of the sphere by varying the resolution parameter.
Here is the code:
def draw_sphere(
center_coordinates,
radius,
figure_title,
color,
background,
foreground
):
sphere = mlab.figure(figure_title)
sphere.scene.background = background
sphere.scene.foreground = foreground
mlab.points3d(
center_coordinates[0],
center_coordinates[1],
center_coordinates[2],
color=color,
resolution=256,
scale_factor=2*radius,
figure=sphere
)
Regarding the connected with each other by the surface issue, your explanation is poor. Maybe you mean just tangent spheres, but I would need more details.
I want to use a Gabor filter as an interpolation method after the conversion of a square-sampled image to an hexagonally-sampled image.
Can I use the normal Gabor filter implementation in an hexagonally-sampled image? Or should I modify the code? If yes, then what part of the Gabor function should I modify for an hexagonally-sampled image?
I've tried implementing the algorithm but I just can't get it right.
Here's a code for Gabor filtering taken from GitHub.
import numpy as np
import cv2
# cv2.getGaborKernel(ksize, sigma, theta, lambda, gamma, psi, ktype)
# ksize - size of gabor filter (n, n)
# sigma - standard deviation of the gaussian function
# theta - orientation of the normal to the parallel stripes
# lambda - wavelength of the sunusoidal factor
# gamma - spatial aspect ratio
# psi - phase offset
# ktype - type and range of values that each pixel in the gabor kernel can hold
g_kernel = cv2.getGaborKernel((21, 21), 8.0, np.pi/4, 10.0, 0.5, 0, ktype=cv2.CV_32F)
img = cv2.imread('test.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
filtered_img = cv2.filter2D(img, cv2.CV_8UC3, g_kernel)
cv2.imshow('image', img)
cv2.imshow('filtered image', filtered_img)
h, w = g_kernel.shape[:2]
g_kernel = cv2.resize(g_kernel, (3*w, 3*h), interpolation=cv2.INTER_CUBIC)
cv2.imshow('gabor kernel (resized)', g_kernel)
cv2.waitKey(0)
cv2.destroyAllWindows()
Assuming the ordinary data structure for a hexagonal grid, you can probably apply a 2d filter to a hexagonal-pixel image, but you need to create a filter that is evaluated at the appropriate coordinates. You see, a 2d filter is just a matrix of values generated by evaluating a function over xy coordinates in a grid. So, a 5x5 Gabor filter matrix is just a Gabor function evaluated at the xy coordinates shown here:
Pixels are "equally spaced" so we can simply pick the distance between each point in the grid to be 1 in x and 1 in y, and we get the Gabor function evaluated at the center of each pixel.
However, hexagonal pixels are not arranged this way. The centers of hexagonal pixels are arranged as thus:
Thus, in order to apply a filter to this, we need to evaluate the appropriate function at these points. Since the stock filters have been evaluated on a rectangular grid, we cannot use them (although they will produce something that probably looks reasonable).
Fortunately, the transformation is relatively easy. If we assume the vertical distance between two rows is 1, then the coordinates are almost just an np.arange.
import numpy as np
import matplotlib.pyplot as plt
ALTERNATE_ROW_SHIFT = 0+np.sqrt(3)/3 # every other row is "offset" by half a hexagon. If the sides are len 2/3, the shift is root 3 over 3
def hex_grid(rect_grid):
rect_grid = np.copy(rect_grid)
rect_grid[0,:,1::2] += ALTERNATE_ROW_SHIFT
return rect_grid
If you have access to the function that creates your filter, there will usually be some logic that creates a rectangular grid on which the function is subsequently evaluated. Drop the hex_grid function in on the line after to get hexagonally-spaced coordinates instead.
For example, the wikipedia page on Gabor filters has a python implementation to create a Gabor filter, shown here:
def gabor_fn(sigma, theta, Lambda, psi, gamma):
sigma_x = sigma
sigma_y = float(sigma) / gamma
# Bounding box
nstds = 3 # Number of standard deviation sigma
xmax = max(abs(nstds * sigma_x * np.cos(theta)), abs(nstds * sigma_y * np.sin(theta)))
xmax = np.ceil(max(1, xmax))
ymax = max(abs(nstds * sigma_x * np.sin(theta)), abs(nstds * sigma_y * np.cos(theta)))
ymax = np.ceil(max(1, ymax))
xmin = -xmax
ymin = -ymax
(y,x) = np.meshgrid(np.arange(ymin, ymax + 1), np.arange(xmin, xmax + 1))
# Rotation
x_theta = x * np.cos(theta) + y * np.sin(theta)
y_theta = -x * np.sin(theta) + y * np.cos(theta)
gb = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.cos(2 * np.pi / Lambda * x_theta + psi)
return gb
Note the line involving a np.meshgrid. This creates a rectagular grid with spacing 1 that is used on subsequent lines. We can simply transform those coordinates to create a new hex_gabor function (Note that this is 95% identical to the gabor_fn code):
def hex_gabor_fn(sigma, theta, Lambda, psi, gamma):
sigma_x = sigma
sigma_y = float(sigma) / gamma
# Bounding box
nstds = 3 # Number of standard deviation sigma
xmax = max(abs(nstds * sigma_x * np.cos(theta)), abs(nstds * sigma_y * np.sin(theta)))
xmax = np.ceil(max(1, xmax))
ymax = max(abs(nstds * sigma_x * np.sin(theta)), abs(nstds * sigma_y * np.cos(theta)))
ymax = np.ceil(max(1, ymax))
xmin = -xmax
ymin = -ymax
yx = np.meshgrid(np.arange(ymin, ymax + 1), np.arange(xmin, xmax + 1))
(y,x) = hex_grid(yx)
# Rotation
x_theta = x * np.cos(theta) + y * np.sin(theta)
y_theta = -x * np.sin(theta) + y * np.cos(theta)
gb = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.cos(2 * np.pi / Lambda * x_theta + psi)
return gb
if __name__ == "__main__":
g = gabor_fn(4,np.pi/4,4,0,2)
hg = hex_gabor_fn(4,np.pi/4,4,0,2)
plt.imshow(g)
plt.show()
plt.imshow(hg)
plt.show()
You should be able to drop the resulting kernel into this line cv2.filter2D(img, cv2.CV_8UC3, g_kernel).
I am looking for the points of intersection of a vertical line with a plot that I have made that has pyplot's interpolated values.
I think the code and plot below will make my question more clear. Below is some example code, and the resulting plot. What I am looking for is all intersection points between the red vertical line and the blue lines (so there should be 3 such points in this case).
I am at a loss for how to do this - does anyone know how?
The code:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
t = np.linspace(-np.pi, np.pi, 512, endpoint=False) + 0.0001 # 0.0001 to get rid of 0 values.
# normalized square wave
u = np.sign(np.sin(2 * np.pi * t))
u = u - np.min(u)
u = u / np.max(u)
# rotate the square wave
phi = - np.pi / 3.0
t_rot = t * np.cos(phi) - u * np.sin(phi)
u_rot = u * np.cos(phi) + t * np.sin(phi)
# level the rotated square wave
u_rot_leveled = u_rot + np.tan(-phi) * t_rot
plt.plot(t_rot, u_rot_leveled, '.-')
plt.axvline(x=-1.1, linestyle=':', color='red')
The plot:
Thanks for any help!
Instead of interpolating the values of y where x==x0, you may actually find the roots(zeros) of x-x0 with respect to y.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-np.pi, np.pi, 512, endpoint=False) + 0.0001 # 0.0001 to get rid of 0 values.
# normalized square wave
u = np.sign(np.sin(2 * np.pi * t))
u = u - np.min(u)
u = u / np.max(u)
# rotate the square wave
phi = - np.pi / 3.0
t_rot = t * np.cos(phi) - u * np.sin(phi)
u_rot = u * np.cos(phi) + t * np.sin(phi)
# level the rotated square wave
u_rot_leveled = u_rot + np.tan(-phi) * t_rot
def find_roots(x,y):
s = np.abs(np.diff(np.sign(y))).astype(bool)
return x[:-1][s] + np.diff(x)[s]/(np.abs(y[1:][s]/y[:-1][s])+1)
x0 = -1.1
z = find_roots(u_rot_leveled, t_rot-x0)
plt.plot(t_rot, u_rot_leveled, '.-')
plt.axvline(x=x0, linestyle=':', color='red')
plt.plot(np.ones_like(z)*x0, z, marker="o", ls="", ms=4, color="limegreen")
plt.show()
Part of the solution here is taken from my answer to How to get values from a graph?