15

I am using the MayaVi Python library to plot 3d points, using the points3d class. The documentation specifies that the colour of each point is specified through a fourth argument, s:

In addition, you can pass a fourth array s of the same shape as x, y, and z giving an associated scalar value for each point, or a function f(x, y, z) returning the scalar value. This scalar value can be used to modulate the color and the size of the points.

This specifies a scalar value for each point, which maps the point to a colourmap, such as copper, jet or hsv. E.g. from their documentation:

import numpy
from mayavi.mlab import *

def test_points3d():
    t = numpy.linspace(0, 4*numpy.pi, 20)
    cos = numpy.cos
    sin = numpy.sin

    x = sin(2*t)
    y = cos(t)
    z = cos(2*t)
    s = 2+sin(t)

    return points3d(x, y, z, s, colormap="copper", scale_factor=.25)

Gives:

enter image description here

Instead, I would like to specify the actual value for each point as an (r, g, b) tuple. Is this possible in MayaVi? I have tried replacing the s with an array of tuples, but an error is thrown.

Bill Cheatham
  • 11,396
  • 17
  • 69
  • 104

4 Answers4

17

After struggling with this for most of today, I found a relatively simple way to do exactly what the question asks -- specify an RGB tuple for each point. The trick is just to define a color map with exactly the same number of entries as there are points to plot, and then set the argument to be a list of indices:

# Imports
import numpy as np
from mayavi.mlab import quiver3d, draw

# Primitives
N = 200 # Number of points
ones = np.ones(N)
scalars = np.arange(N) # Key point: set an integer for each point

# Define color table (including alpha), which must be uint8 and [0,255]
colors = (np.random.random((N, 4))*255).astype(np.uint8)
colors[:,-1] = 255 # No transparency

# Define coordinates and points
x, y, z = colors[:,0], colors[:,1], colors[:,2] # Assign x, y, z values to match color
pts = quiver3d(x, y, z, ones, ones, ones, scalars=scalars, mode='sphere') # Create points
pts.glyph.color_mode = 'color_by_scalar' # Color by scalar

# Set look-up table and redraw
pts.module_manager.scalar_lut_manager.lut.table = colors
draw()
Cliff Kerr
  • 311
  • 3
  • 4
  • 2
    For anyone wanting to do this on other kind of plots than quiver; it is the second last line which is the important one; i.e. you can do `surf.module_manager.scalar_lut_manager.lut.table = colors` as well. – RolKau Aug 07 '15 at 18:07
9

I've found a better way to set the colors directly.

You can create your own direct LUT pretty easily. Let's say we want 256**3 granularity:

#create direct grid as 256**3 x 4 array 
def create_8bit_rgb_lut():
    xl = numpy.mgrid[0:256, 0:256, 0:256]
    lut = numpy.vstack((xl[0].reshape(1, 256**3),
                        xl[1].reshape(1, 256**3),
                        xl[2].reshape(1, 256**3),
                        255 * numpy.ones((1, 256**3)))).T
    return lut.astype('int32')

# indexing function to above grid
def rgb_2_scalar_idx(r, g, b):
    return 256**2 *r + 256 * g + b

#N x 3 colors. <This is where you are storing your custom colors in RGB>
colors = numpy.array([_.color for _ in points])

#N scalars
scalars = numpy.zeros((colors.shape[0],))

for (kp_idx, kp_c) in enumerate(colors):
    scalars[kp_idx] = rgb_2_scalar_idx(kp_c[0], kp_c[1], kp_c[2])

rgb_lut = create_8bit_rgb_lut()

points_mlab = mayavi.mlab.points3d(x, y, z, scalars, mode='point')

#magic to modify lookup table 
points_mlab.module_manager.scalar_lut_manager.lut._vtk_obj.SetTableRange(0, rgb_lut.shape[0])
points_mlab.module_manager.scalar_lut_manager.lut.number_of_colors = rgb_lut.shape[0]
points_mlab.module_manager.scalar_lut_manager.lut.table = rgb_lut
eqzx
  • 5,323
  • 4
  • 37
  • 54
6

You can use a rgb look up table and map your rgb values to it using whatever logic you want. Here's a simple example:

import numpy, random
from mayavi.mlab import *

def cMap(x,y,z):
    #whatever logic you want for colors
    return [random.random() for i in x]

def test_points3d():
    t = numpy.linspace(0, 4*numpy.pi, 20)
    cos = numpy.cos
    sin = numpy.sin

    x = sin(2*t)
    y = cos(t)
    z = cos(2*t)
    s = cMap(x,y,z)

    return points3d(x, y, z, s, colormap="spectral", scale_factor=0.25)

test_points3d()

I have no idea what color scheme you want, but you can evaluate the positions of x,y,z and return whatever scalar corresponds to the rgb value you are seeking.

Chrismit
  • 1,488
  • 14
  • 23
  • Is there a way to do this to produce an arbitrary 0-255 RGB value, without specifying a unique colormap with 256x256x256 values? Put another way, in your solution, is there a way to characterize the inverse Spectral() function, where Spectral() is a function with domain [0,1] and range 0-255,0-255,0-255? This behavior has always irked me, and I have seen some applications get around it (for example the vtk program trackvis, whose owner has declined to release the source to me so I can see how he does it). – aestrivex Sep 03 '13 at 19:10
  • You can explore mayavi.core.lut_manager, in particular pylab_luts within mayavi.core.lut_manager to see how these are built (and to presumably build your own and assign it with whatever behavior you desire) – Chrismit Sep 03 '13 at 19:38
  • I have played with these. Working with custom luts to specify a desired color scheme is far less general of a solution than specifying a unique RGB value for each vertex where a scalar is specified. Computationally speaking that isn't true, because it is possible to specify a LUT with 256x256x256 values that uniquely defines the RGB color spectrum. But it would be an extraordinary pain to actually do that. I think the right answer to this question is "sorry, there is no right way to do this." – aestrivex Sep 05 '13 at 19:24
  • Thanks, I think this is the only way to go. It's a shame the colour can't be directly controlled, but this is a suitable hack for many purposes... – Bill Cheatham Sep 09 '13 at 18:32
  • 4
    This makes me cry a little. Is there any other package that can achieve this? – Andreas Mueller Sep 10 '13 at 14:48
  • It seems no way to specify individually sizes and color is possible... how bad! – linello Mar 07 '14 at 14:40
  • 1
    I agree with Andreas, I also took the fetas position and started weeping after I realise the API doesn't have any clear/default way of custom colours. This is my solution: http://stackoverflow.com/questions/24471210/how-to-draw-a-color-image-in-mayavi-imshow – Simon Streicher Jul 01 '14 at 09:40
  • @aestrivex it wasn't too painful (see my answer) – eqzx Feb 23 '15 at 23:47
3

This can now simply be done with the color argument

from mayavi import mlab
import numpy as np

c = np.random.rand(200, 3)
r = np.random.rand(200) / 10.

mlab.points3d(c[:, 0], c[:, 1], c[:, 2], r, color=(0.2, 0.4, 0.5))

mlab.show()

enter image description here

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249