Use a color image for a surface plot

I know that this topic has already occured a few times in the forum but I still would like to ask if it is possible to use a color image for coloring a surface plot. Specifically, I do not understand why my code is not working :pensive:. Maybe you could help me out or at least explain me why it does not work :wink:.

As mentioned I am trying to use an image to color a surface. At first I am loading the image:

import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go


im = plt.imread('Peacock.jpg')[::5, ::5]  # reduce overall image size
plt.imshow(im)

image

Next, I make some surface according to some basically random function (in reality I have an image from a 3D microscope with its color image equivalent).

x = np.linspace(0, 5, im.shape[1])
y = np.linspace(5, 10, im.shape[0])
X, Y = np.meshgrid(x,y)
z = (X+Y)/(2+np.cos(X)*np.sin(Y))

fig = go.Figure(data=go.Surface(z=z))
fig.show()

Next, I transfer my image data to numbers between 0 and 1 with just constantly increasing values from one corner to the other:

im_first_channel = im[:, :, 0]

image_data =  (np.arange(im_first_channel.size).reshape(im_first_channel.shape)/im_first_channel.size).round(5)
image_data

So the image looks like this:

array([[0.0000e+00, 1.0000e-05, 2.0000e-05, ..., 2.4600e-03, 2.4700e-03,
        2.4800e-03],
       [2.4900e-03, 2.5000e-03, 2.5100e-03, ..., 4.9500e-03, 4.9600e-03,
        4.9700e-03],
       [4.9800e-03, 4.9800e-03, 4.9900e-03, ..., 7.4300e-03, 7.4400e-03,
        7.4500e-03],
       ...,
       [9.9254e-01, 9.9255e-01, 9.9256e-01, ..., 9.9500e-01, 9.9501e-01,
        9.9502e-01],
       [9.9502e-01, 9.9503e-01, 9.9504e-01, ..., 9.9748e-01, 9.9749e-01,
        9.9750e-01],
       [9.9751e-01, 9.9752e-01, 9.9753e-01, ..., 9.9997e-01, 9.9998e-01,
        1.0]])

Then I am making a color scale with always twice the same color where the colors are the color of each of the pixels in an ascending manner:

color_scale1 = [[val, f"rgb({r},{g},{b})"] for val, (r, g, b) in zip(image_data.flatten(), im.reshape((image_data.size, -1)))]

color_scale2 = [[round(val+1/z.size, 6), f"rgb({r},{g},{b})"] for val, (r, g, b) in zip(image_data.flatten(), im.reshape((image_data.size, -1)))]

color_scale = []

for val1, val2 in zip(color_scale1, color_scale2):
    color_scale.extend([val1, val2])
    
color_scale[-10:]

And then the color scale looks like this:

[[0.99995, 'rgb(51,51,25)'],
 [0.999959, 'rgb(51,51,25)'],
 [0.99996, 'rgb(32,33,0)'],
 [0.999969, 'rgb(32,33,0)'],
 [0.99997, 'rgb(44,52,13)'],
 [0.999979, 'rgb(44,52,13)'],
 [0.99998, 'rgb(49,60,20)'],
 [0.999989, 'rgb(49,60,20)'],
 [0.99999, 'rgb(31,33,12)'],
 [1.0, 'rgb(31,33,12)']]

So, from my understanding I now have a color scale with the colors of each pixel that is normalized between 0 and 1. With the new image that also has the pixels between 0 and 1 ascending the corresponding colors should be used for the surface, thus showing the image. (I am adding half of the difference between to values to the data so that I am in between the two constant color values). However, the output looks like this when I run the following code:

fig = go.Figure(data=go.Surface(z=z, 
                                colorscale=color_scale, 
                                surfacecolor=colors_values+1/z.size/2,
                                showscale=False))

fig.show()

I honestly do not understand why my approach is not working :worried:. At least, what you can see is that in the final image The color changes from one corner to the other but plotly does not seem to be using my color scale. Can someone tell me why it is not working?

Thanks in advance for your answers :blush:.

@SariO
Take a look at this procedure: https://github.com/empet/Texture-mapping-with-Plotly/blob/main/Texture-mapping-surface.ipynb

Thank you @empet for the link. So overall it’s not so straight forward to use an image to color a surface plot. However, still I do not know why my code is not working. If I just use the first an second colors for the color scale it’s not a problem and the code works as intended. I was thinking about maybe some maximum number of different colors in the color scale :thinking:

A colorscale should have the scale starting from 0, and going increasingly to 1. Your scale starts from 0.99995 and plotly doesn’t recognize your colorscale as a valid object. Unfortunately I have access only to my phone and canot run your code to understand how your method works.

Actually, my color scale starts from 0 - I am just printing the last ten entries here because otherwise it would be too long :sweat_smile: If the color scale does not start with 0 this will result in an error.