Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rendering vectors in a sphere, with better perception

I need to render some 3D vectors starting in (0,0,0) ending all on a sphere. Currently I do this with python and matplotlib:

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import proj3d
from matplotlib.patches import FancyArrowPatch

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect("equal")

# render the sphere mesh
u, v = np.mgrid[0:2*np.pi:20j, 00:np.pi:10j]
x=np.cos(u)*np.sin(v)
y=np.sin(u)*np.sin(v)
z=np.cos(v)
ax.plot_wireframe(x, y, z, color="r")
plt.axis('off')

class Arrow3D(FancyArrowPatch):
    def __init__(self, xs, ys, zs, *args, **kwargs):
        FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def draw(self, renderer):
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
        self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
        FancyArrowPatch.draw(self, renderer)

gradients = [[ 0.57735027, -0.57735027,  0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058,  0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662,  0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121,  0.22638878], [ 0.51285124, -0.09350922,  0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038,  0.87474549], [-0.11906644, -0.66461051,  0.73764223], [-0.69553846, -0.26904184,  0.66621518], [ 0.84464198, -0.38759068,  0.3692607 ], [ 0.01218729, -0.86413401,  0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422,  0.715783  ], [ 0.35672677, -0.81123132,  0.46330312], [-0.64859039, -0.59819001,  0.47063699], [-0.9860335 , -0.04110239,  0.16139577], [ 0.78665483, -0.298026  , -0.54069835], [-0.22515292, -0.39356178,  0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461,  0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807,  0.97246754], [-0.11702234, -0.10076906,  0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486,  0.64933801], [ 0.97685069, -0.17835225,  0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949,  0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 ,  0.45099396], [ 0.09939465, -0.40802586,  0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476,  0.06741077], [-0.19623031, -0.95031369,  0.24165596], [ 0.45153776, -0.87650371,  0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433,  0.20257032], [ 0.86954373, -0.49204889,  0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777,  0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134,  0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413,  0.48543158]]

for i in gradients:
    ax.add_artist( Arrow3D([0,-i[0]],[0,-i[1]],[0,-i[2]], mutation_scale=20, lw=1, arrowstyle="-|>", color="k") )
    ax.add_artist( Arrow3D([0,i[0]],[0,i[1]],[0,i[2]], mutation_scale=20, lw=1, arrowstyle="-|>", color="k") )
plt.show()

Which results in such images:

enter image description here

However, this is very bad, as no spatial depth is visible. How can I improve the result?

Edit: Their endings have no special meaning, but the reader of the image should see, that these directions are almost uniformly distributed.

like image 536
math Avatar asked Jan 12 '23 00:01

math


1 Answers

The apparent lack of depth in your current rendering is mostly due to the lack of shading.

matplotlib isn't set up to handle "true" 3D rendering (i.e. occlusion, shading, etc), and you're going to need a lot of the subtle visual hints that this gives to get the impression of depth.

mayavi.mlab is a good choice for a simple python API to a very full-featured 3D visualization package (VTK).

As an example based on yours:

import numpy as np
from mayavi import mlab

# render the sphere mesh
u, v = np.mgrid[0:2*np.pi:20j, 00:np.pi:10j]
x=np.cos(u)*np.sin(v)
y=np.sin(u)*np.sin(v)
z=np.cos(v)

for a,b,c in zip(x.T, y.T, z.T) + zip(x, y, z):
    mlab.plot3d(a, b, c, color=(1,0,0), tube_radius=0.01)

gradients = np.array([[ 0.57735027, -0.57735027,  0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058,  0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662,  0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121,  0.22638878], [ 0.51285124, -0.09350922,  0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038,  0.87474549], [-0.11906644, -0.66461051,  0.73764223], [-0.69553846, -0.26904184,  0.66621518], [ 0.84464198, -0.38759068,  0.3692607 ], [ 0.01218729, -0.86413401,  0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422,  0.715783  ], [ 0.35672677, -0.81123132,  0.46330312], [-0.64859039, -0.59819001,  0.47063699], [-0.9860335 , -0.04110239,  0.16139577], [ 0.78665483, -0.298026  , -0.54069835], [-0.22515292, -0.39356178,  0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461,  0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807,  0.97246754], [-0.11702234, -0.10076906,  0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486,  0.64933801], [ 0.97685069, -0.17835225,  0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949,  0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 ,  0.45099396], [ 0.09939465, -0.40802586,  0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476,  0.06741077], [-0.19623031, -0.95031369,  0.24165596], [ 0.45153776, -0.87650371,  0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433,  0.20257032], [ 0.86954373, -0.49204889,  0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777,  0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134,  0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413,  0.48543158]])
u, v, w = gradients.T
zero = np.zeros_like(u)

kwargs = dict(color=(.2,.2,.2), mode='arrow', resolution=20)
mlab.quiver3d(zero, zero, zero, u, v, w, **kwargs)
mlab.quiver3d(zero, zero, zero, -u, -v, -w, **kwargs)

mlab.show()

enter image description here

The only downside to this is that the relative arrow heads are fixed (they're just a static glyph). It's probably possible to change that (i.e. use a custom glyph), but I don't know how offhand.

You can work around that by drawing the arrow head and body separately. This will be slow to render, but it illustrates the idea:

import numpy as np
from mayavi import mlab

# render the sphere mesh
u, v = np.mgrid[0:2*np.pi:20j, 00:np.pi:10j]
x=np.cos(u)*np.sin(v)
y=np.sin(u)*np.sin(v)
z=np.cos(v)

# White background and turn on antialiasing
fig = mlab.figure(bgcolor=(1,1,1))
fig.scene.render_window.aa_frames = 8

# Plot the sphere's mesh
for a,b,c in zip(x.T, y.T, z.T) + zip(x, y, z):
    mlab.plot3d(a, b, c, color=(1,0,0), tube_radius=0.01)

gradients = np.array([[ 0.57735027, -0.57735027,  0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058,  0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662,  0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121,  0.22638878], [ 0.51285124, -0.09350922,  0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038,  0.87474549], [-0.11906644, -0.66461051,  0.73764223], [-0.69553846, -0.26904184,  0.66621518], [ 0.84464198, -0.38759068,  0.3692607 ], [ 0.01218729, -0.86413401,  0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422,  0.715783  ], [ 0.35672677, -0.81123132,  0.46330312], [-0.64859039, -0.59819001,  0.47063699], [-0.9860335 , -0.04110239,  0.16139577], [ 0.78665483, -0.298026  , -0.54069835], [-0.22515292, -0.39356178,  0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461,  0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807,  0.97246754], [-0.11702234, -0.10076906,  0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486,  0.64933801], [ 0.97685069, -0.17835225,  0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949,  0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 ,  0.45099396], [ 0.09939465, -0.40802586,  0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476,  0.06741077], [-0.19623031, -0.95031369,  0.24165596], [ 0.45153776, -0.87650371,  0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433,  0.20257032], [ 0.86954373, -0.49204889,  0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777,  0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134,  0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413,  0.48543158]])
u, v, w = gradients.T
zero = np.zeros_like(u)

color = (0.2, 0.2, 0.2)
head_length = 0.2
for direct in [-1, 1]:
    h, b = direct * 0.2, direct * (1 - head_length)
    mlab.quiver3d(b*u, b*v, b*w, h*u, h*v, h*w, 
                  mode='cone', scale_factor=1, color=color, resolution=20)
    for i, j, k in zip(u, v, w):
        mlab.plot3d([0, b*i], [0, b*j], [0, b*k], color=color, tube_radius=0.01)

mlab.show()

enter image description here

You could also change things so that the cones are on the outside of the sphere:

enter image description here

Taking that one step farther, you can improve the rendering speed a bit and get a less confusing figure by doing:

import numpy as np
from mayavi import mlab

# White background and turn on antialiasing
fig = mlab.figure(bgcolor=(1,1,1))
fig.scene.render_window.aa_frames = 8

# Sphere at origin with a diameter of 2 (radius of 1)
ltgray = (0.9, 0.9, 0.9)
mlab.points3d([0], [0], [0], [2], resolution=20, scale_factor=1, color=ltgray)

gradients = np.array([[ 0.57735027, -0.57735027,  0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058,  0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662,  0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121,  0.22638878], [ 0.51285124, -0.09350922,  0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038,  0.87474549], [-0.11906644, -0.66461051,  0.73764223], [-0.69553846, -0.26904184,  0.66621518], [ 0.84464198, -0.38759068,  0.3692607 ], [ 0.01218729, -0.86413401,  0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422,  0.715783  ], [ 0.35672677, -0.81123132,  0.46330312], [-0.64859039, -0.59819001,  0.47063699], [-0.9860335 , -0.04110239,  0.16139577], [ 0.78665483, -0.298026  , -0.54069835], [-0.22515292, -0.39356178,  0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461,  0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807,  0.97246754], [-0.11702234, -0.10076906,  0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486,  0.64933801], [ 0.97685069, -0.17835225,  0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949,  0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 ,  0.45099396], [ 0.09939465, -0.40802586,  0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476,  0.06741077], [-0.19623031, -0.95031369,  0.24165596], [ 0.45153776, -0.87650371,  0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433,  0.20257032], [ 0.86954373, -0.49204889,  0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777,  0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134,  0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413,  0.48543158]])
u, v, w = gradients.T

# Put cones on the outside of the sphere
for direct in [-1, 1]:
    h = direct * 0.2 # Head length
    mlab.quiver3d(direct*u, direct*v, direct*w, h*u, h*v, h*w, color=(.2,.2,.2),
                  mode='cone', scale_factor=1, resolution=20)

mlab.show()

enter image description here

You could also make the sphere slightly larger as a fudge factor to get around the flat sides not exactly intersecting the base of some cones (e.g. points3d([0],[0],[0],[2.05],...)):

enter image description here

Another way to show this is a stereonet. Stereonets are a bit obscure, but they're a very good way to show distribution of orientations in 3D using a 2D plot.

import numpy as np
import matplotlib.pyplot as plt
import mplstereonet

gradients = np.array([[ 0.57735027, -0.57735027,  0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058,  0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662,  0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121,  0.22638878], [ 0.51285124, -0.09350922,  0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038,  0.87474549], [-0.11906644, -0.66461051,  0.73764223], [-0.69553846, -0.26904184,  0.66621518], [ 0.84464198, -0.38759068,  0.3692607 ], [ 0.01218729, -0.86413401,  0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422,  0.715783  ], [ 0.35672677, -0.81123132,  0.46330312], [-0.64859039, -0.59819001,  0.47063699], [-0.9860335 , -0.04110239,  0.16139577], [ 0.78665483, -0.298026  , -0.54069835], [-0.22515292, -0.39356178,  0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461,  0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807,  0.97246754], [-0.11702234, -0.10076906,  0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486,  0.64933801], [ 0.97685069, -0.17835225,  0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949,  0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 ,  0.45099396], [ 0.09939465, -0.40802586,  0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476,  0.06741077], [-0.19623031, -0.95031369,  0.24165596], [ 0.45153776, -0.87650371,  0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433,  0.20257032], [ 0.86954373, -0.49204889,  0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777,  0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134,  0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413,  0.48543158]])
x, y, z = gradients.T

fig, ax = mplstereonet.subplots(projection='equal_area')
ax.grid(True)
ax.pole(*mplstereonet.vector2pole(x,y,z))

plt.show()

enter image description here

like image 93
Joe Kington Avatar answered Jan 22 '23 07:01

Joe Kington