I'm trying to plot a surface in 3D from a set of data which specifies the z-values. I get some weird transparency artefact though, where I can see through the surface, even though I set alpha=1.0.
The artefact is present both when plotting and when saved to file (both as png and pdf):
I have tried changing the line width, and changing the number of strides from 1 to 10 (in the latter case, the surface is not visible though due to too rough resolution).
Q: How can I get rid of this transparency?
Here is my code:
import sys
import numpy as np
import numpy.ma as ma
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
y_label = r'x'
x_label = r'y'
z_label = r'z'
x_scale = 2.0*np.pi
y_scale = 2.0*np.pi
y_numPoints = 250
x_numPoints = 250
def quasiCrystal(x, y):
z = 0
for i in range(0,5):
z += np.sin(x * np.cos(float(i)*np.pi/5.0) +
y * np.sin(float(i)*np.pi/5.0))
return z
x = np.linspace(-x_scale, x_scale, x_numPoints)
y = np.linspace(-y_scale, y_scale, y_numPoints)
X,Y = np.meshgrid(x,y)
Z = quasiCrystal(X, Y)
f = plt.figure()
ax = f.gca(projection='3d')
surf = ax.plot_surface( X, Y, Z,
rstride=5, cstride=5,
cmap='seismic',
alpha=1,
linewidth=0,
antialiased=True,
vmin=np.min(Z),
vmax=np.max(Z)
)
ax.set_zlim3d(np.min(Z), np.max(Z))
f.colorbar(surf, label=z_label)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
ax.set_zlabel(z_label)
plt.show()
Here is another picture of my actual data where it is easier to see the artefact:
Matplotlib is not a "real" 3D engine. This is a very well known problem and once in a while a similar question to yours appears appears (see this and this). The problem is that the same artefact can originate problems that seem to be different. I believe such is the case for you.
Before going on with my recommendations let me just quote this information from the maplotlib website:
My 3D plot doesn’t look right at certain viewing angles
This is probably the most commonly reported issue with mplot3d. The problem is
that – from some viewing angles – a 3D object would appear in front of
another object, even though it is physically behind it. This can
result in plots that do not look “physically correct.”
Unfortunately, while some work is being done to reduce the occurance
of this artifact, it is currently an intractable problem, and can not
be fully solved until matplotlib supports 3D graphics rendering at its
core.
The problem occurs due to the reduction of 3D data down to 2D +
z-order scalar. A single value represents the 3rd dimension for all
parts of 3D objects in a collection. Therefore, when the bounding
boxes of two collections intersect, it becomes possible for this
artifact to occur. Furthermore, the intersection of two 3D objects
(such as polygons or patches) can not be rendered properly in
matplotlib’s 2D rendering engine.
This problem will likely not be solved until OpenGL support is added
to all of the backends (patches are greatly welcomed). Until then, if
you need complex 3D scenes, we recommend using MayaVi.
It seems that Mayavi has finally moved on to Python 3, so its certainly a possibility. If you want to stick with matplotlib for this kind of plot my advice is that you work with rstride and cstride values to see which ones produce a plot satisfactory to you.
surf = ax.plot_surface( X, Y, Z,
rstride=5, cstride=5,
cmap='jet',
alpha=1,
linewidth=0,
antialiased=True,
vmin=0,
rstride=10,
cstride=10,
vmax=z_scale
)
Other possibility is to try to see if other kinds of 3D plots do better. Check plot_trisurf, contour or contourf. I know its not ideal but in the past I also managed to circumvent other type of artefacts using 3D polygons.
Sorry for not having a more satisfactory answer. Perhaps other SO users have better solutions for this. Best of luck.
I ran into some similar issues and found that they were antialiasing artifacts and could be fixed by setting antialiased=False in plot_surface.
Related
This is my first question here so I'd appreciate if you go easy on me.
I'm a total newbie in python and in programming in general to be honest. My main passion is astrophotography and my working horse is a very old program "Iris" written by Cristian Buil.
Recently I took spherical panorama and I wanted to represent it in Hammer-Aitoff projection. Iris is able to do that but it struggles with very large images. Therefore I thought that I could try to use a Python for this task.
During my research of this question I found that Matplotlib library seems to be designed for this. In particular Basemap tool directly offers the features necessary such as projection selection, image warping e.t.c.
I've tried to cope with the manuals but the longer I've played with the examples the more my frustration grew. It seems that only half of the examples work for me! Although I've replicated al the steps and installed the libraries necessary.
For example if I try to replicate these two examples from here, the first one returns an empty circle and the second is working properly:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib import image
import matplotlib.image as mpimg
import numpy as np
#empty circle
plt.figure(figsize=(8, 8))
m = Basemap(projection='ortho', resolution=None, lat_0=50, lon_0=-100)
m.bluemarble(scale=0.5);
#proper map
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None,
width=8E6, height=8E6,
lat_0=45, lon_0=-100,)
m.etopo(scale=0.5, alpha=0.5)
#it works even if I change "m.etopo(scale=0.5, alpha=0.5)" by "m.bluemarble(scale=0.5);"
#so it seems that the problem in the projection and not something else
# Map (long, lat) to (x, y) for plotting
x, y = m(-122.3, 47.6)
plt.plot(x, y, 'ok', markersize=5)
plt.text(x, y, ' Seattle', fontsize=12);
with the image warping I also have the same inconsistent behavior. Lambert projection works fine but the hammer or mollweide projections return empty ellipses.
#works just fine
m = Basemap(width=20000000,height=10000000,projection='lcc', resolution=None,lat_1=-55.,lat_2=-55,lat_0=-0,lon_0=30.)
m.warpimage(image="DJI_0114.png")
plt.show()
#empty shells in the output
m = Basemap(projection='hammer', resolution=None, lat_0=0,lon_0=0.)
m = Basemap(projection='mall', resolution=None, lat_0=0,lon_0=0.)
#There are no width and height fields here since python shows this message:
#"warning: width and height keywords ignored for Hammer projection"
m.warpimage(image="DJI_0114.png")
Manuals say that the image has to cover the whole sky/ground i.e. be in equirectangular projection with the aspect ratio of 2:1. So it is.
Is it something wrong with me/my_code or is there some mistake in the library?.. I bet that I'm doing something wrong but I can't see what. So any help would be very welcomed!
I am currently taking a Matplotlib class. I was given an image to create the image as a 3D subplot 4 times at 4 different angles. It's a linear plot. As the data changes the plots change colors. As it's an image, I'm not certain where the actual changes start. I don't want an exact answer, just an explanation of how this would work. I have found many methods for doing this for a small list but this has 75 data points and I can't seem to do it without adding 75 entries.
I've also tried to understand cmap but I am confused on it as well.
Also, it needs to done without Seaborn.
This is part of the photo.
I am finding your question a little bit hard to understand. What I think you need is a function to map the input x/y argument onto a colour in your chosen colour map. See the below example:
import numpy as np
import matplotlib.pyplot
def number_to_colour(number, total_number):
return plt.cm.rainbow(np.linspace(0,1.,total_number))[list(number)]
x = np.arange(12)
y = x*-3.
z = x
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c=number_to_colour(x, len(x)))
plt.show()
plt.cm.rainbow(np.linspace(0,1.,total_number)) creates an array of colours of length total_number evenly spaced spaced across the colour map (in this case rainbow). Modifying the indexing of this array (or changing np.linspace to another function with the desired scaling), should give you the colour scaling that you need.
I have a large dataset of the form [(X1, Y1, Z1, VALUE1), (X2, Y2, Z2, VALUE2)...]. The geometry of the points is the surface of a cylinder, while there are many discrete points they come nowhere near being a full mesh.
I would like to create a basic plot, where each of the points is given an intensity of a color (like a heatmap) based on how high its value is, and then the colors are smoothed to some degree to create a cohesive surface rather than discrete points
I am currently using matplotlib, however, I would also use other libraries if necessary.
I have looked into both surface plots and Tri-Surface plots but neither seem to do what I want (although the documentation for plot_trisurf() is a little confusing so maybe it is still a possibility).
I have also looked at this post: 3D discrete heatmap in matplotlib.
And while the set up is mostly the same, I would like to have a more cohesive surface plot rather than a 3d Tetris set up. The original answer seems pretty close to my desired solution, however, I would like the colors to be based on VALUE rather than Z and if possible for there to be color smoothing between the sections.
Depending on how dense your point cloud is you may be able to get what you want with this (adjust the size parameter, s, to fill out the plot best for your data):
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(X, Y, Z, c=Value, lw=0, s=20)
plt.show()
This question explains how to change the "camera position" of a 3D plot in matplotlib by specifying the elevation and azimuth angles. ax.view_init(elev=10,azim=20), for example.
Is there a similar way to specify the zoom of the figure numerically -- i.e. without using the mouse?
The only relevant question I could find is this one, but the accepted answer to that involves installing another library, which then also requires using the mouse to zoom.
EDIT:
Just to be clear, I'm not talking about changing the figure size (using fig.set_size_inches() or similar). The figure size is fine; the problem is that the plotted stuff only takes up a small part of the figure:
The closest solution to view_init is setting ax.dist directly. According to the docs for get_proj "dist is the distance of the eye viewing point from the object point". The initial value is currently hardcoded with dist = 10. Lower values (above 0!) will result in a zoomed in plot.
Note: This behavior is not really documented and may change. Changing the limits of the axes to plot only the relevant parts is probably a better solution in most cases. You could use ax.autoscale(tight=True) to do this conveniently.
Working IPython/Jupyter example:
%matplotlib inline
from IPython.display import display
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Grab some test data.
X, Y, Z = axes3d.get_test_data(0.05)
# Plot a basic wireframe.
ax.view_init(90, 0)
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
plt.close()
from ipywidgets import interact
#interact(dist=(1, 20, 1))
def update(dist=10):
ax.dist = dist
display(fig)
Output
dist = 10
dist = 5
I was wondering if there's a way to plot a data cube in Python. I mean I have three coordinate for every point
x=part.points[:,0]
y=part.points[:,1]
z=part.points[:,2]
And for every point I have a scalar field t(x,y,z)
I would like to plot a 3D data cube showing the position of the point and for every point a color which is proportional to the scalar field t in that point.
I tried with histogramdd but it didn't work.
You can use matplotlib.
Here you have a working example (that moves!):
import random
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
mypoints = []
for _ in range(100):
mypoints.append([random.random(), #x
random.random(), #y
random.random(), #z
random.randint(10,100)]) #scalar
data = zip(*mypoints) # use list(zip(*mypoints)) with py3k
fig = pyplot.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(data[0], data[1], data[2], c=data[3])
pyplot.show()
You probably have to customize the relation of your scalar values with the corresponding colors.
Matplotlib has a very nice look but it can be slow drawing and moving these 3D drawings when you have many points. In these cases I used to use Gnuplot controlled by gnuplot.py. Gnuplot can also be used directly as a subprocess as shown here and here.
Another option is Dots plot, produced by MathGL. It is GPL plotting library. Add it don't need many memory if you save in bitmap format (PNG, JPEG, GIF and so on).