6

I am rather new to matplotlib (and this is also my first question here). I'm trying to represent the scalp surface potential as recorded by an EEG. So far I have a two-dimensional figure of a sphere projection, which I generated using contourf, and pretty much boils down to an ordinary heat map.

Is there any way this can be done on half a sphere?, i.e. generating a 3D sphere with surface colours given by a list of values? Something like this, http://embal.gforge.inria.fr/img/inverse.jpg, but I have more than enough with just half a sphere.

I have seen a few related questions (for example, Matplotlib 3d colour plot - is it possible?), but they either don't really address my question or remain unanswered to date.

I have also spent the morning looking through countless examples. In most of what I've found, the colour at one particular point of a surface is indicative of its Z value, but I don't want that... I want to draw the surface, then specify the colours with the data I have.

Community
  • 1
  • 1
lambda
  • 63
  • 1
  • 5

1 Answers1

13

You can use plot_trisurf and assign a custom field to the underlying ScalarMappable through set_array method.

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri

(n, m) = (250, 250)

# Meshing a unit sphere according to n, m 
theta = np.linspace(0, 2 * np.pi, num=n, endpoint=False)
phi = np.linspace(np.pi * (-0.5 + 1./(m+1)), np.pi*0.5, num=m, endpoint=False)
theta, phi = np.meshgrid(theta, phi)
theta, phi = theta.ravel(), phi.ravel()
theta = np.append(theta, [0.]) # Adding the north pole...
phi = np.append(phi, [np.pi*0.5])
mesh_x, mesh_y = ((np.pi*0.5 - phi)*np.cos(theta), (np.pi*0.5 - phi)*np.sin(theta))
triangles = mtri.Triangulation(mesh_x, mesh_y).triangles
x, y, z = np.cos(phi)*np.cos(theta), np.cos(phi)*np.sin(theta), np.sin(phi)

# Defining a custom color scalar field
vals = np.sin(6*phi) * np.sin(3*theta)
colors = np.mean(vals[triangles], axis=1)

# Plotting
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
cmap = plt.get_cmap('Blues')
triang = mtri.Triangulation(x, y, triangles)
collec = ax.plot_trisurf(triang, z, cmap=cmap, shade=False, linewidth=0.)
collec.set_array(colors)
collec.autoscale()
plt.show()

enter image description here

U3.1415926
  • 812
  • 12
  • 30
GBy
  • 1,719
  • 13
  • 19
  • Thank you. It is not working right now, but I think it's because I'm on version 1.1.1. I'll try to upgrade and if this was the problem I'll accept your answer immediately. – lambda Jun 15 '14 at 15:42
  • 1
    Just in case someone runs into the same problem in the future, this solution does need the latest (as of now, 1.3.1) version of matplotlib. In addition, is there any way for the colours NOT to be a function of the location? I have been trying to pass a list of values to your solution, but I can't make it work. – lambda Jun 15 '14 at 19:50
  • 1
    Thanks for the precision. Regarding your question, it is indeed possible: simply pass a 1d array of size ntri (number of triangles) to `collec.set_array`. In case you know only nodal values, you will have to average them by triangles, which is what I do in the code block "# Defining a custom color scalar field". Or is there anything I am missing ? – GBy Jun 15 '14 at 21:27
  • Nice answer - however I am having issues when the number of triangles is large (say around 70,000) in this case my computer gets some kind of memory issue when trying to plot and the program dies. – Dipole Jul 19 '17 at 00:24
  • Thank you so much! This `collec.set_array(colors)` is like magic! – CodingNow Nov 19 '19 at 05:40
  • Thank you for this answer! I see your sphere is not connected at the south pole, is there anyway around this? like in the triangulation step, is there a way to make the boundaries identified? – A. Jahin Jun 25 '20 at 15:04