mikedh / trimesh

Python library for loading and using triangular meshes.
https://trimesh.org
MIT License
3.01k stars 582 forks source link

how to color vertices on meshes and how to visualize several meshes in a row with some background? #526

Closed sw-gong closed 5 years ago

sw-gong commented 5 years ago

Hi,

Thanks for the good library.

May I ask how to color vertices on meshes and how to visualize several meshes in a row with a specific background?

marcomusy commented 5 years ago

possible solution with vedo:

import trimesh
from vedo import Points, show

mesh = trimesh.creation.icosphere()
mesh.visual.face_colors = [200, 200, 250, 100]
n = mesh.vertices.shape[0]

#Assign a color based on a scalar and a color map
pc1 = Points(mesh.vertices, r=10)
pc1.cmap("jet", list(range(n)))

pc2 = Points(mesh.vertices, r=10)
pc2.cmap("viridis", mesh.vertices[:,2])

# Draw result on N=2 sync'd renderers
show([(mesh,pc1), (mesh,pc2)], N=2, axes=1)

Screenshot from 2019-08-05 21-59-52

sw-gong commented 5 years ago

possible solution with vtkplotter:

import trimesh

mesh = trimesh.creation.icosphere()
mesh.visual.face_colors = [200, 200, 250, 100]

from vtkplotter import *

n = mesh.vertices.shape[0]

pc1 = Points(mesh.vertices, r=10)
#Assign a color to each vertex individually, 
# format can be integer, tuple, string name:
# (method name will change in future release)
pc1.colorVerticesByArray(range(n)) 

#Assign a color based on a scalar and a color map
pc2 = Points(mesh.vertices, r=10)
pc2.pointColors(mesh.vertices[:,2], cmap='viridis')

# Draw result on N=2 sync'd renderers, white background
show([(mesh,pc1), (mesh,pc2)], N=2, bg="white", axes=1)

Screenshot from 2019-08-05 21-59-52

I have tried your code and it shows the below error: image

Do you know the reason?

sw-gong commented 5 years ago

image

Do you know how to draw 3d shapes like this? It includes drawing meshes in a row with a white background and color each vertex with some consistent values

mikedh commented 5 years ago

You could also use a trimesh.Scene object (or check out the widget example for something more advanced):

mikedh@cue:~$ ipython -i
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 19:07:31) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import trimesh

In [2]: meshes = [trimesh.creation.uv_sphere() for i in range(10)]

In [3]: for i, m in enumerate(meshes):
   ...:     m.apply_translation([0,0, i*2])
   ...:     m.visual.vertex_colors = trimesh.visual.random_color()
   ...:     

In [6]: trimesh.Scene(meshes).show()

balls

sw-gong commented 5 years ago

You could also use a trimesh.Scene object (or check out the widget example for something more advanced):

mikedh@cue:~$ ipython -i
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 19:07:31) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import trimesh

In [2]: meshes = [trimesh.creation.uv_sphere() for i in range(10)]

In [3]: for i, m in enumerate(meshes):
   ...:     m.apply_translation([0,0, i*2])
   ...:     m.visual.vertex_colors = trimesh.visual.random_color()
   ...:     

In [6]: trimesh.Scene(meshes).show()

balls

Awesome! One more question, If I know each vertex is related to some value, I want to color vertices based on these values (ideally, values from low to high are related to a color map, just like the image I attached in the last reply). Do you know how to draw like this way?

mikedh commented 5 years ago

Hey, yeah you could use trimesh.visual.interpolate for that:

In [1]: import trimesh

In [2]: import numpy as np

In [3]: meshes = [trimesh.creation.uv_sphere() for i in range(10)]

In [4]: trimesh.visual.interpolate?
Signature: trimesh.visual.interpolate(values, color_map=None, dtype=<class 'numpy.uint8'>)
Docstring:
Given a 1D list of values, return interpolated colors
for the range.

Parameters
---------------
values : (n, ) float
  Values to be interpolated over
color_map : None, or str
  Key to a colormap contained in:
  matplotlib.pyplot.colormaps()
  e.g: 'viridis'

Returns
-------------
interpolated : (n, 4) dtype
  Interpolated RGBA colors
File:      ~/trimesh/trimesh/visual/color.py
Type:      function

In [5]: for i, m in enumerate(meshes):
   ...:     m.vertices *= (np.random.random(3) + 1 ) * 2
   ...:     m.apply_translation([0,0, i*6])
   ...:     
   ...:     radii = np.linalg.norm(m.vertices - m.center_mass, axis=1)
   ...:     m.visual.vertex_colors = trimesh.visual.interpolate(radii, color_map='viridis')
   ...:     

In [6]: trimesh.Scene(meshes).show()

balls

marcomusy commented 5 years ago

I have tried your code and it shows the below error: image

Do you know the reason?

Oh, that's because jupyter notebook rendering does not support more than one rendering window (I will add a more meaningful error message).. You need to add embedWindow(False) in that case. Otherwise try: (this should also work in jupyter)


import trimesh
from vedo import trimesh2vedo, show

meshes = [trimesh.creation.uv_sphere().apply_translation([0,0,i*2]) for i in range(3)]

vmeshes = trimesh2vedo(meshes)

for i in range(3):
    scals = vmeshes[i].points()[:,i] # define some dummy point scalar
    vmeshes[i].cmap("jet", scals)

show(vmeshes, axes=1)

Screenshot from 2019-08-06 12-51-23

sw-gong commented 5 years ago

Hey, yeah you could use trimesh.visual.interpolate for that:

In [1]: import trimesh

In [2]: import numpy as np

In [3]: meshes = [trimesh.creation.uv_sphere() for i in range(10)]

In [4]: trimesh.visual.interpolate?
Signature: trimesh.visual.interpolate(values, color_map=None, dtype=<class 'numpy.uint8'>)
Docstring:
Given a 1D list of values, return interpolated colors
for the range.

Parameters
---------------
values : (n, ) float
  Values to be interpolated over
color_map : None, or str
  Key to a colormap contained in:
  matplotlib.pyplot.colormaps()
  e.g: 'viridis'

Returns
-------------
interpolated : (n, 4) dtype
  Interpolated RGBA colors
File:      ~/trimesh/trimesh/visual/color.py
Type:      function

In [5]: for i, m in enumerate(meshes):
   ...:     m.vertices *= (np.random.random(3) + 1 ) * 2
   ...:     m.apply_translation([0,0, i*6])
   ...:     
   ...:     radii = np.linalg.norm(m.vertices - m.center_mass, axis=1)
   ...:     m.visual.vertex_colors = trimesh.visual.interpolate(radii, color_map='viridis')
   ...:     

In [6]: trimesh.Scene(meshes).show()

balls

Hi,

After trying it, I still have a few questions:

jrdkr commented 5 years ago

Hey, first of all thank you for this awesome library!!! I am also very interested in a visualiziation of the colorbar.

gauzias commented 5 years ago

Hi, here is a function to plot a separate figure with the colormap before showing the pyglet figure. It would clearly be better to plot the colorbar within the same figure, but I don't know how to do that.

Hope it helps

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

def pyglet_plot(mesh, values=None, color_map=None, plot_colormap=False, caption=None): """ Visualize a trimesh object using pyglet as proposed in trimesh the added value is for texture visualization :param mesh: trimesh object :param values: numpy array of a texture to be visualized on the mesh :param color_map: str, matplotlib colormap, default is 'jet' :param plot_colormap: Boolean, if True use matplotlib to plot the colorbar of the map on a separate figure :param caption: Title of window :return: """

if values is not None:
    smooth = True
    if color_map is None:
        color_map = 'jet'

    vect_col_map = \
        trimesh.visual.color.interpolate(values, color_map=color_map)

    if values.shape[0] == mesh.vertices.shape[0]:
        mesh.visual.vertex_colors = vect_col_map
    elif values.shape[0] == mesh.faces.shape[0]:
        mesh.visual.face_colors = vect_col_map
        smooth = False

    if plot_colormap:
        gradient = np.linspace(0, 1, 256)
        gradient = np.vstack((gradient, gradient))
        fig, ax = plt.subplots(1, 1)
        # fig.subplots_adjust(top=0.95, bottom=0.05, left=0.01, right=0.99)

        ax.set_title(caption)
        ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(color_map))
        pos = list(ax.get_position().bounds)
        y_text = pos[1] + pos[3] / 2.
        fig.text(pos[0] - 0.01, y_text,
                 '{:0.0000009f}'.format(np.min(values)),
                 va='center', ha='right', fontsize=15, color='k')
        fig.text(pos[2] + pos[0] + 0.01, y_text,
                 '{:0.0000009f}'.format(np.max(values)),
                 va='center', fontsize=15, color='k')
        ax.set_axis_off()
        plt.show()

# call the default trimesh visualization tool using pyglet
mesh.show(caption=caption, smooth=smooth)
jrdkr commented 5 years ago

Hey Gauzias, Thank you! I also noticed the tutorials for the vtkplotter library e.g. : https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/moving_least_squares3D.py that helped a lot! Greetings, Julian

marcomusy commented 5 years ago

Hey Gauzias, Thank you! I also noticed the tutorials for the vtkplotter library e.g. : https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/moving_least_squares3D.py that helped a lot! Greetings, Julian

you can add a 2D scalarbar with e.g.:

import trimesh

meshes = [trimesh.creation.uv_sphere().apply_translation([0,0,i*2]) for i in range(3)]

from vtkplotter import trimesh2vtk, show

vtkmeshes = [trimesh2vtk(m) for m in meshes]

for i in range(3):
    scals = vtkmeshes[i].coordinates()[:,i] # define some dummy point scalar
    vtkmeshes[i].pointColors(scals, cmap='jet')

vtkmeshes[0].addScalarBar(title="my scalarbar")

show(vtkmeshes, bg='w', axes=1)

Screenshot from 2019-08-09 15-38-54

sw-gong commented 5 years ago

Hey Gauzias, Thank you! I also noticed the tutorials for the vtkplotter library e.g. : https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/moving_least_squares3D.py that helped a lot! Greetings, Julian

you can add a 2D scalarbar with e.g.:

import trimesh

meshes = [trimesh.creation.uv_sphere().apply_translation([0,0,i*2]) for i in range(3)]

from vtkplotter import trimesh2vtk, show

vtkmeshes = [trimesh2vtk(m) for m in meshes]

for i in range(3):
  scals = vtkmeshes[i].coordinates()[:,i] # define some dummy point scalar
  vtkmeshes[i].pointColors(scals, cmap='jet')

vtkmeshes[0].addScalarBar(title="my scalarbar")

show(vtkmeshes, bg='w', axes=1)

Screenshot from 2019-08-09 15-38-54

Hi @marcomusy ,

I installed vtkplotter with pip install vtkplotter, but it shows the error ImportError: cannot import name 'trimesh2vtk'. Do you know why it is? Additionally, can you plot things without the background (only white) if using your library?

marcomusy commented 5 years ago

sorry, can you try:

from vtkplotter import show
from vtkplotter.trimesh import trimesh2vtk

# to eliminate axes
show(vtkmeshes, bg='white', axes=0)
jrdkr commented 5 years ago

Hey Gauzias, Thank you! I also noticed the tutorials for the vtkplotter library e.g. : https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/moving_least_squares3D.py that helped a lot! Greetings, Julian

you can add a 2D scalarbar with e.g.:

import trimesh

meshes = [trimesh.creation.uv_sphere().apply_translation([0,0,i*2]) for i in range(3)]

from vtkplotter import trimesh2vtk, show

vtkmeshes = [trimesh2vtk(m) for m in meshes]

for i in range(3):
    scals = vtkmeshes[i].coordinates()[:,i] # define some dummy point scalar
    vtkmeshes[i].pointColors(scals, cmap='jet')

vtkmeshes[0].addScalarBar(title="my scalarbar")

show(vtkmeshes, bg='w', axes=1)

Screenshot from 2019-08-09 15-38-54

Hi @marcomusy ,

I installed vtkplotter with pip install vtkplotter, but it shows the error ImportError: cannot import name 'trimesh2vtk'. Do you know why it is? Additionally, can you plot things without the background (only white) if using your library?

Had the same problem. You have to clone the current release here from github. in the version of pip these file are missing yet. And it is possible to set a white background. See the following tutorial:

https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/fitspheres1.py

sw-gong commented 5 years ago

sorry, can you try:

from vtkplotter import show
from vtkplotter.trimesh import trimesh2vtk

# to eliminate axes
show(vtkmeshes, bg='white', axes=0)

Thanks!

image

It works and looks really nice!

Does the viewer support actions like the viewer used in trimesh like below? image

How to export this mesh or this view?

Addtionally, when view from jupyter notebook, I set embedWindow(False), the kernel died every time.

marcomusy commented 5 years ago

@sw-gong

Does the viewer support actions like the viewer used in trimesh like below?

Yes, press h in window and it will show (in terminal) a helper message with all different options

How to export this mesh or this view?

Press S to a png screenshot or E to export all meshes to a single numpy file (this is still experimental, different color mappings are not yet supported). Or can save individual meshes in various formats with e.g. actor.write('mesh.ply')

Addtionally, when view from jupyter notebook, I set embedWindow(False), the kernel died every time.

Thanks I will investigate the issue..

sw-gong commented 5 years ago

Hi @marcomusy,

Is that possible to export mesh (.ply or other file types which can be loaded by meshlab directly) with colored vertices via vtkplotter?

mikedh commented 5 years ago

Hey, if the colors are set in mesh.visual.vertex_colors they should export when you save it as ply. If they are set, mesh.visual.kind == 'vertex' should be true.

marcomusy commented 5 years ago

@sw-gong sorry for the late reply, yes - you should be able to do it with the latest version: pip install --upgrade vedo

then

from trimesh.creation import uv_sphere

meshes = [uv_sphere().apply_translation([0,i*2.1,0]) for i in range(3)]

###############################################
from vedo import trimesh2vedo, show

vmeshes = trimesh2vedo(meshes)

cmaps = ('jet', 'PuOr', 'viridis')
for i in range(3):
    scals = vmeshes[i].points()[:,i] # define some dummy point scalar
    vmeshes[i].cmap(cmaps[i], scals).lighting('plastic')

# add a 2D scalar bar to a mesh
vmeshes[0].addScalarBar(title="my scalarbar\nnumber #0", c='k')

# add 3D scalar bars
vmeshes[1].addScalarBar3D(pos=(1.2,2.1,-1.0), c='k')

show(vmeshes, axes=1)

image note that

Thanks for reporting and for your feedback, Best

sw-gong commented 5 years ago

Thanks! @marcomusy @mikedh

ZENGYIMING-EAMON commented 2 years ago

Hey guys! Your images are so great! Is it possible to show them screenless (as folder/xxx.png) when using a remote server without x11 or screen? (instead of a pop-up window every time)

marcomusy commented 2 years ago

@ZENGYIMING-EAMON yes check out https://github.com/marcomusy/vedo/issues/64

miaoYuanyuan commented 1 year ago

how can i calculate the error? np.linalg.norm(mesh.vertices-label.vertices,axis=1)?

Nibenitez commented 1 year ago

@sw-gong Do you have the script to create the face heatmap? I am working on a 3d face reconstruction project, so I am wonder how I can have my error distances results calculated and displayed as a heatmap.

image

FarzanehJafari1987 commented 3 months ago
from vtkplotter import show
from vtkplotter.trimesh import trimesh2vtk

# to eliminate axes
show(vtkmeshes, bg='white', axes=0)

@sw-gong Do you have the script to create the face heatmap? I am working on a 3d face reconstruction project, so I am wonder how I can have my error distances results calculated and displayed as a heatmap.

image

I have the same issue. Could find a way to visualize the distance results?

marcomusy commented 3 months ago

I have the same issue. Could find a way to visualize the distance results?

Try:

import numpy as np
from trimesh.creation import uv_sphere
from vedo import Mesh, show

tmsh = uv_sphere() # trimesh object
vmsh = Mesh(tmsh)  # vedo object

# Add a scalar field to the mesh 
scalar_field = np.random.randn(vmsh.npoints)
vmsh.cmap("viridis", scalar_field)
vmsh.add_scalarbar3d(title="residuals")

show(vmsh, axes=1)

image