0

I am trying to plot a surface image which intersects a point cloud. Here is what I am hoping to get as an output: enter image description here Here is what I am getting: enter image description here As you can see, the surface seems to overwrite the scatter plot. I have tried dividing the scatter plot in two around the plane, like so:

break_idx = np.searchsorted(ts, img_ts)

ax.scatter(xs[0:break_idx], ts[0:break_idx], ys[0:break_idx], zdir='z',
   c=colors[0:break_idx], facecolors=colors[0:break_idx])

ax.plot_surface(y, img_ts, x, rstride=5, cstride=5, facecolors=img, alpha=1)

ax.scatter(xs[break_idx:-1], ts[break_idx:-1], ys[break_idx:-1], zdir='z',
   c=colors[break_idx:-1], facecolors=colors[break_idx:-1])

However this has not worked for me. Any ideas what might be going on?

+++ EDIT +++

Thanks to @JohanC for his suggestions, unfortunately it resulted in the same issue, visualized here in GIF format:

enter image description here

Judging by my results and the comments, this is just a bug/limitation of matplotlibs 3D plotting abilities. Ohwell, time to learn to use mayavi...

A working minimal example using mayavi can be found here, although the reason for it being on stackoverflow is that I am encountering similar issues.

Mr Squid
  • 1,196
  • 16
  • 34
  • 2
    Matplotlib not fully supports 3D plotting and ["My 3D plot doesn’t look right at certain viewing angles"](https://matplotlib.org/mpl_toolkits/mplot3d/faq.html) is a documented problem. Their suggestion is to move to [Mayavi](https://docs.enthought.com/mayavi/mayavi/) for more sophisticated 3D plotting capabilities. But maybe you can orientate the plot in a way that it looks kind of normal. – scleronomic Jul 12 '20 at 10:13
  • @sclermonomic Yes, the orienting would be a good idea, unfortunately the orientation that it bugs out with depends on the position of the image on the time axis -_- – Mr Squid Jul 15 '20 at 04:27
  • Did you try with the two scatter plots in reversed order? How did you create `img_ts`? – JohanC Jul 15 '20 at 10:17
  • @JohanC ts is a numpy array, so I just took img_ts = ts[len(ts)//2] as an arbitrary point for the above GIF. Yes, I tried with your code below. – Mr Squid Jul 15 '20 at 23:16
  • Some testing makes me think it doesn't work with images. Your question would be easier to handle if you also included code to create toy versions of the `x`, `y` and `img` arrays. You should also make a reference to where you got the method to draw an image inside a 3D plot, e.g. [this post](https://stackoverflow.com/questions/13570287/image-overlay-in-3d-plot-using-python/15592168#15592168). – JohanC Jul 16 '20 at 00:36

1 Answers1

2

Maybe your arrays ts, xs, ys and colors aren't nicely sorted via increasing ts? Also, np.searchsorted returns an array, so you can't directly use its result as[0:break_idx]. Probably you could take the first element of the returned array: break_idx = np.searchsorted(ts, img_ts)[0].

As stated in the comments, matplotlib doesn't support full 3D, and Mayavi is one of the usual recommendations if you need nice 3D hiding.

Nevertheless, in your example, if you don't move the viewing position behind the plane, you can first draw the points behind the plane, then the plane and then the points in front. This is what the code in the question tries to do.

To select the points in front of the plane, an easier solution is to just create a list of indices: front_indices = ts < img_ts. If this gives the wrong result, just swap < to >. Use [front_indices] to only select the values in the front, and [~front_indices] to select the values at the other side of the plane.

front_indices = ts < img_ts  # supposing ts is a numpy array, and img_ts is one special value

ax.scatter(xs[~front_indices], ts[~front_indices], ys[~front_indices], zdir='z',
   c=colors[~front_indices])

ax.plot_surface(y, img_ts, x, rstride=5, cstride=5, facecolors=img, alpha=1)

ax.scatter(xs[front_indices], ts[front_indices], ys[front_indices], zdir='z',
   c=colors[front_indices])

Here is a more complete example:

import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
import numpy as np

N = 10000
xs = np.random.uniform(4, 6, N)
ys = np.random.uniform(4, 6, N)
ts = np.random.uniform(1, 9, N)
colors = np.random.uniform(0, 1, N)
img_ts = 5

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

front_indices = ts < img_ts

ax.scatter(xs[~front_indices], ts[~front_indices], ys[~front_indices], zdir='z',
           c=colors[~front_indices], cmap='seismic', s=1)

# Draw a circle on the y=5 'wall'
p = Circle((5, 5), 4)
ax.add_patch(p)
art3d.pathpatch_2d_to_3d(p, z=img_ts, zdir="y")

ax.scatter(xs[front_indices], ts[front_indices], ys[front_indices], zdir='z',
           c=colors[front_indices], cmap='seismic', s=1)

ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
ax.set_xlabel('x')
ax.set_ylabel('t')
ax.set_zlabel('y')

ax.view_init(elev=15, azim=33)
plt.show()

example plot

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Yes the list is sorted. But I'll look into this Mayavi library, I hadn't heard of it. Thanks! – Mr Squid Jul 13 '20 at 05:14
  • No, my original code did the exact same (although this code is more elegant). The post was helpful, as it pointed me to the 'mayavi' library, but I have yet to implement a new solution using that. Will update once I have. – Mr Squid Jul 14 '20 at 23:15
  • Wow, that's very generous of you to fix up a working example to help me :) After trying what you suggested (and getting the same result as before), I think this might be a bug in matplotlib - see my edits above. I am going to try with `mayavi`, but I am having a similar issue! (https://stackoverflow.com/questions/62907389/mayavi-imshow-obscures-quiver3d) – Mr Squid Jul 15 '20 at 04:22