marcomusy / vedo

A python module for scientific analysis of 3D data based on VTK and Numpy
https://vedo.embl.es
MIT License
2.03k stars 265 forks source link

Lines.cmap maps to incorrect color when using `vtkLookupTable` as `input_cmap` #1025

Open tomas16 opened 8 months ago

tomas16 commented 8 months ago

Description

import numpy as np
from vedo import build_lut, Lines, Plotter, Points

colors = [
    "black",
    "lightgreen",
    "orange",
    "yellow",
    "green",
    "lightblue",
    "pink",
    "red",
    "cyan",
    "yellow",
]
assert len(colors) == 10

xyz = np.concatenate((np.linspace(0, 110, 11)[:, None], np.ones((11, 2))),
                     axis=-1)
xyz_segments = np.stack((xyz[:-1, :], xyz[1:, :]), axis=1)
lines = Lines(xyz_segments, lw=2)

if False:
    # correct colors
    clut = colors
else:
    # incorrect colors
    clut = build_lut(
        [(index, color, 1.0) for index, color in enumerate(colors)],
        vmin=0,
        vmax=9,
        interpolate=False
    )
lines.cmap(clut, np.arange(10), on="cells")

plt = Plotter()
plt += Points(xyz, c='black', r=10)
plt += lines
plt.show(axes=False, interactive=True)

When the conditional is True and we're passing a list of colors, the behavior is correct:

Screenshot 2024-01-17 at 6 11 29 PM

With the vtkLookupTable output from build_lut, some of the colors are missing/repeated:

Screenshot 2024-01-17 at 7 06 20 PM

Versions

marcomusy commented 8 months ago

Thanks @tomas16 for reporting and for the very clear explanation of the issue. I think there is a mismatch in the convention by which the LUT is manually created: in build_lut the entries refer to the color the object should get up to that specific value, E.g.

pip install -U git+https://github.com/marcomusy/vedo.git

then

from vedo import *

colors = [
    "black",
    "lightgreen",
    "orange",
    "yellow",
    "green",
    "lightblue",
    "pink",
    "red",
    "cyan",
    "yellow",
    "blue",
    "tomato",
    "violet",
    "brown",
]
n = len(colors)
vals = np.arange(n)

xyz = np.concatenate((np.linspace(0, n, n+1)[:, None], np.ones((n+1, 2))), axis=-1)
xyz_segments = np.stack((xyz[:-1, :], xyz[1:, :]), axis=1)
lines = Lines(xyz_segments, lw=8)

if 0:
    clut = colors
else:
    ctable = [(index+1, color) for index, color in enumerate(colors)]
    clut = build_lut(ctable, vmin=vals[0])

print("range:", vals)
print("table:", ctable)

lines.cmap(clut, vals, on="cells")
pts = Points(xyz, c='black', r=8)

plt = Plotter()
plt += [pts, pts.labels2d('id'), lines]
plt.show(axes=False, size=(1200,300), zoom=3.5)

Up to 14 it's brown Up to 13 is violet ...etc

Screenshot from 2024-01-18 12-51-23

I must think if it's worth changing this convention to make the thing more intuitive... plus my last commit still breaks examples/volumetric/earth_model.py ..so some other bug might still be there.

tomas16 commented 8 months ago

Thanks @marcomusy for the quick resolution!

In my actual application the colors were also wrong when I simply provided a list of colors, as in the "correct" case in my code snippet above. Unfortunately I can't reproduce this easily. I'm now using your patch and this snippet for generating the LUT and it seems to work correctly:

clut = build_lut(
    [(index + 1, color) for index, color in enumerate(colors)],
    vmin=0
)

Note it's important to provide the vmin=0 part.

marcomusy commented 8 months ago

colors were also wrong when I simply provided a list of colors

OK this is an interesting piece of information... there must be a combination of a precision error + some other problem... I will keep investigating! Thanks