0

Is there a better way to plot a plane with image in 3D in matplotlib than this:

xx, yy = np.meshgrid(np.linspace(0, 1, img_size[1]), np.linspace(0, 1, img_size[0]))
zz     = np.ones((img_size[0],img_size[1]))
ax.plot_surface(xx, yy, zz, rstride=1, cstride=1, facecolors=img / 255, shade=False)

I dont want to create a surface with as many faces as I have pixels, since that quite inefficient. Is there a better way to do so?

This is what my plot looks like:

enter image description here

jared
  • 4,165
  • 1
  • 8
  • 31
mojado
  • 370
  • 1
  • 12
  • Please, attach result. And, This question is subjective. Somebody says that way worse, somebody says better. – Volk Aug 09 '23 at 10:44
  • The result is like expected! I am looking for alternatives. The problem is, that on a "big" image the meshgrid is getting huge and thus the looking at the plot result in plt.show() is slow/lags. – mojado Aug 09 '23 at 10:58
  • Please share a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – jared Aug 09 '23 at 13:48

1 Answers1

2

A simple one-liner method is to subsample as follows: img_smaller = img[::10, ::10, :]. This will subsample it down 10x, but the image will look coarse as no interpolation was applied, and edges may look clipped if the dimensions aren't multiples of 10:

Before downsampling:

enter image description here

Downsampling using array slicing:

enter image description here

Another approach is to downsample the image after loading it using PIL. After setting downsample=12, i.e. 12x downsampling:

enter image description here

PIL downsampling:

from PIL import Image

#Load image, or make a test image
np.random.seed(0)
img = Image.fromarray( np.random.randint(0, 255, size=(100, 100, 3), dtype=np.ubyte))
img = Image.open('../image.png')

#Subsample 12x and apply interpolation
downsample = 12
img_small = img.resize((img.height // downsample, img.width // downsample),
                       resample=Image.BICUBIC)
h, w = img_small.height, img_small.width
img_small_arr = np.asarray(img_small)

#Plot
ax = plt.figure(figsize=(3, 3)).add_subplot(projection='3d')
xx, yy = np.meshgrid(np.linspace(0, 1, w), np.linspace(0, 1, h))
zz     = np.ones((h, w))
ax.plot_surface(xx, yy, zz, facecolors=img_small_arr / 255, shade=False)
                #facecolor=[0,0,0,0], linewidth=1) #optionally add a 'grid' effect

One-liner method using slicing:

#Downsample using slicing
img_small_arr = np.asarray(img)[::10, ::10, :]

#New dimensions
h, w, _ = img_small_arr.shape

#Rest of the plotting, as before
ax = plt.figure(figsize=(3, 3)).add_subplot(projection='3d')
xx, yy = np.meshgrid(np.linspace(0, 1, w), np.linspace(0, 1, h))
zz     = np.ones((h, w))
ax.plot_surface(xx, yy, zz, facecolors=img_small_arr / 255, shade=False)
some3128
  • 1,430
  • 1
  • 2
  • 8
  • Thanks, thats what I currently do! I was wondering if there is an alternative to plot_surface, or that there is actually a possibility to have just 2 faces for the plane and still map the whole image on the surface. Because I do not want to lose image quality. So there is probably no texture mapping – mojado Aug 10 '23 at 07:38
  • I see. For `matplotlib` seems to me like such functionality isn't available. The `pyvista` library does texture mapping: https://docs.pyvista.org/version/stable/examples/02-plot/texture.html You can export some data back into `matplotlib` using the steps described here, though not sure if it'll resolve your exact problem: https://github.com/pyvista/pyvista-support/issues/70 – some3128 Aug 10 '23 at 10:55