I'm trying to annotate points plotted with the points3d() function using mayavi.mlab.
Each point is associated with a label which I would like to plot next to the points using the text3d() function. Plotting the points is fast, however the mlab.text3d() function does not seem to accept arrays of coordinates, so I have to loop over the points and plot the text individually, which is very slow:
for i in xrange(0, self.n_labels):
self.mlab_data.append(
mlab.points3d( pX[self.labels == self.u_labels[i], 0],
pX[self.labels == self.u_labels[i], 1],
pX[self.labels == self.u_labels[i], 2],
color=self.colours[i],
opacity=1,
scale_mode="none",
scale_factor=sf ) )
idcs, = np.where(self.labels == self.u_labels[i])
for n in idcs.flatten():
mlab.text3d( pX[n, 0],
pX[n, 1],
pX[n, 2],
"%d" % self.u_labels[i],
color=self.colours[i],
opacity=1,
scale=sf )
Any ideas how I could speed this up? Also, is it possible to add a legend (as for instance in matplotlib), I couldn't find anything in the docs.
Thanks,
Patrick
The way you are doing it above will render the scene every time you plot a point or text. This is slow. You can disable the scene rendering, do the plotting and then render the scene by figure.scene.disable_render = True/False:
import scipy
from mayavi import mlab
X = 100 * scipy.rand(100, 3)
figure = mlab.figure('myfig')
figure.scene.disable_render = True # Super duper trick
mlab.points3d(X[:,0], X[:,1], X[:,2], scale_factor=0.4)
for i, x in enumerate(X):
mlab.text3d(x[0], x[1], x[2], str(i), scale=(2, 2, 2))
figure.scene.disable_render = False # Super duper trick
I use this trick and others in Figure class in morphic Viewer https://github.com/duanemalcolm/morphic/blob/master/morphic/viewer.py
Another good trick in the code is to reuse existing objects, i.e., if you've plotted the text already, don't replot them, just update their position and text attributes. This means saving the mlab object. You can see how I do this in morphic.Viewer.
Related
I'm using the amazing open3d Python libary to visualize some point Cloud. I already know the normal vectors of these points that I attribute directly as follows:
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
pcd.normals = o3d.utility.Vector3dVector(normals)
I am also setting a visualizer in which I insert these points as follows:
app = gui.Application.instance
app.initialize()
vis = o3d.visualization.O3DVisualizer("Open3D - 3D Text", 1024, 768)
vis.show_settings = True
vis.add_geometry("my points", pcd)
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm:
'''visualize'''
vis.reset_camera_to_default()
app.add_window(vis)
app.run()
Up to now, all of this has run as intended, however I am not able to set the visualizer in such a way that enables me to visualize the normal vectors. Apparently o3d.visualization.Visualizer() has this method get_render_option() that is said to "retrieve a RenderOption" object, and in this RenderOption object there is a point_show_normal property but I couldn't make my code (more complicated than the minimal example above) work with o3d.visualization.Visualizer(): I don't see how to use this o3d.visualization.Visualizer().get_render_option().point_show_normal.
Is there any way to show the normal vectors with with open3d.visualization.O3DVisualizer?
you need add two lines to your code, get the render and set point_show_normal to True:
opt = vis.get_render_option()
opt.point_show_normal = True
You can see in the documentation open3D tutorials and python examples
I hope it helps
I didn't find a solution so far, so I resorted to look at my normal vectors in another window, produced using the mayavi library rather than the open3D library. To do so, I used this simple code snippet:
from mayavi.mlab import *
P = [my list of 3D points]
N = [my list of normal vectors]
x = P[:, 0]
y = P[:, 1]
z = P[:, 2]
points3d(x, y, z, color=(0, 1, 0), scale_factor=0.5)
u = N[:, 0]
v = N[:, 1]
w = N[:, 2]
quiver3d(x, y, z, u, v, w)
show()
And it worked as intended. Ideally I would like to have the normal vectors displayed with the rest of the figure, but this responded to my immediate needs.
I consider this as a workaround rather than the definitive solution, so I put it here as an answer if someone else having the problem finds it useful. But my question still isn't solved.
I am using the parallel_offset function of the shapely package to get offset structures to some polygons that are closed rings. I have several polygons at once, many with similar shapes. Around 10-25% of them, however, do not generate a closed ring from the parallel_offset. Here is a MWE of a shape that does not work:
import matplotlib.pyplot as plt
from shapely.geometry.polygon import LinearRing
def plot_line(ax, ob, color):
x, y = ob.xy
ax.plot(x, y, color=color, alpha=0.7, linewidth=3,
solid_capstyle='round', zorder=2)
polygon = [[-29.675, -30.675],
[-28.4094, -29.4094],
[-28.325, -29.325],
[-28.325, -29.764],
[-28.325, -29.7933],
[-28.4587, -29.8274],
[-28.4676, -29.8297],
[-28.5956, -29.8814],
[-28.6041, -29.8848],
[-28.724, -29.953],
[-28.732, -29.9576],
[-28.8417, -30.0413],
[-28.849, -30.0469],
[-28.9466, -30.1445],
[-28.9531, -30.151],
[-29.0368, -30.2607],
[-29.0424, -30.268],
[-29.1106, -30.3879],
[-29.1152, -30.3959],
[-29.1669, -30.5239],
[-29.1703, -30.5324],
[-29.2044, -30.6661],
[-29.2067, -30.675],
[-29.6457, -30.675],
[-29.675, -30.675]]
poly_line = LinearRing(polygon)
poly_line_offset = poly_line.parallel_offset(0.05, side="left", resolution=16,
join_style=2, mitre_limit=1)
fig = plt.figure()
ax = fig.add_subplot(111)
plot_line(ax, poly_line, "blue")
plot_line(ax, poly_line_offset, "green")
plt.show()
As you can see, the green offset polygon does not close at the point that is first/last in the list of vertices. Other very similar shapes, however, do work as intended. They have the same data structure and also have the same start/end point, as does my example above. The join_style attribute does not change the outcome to what I want. Changing the resolution or distance does not help either. The documentation also does not address this issue.
Do you have any guidance? I am using shapely 1.6.3.
not completely sure why this happens, nevertheless you might use a workaround based on the buffer method:
poly_line = LinearRing(polygon)
poly_line_offset = poly_line.buffer(0.05,
resolution=16, join_style=2, mitre_limit=1).exterior
With your data, this produces the (probably) desired result:
Here's a work around I did in my code.
I basically rolled the LinearRing (shifting the start point along the ring),
applied two offsets, and then added them back together.
It's probably not an ideal solution, but hopefully can work as a starting point:
from shapely import ops, geometry
import numpy as np
# test geo:
ring_coords = [(0,0.1),(0,2),(4,2),(4,0)]
ring = geometry.LinearRing(ring_coords)
# shifts the ring by one point
rolled = LinearRing(np.roll(ring.coords[:-1], 2))
# apply the offsets
offset_ring = ring.parallel_offset(-0.2, side='right', resolution=3, join_style=2, mitre_limit=3)
offset_rolled = rolled.parallel_offset(-0.2, side='right', resolution=3, join_style=2, mitre_limit=3)
# combine the points
# assuming you started with two rings, backward should be empty
forward, backward = ops.shared_paths(offset_rolled, offset_ring)
combined = geometry.LinearRing(ops.linemerge(forward))
I am trying to plot a linear line with associated error.
I calculated values for slope (a) and intercepts (b). In addition, I calculated the error associated with these values. So I drew the line given by the typical formula below.
y=ax+b
However, in addition to the line, I also want to draw the associated error. I came up with the idea to draw the lines associated with these formulas and color the space between the lines gray.
y=(a+a_sd)x+(b+b_sd)
y=(a-a_sd)x+(b-b_sd)
Uisng the following piece of code, I am able to color part of the surface between the lines, but not the whole span (see included output).
I think this may be due to the fact that "distance" is not sorted, and fill_between is using distance[0] and distance[-1] as begin and end for the span, respectively.
As always, any help would be highly appreciated!
import matplotlib.pyplot as plt
distance=[0.35645334340084989, 0.55406894241607718, 0.10201413273193734, 0.13401365724625941, 0.71918808865838735, 0.14151335417722818]
time=[2.4004984846346171, 2.4909766335028447, 1.9852064018125195, 1.9083156734132103, 2.6380396934372863, 1.9114505780323543]
time_SD=[0.062393810960652669, 0.056945715242838917, 0.073960838867327183, 0.084111239062664475, 0.026912957190265499, 0.08595664694840538]
distance_SD=[0.035160608598240162, 0.032976715460514235, 0.02782911002465227, 0.035465701695038584, 0.043009444687382707, 0.038387585107200854]
a=1.17887019041
b=1.83339229489
a_sd=0.159771527859
b_sd=0.0762509747218
plt.errorbar(distance,time,yerr=time_SD, xerr=distance_SD, linestyle="None")
abline_values = [(a)*i + (b) for i in distance]
abline_values_plus = [(a+a_sd)*i + (b+b_sd) for i in distance]
abline_values_minus = [(a-a_sd)*i + (b-b_sd) for i in distance]
plt.plot(distance, abline_values,"r")
plt.fill_between(distance,abline_values_minus,abline_values_plus,facecolor='lightgrey', interpolate=True, edgecolors="None")
leg = plt.legend(loc="lower right", frameon=False, handlelength=0, handletextpad=0)
for item in leg.legendHandles:
item.set_visible(False)
plt.show()
In order to use pyplot.fill_between() the list to plot the horizontal coordinate should be sorted. Using an unsorted list of x values is possible, but can lead to undesired results.
Sorting a list can be done using sorted(list).
import matplotlib.pyplot as plt
distance=[0.35645334340084989, 0.55406894241607718, 0.10201413273193734, 0.13401365724625941, 0.71918808865838735, 0.14151335417722818]
time=[2.4004984846346171, 2.4909766335028447, 1.9852064018125195, 1.9083156734132103, 2.6380396934372863, 1.9114505780323543]
time_SD=[0.062393810960652669, 0.056945715242838917, 0.073960838867327183, 0.084111239062664475, 0.026912957190265499, 0.08595664694840538]
distance_SD=[0.035160608598240162, 0.032976715460514235, 0.02782911002465227, 0.035465701695038584, 0.043009444687382707, 0.038387585107200854]
a=1.17887019041
b=1.83339229489
a_sd=0.159771527859
b_sd=0.0762509747218
distance_sorted = sorted(distance)
plt.errorbar(distance,time,yerr=time_SD, xerr=distance_SD, linestyle="None")
abline_values = [(a)*i + (b) for i in distance_sorted]
abline_values_plus = [(a+a_sd)*i + (b+b_sd) for i in distance_sorted]
abline_values_minus = [(a-a_sd)*i + (b-b_sd) for i in distance_sorted]
plt.plot(distance_sorted, abline_values,"r")
plt.fill_between(distance_sorted,abline_values_minus,abline_values_plus, facecolor='lightgrey', edgecolors="None")
plt.show()
The documentation does not mention the requirement of x values being sorted. The reason is probably that fill_between actually works even with unsorted lists, just not the way one might expect. Maybe the following animation gives a more intuitive understanding on the issue:
You are right fill_between seems to expect the values to be sorted. The documentation is not clear about this behaviour though. The following example however shows the same effect:
import matplotlib.pyplot as plt
from numpy import random, array
#x = random.randn(20) #does not work
x = array(sorted(random.randn(20))) #works
a = 2
d = .5
y_h = x*(a+d)
y_l = x*(a-d)
plt.fill_between(x,y_h, y_l)
plt.show()
As a workaround just sort your values before calculating your errorlines using sorted.
Is there a way to extract the data from an array, which corresponds to a line of a contourplot in python? I.e. I have the following code:
n = 100
x, y = np.mgrid[0:1:n*1j, 0:1:n*1j]
plt.contour(x,y,values)
where values is a 2d array with data (I stored the data in a file but it seems not to be possible to upload it here). The picture below shows the corresponding contourplot. My question is, if it is possible to get exactly the data from values, which corresponds e.g. to the left contourline in the plot?
Worth noting here, since this post was the top hit when I had the same question, that this can be done with scikit-image much more simply than with matplotlib. I'd encourage you to check out skimage.measure.find_contours. A snippet of their example:
from skimage import measure
x, y = np.ogrid[-np.pi:np.pi:100j, -np.pi:np.pi:100j]
r = np.sin(np.exp((np.sin(x)**3 + np.cos(y)**2)))
contours = measure.find_contours(r, 0.8)
which can then be plotted/manipulated as you need. I like this more because you don't have to get into the deep weeds of matplotlib.
plt.contour returns a QuadContourSet. From that, we can access the individual lines using:
cs.collections[0].get_paths()
This returns all the individual paths. To access the actual x, y locations, we need to look at the vertices attribute of each path. The first contour drawn should be accessible using:
X, Y = cs.collections[0].get_paths()[0].vertices.T
See the example below to see how to access any of the given lines. In the example I only access the first one:
import matplotlib.pyplot as plt
import numpy as np
n = 100
x, y = np.mgrid[0:1:n*1j, 0:1:n*1j]
values = x**0.5 * y**0.5
fig1, ax1 = plt.subplots(1)
cs = plt.contour(x, y, values)
lines = []
for line in cs.collections[0].get_paths():
lines.append(line.vertices)
fig1.savefig('contours1.png')
fig2, ax2 = plt.subplots(1)
ax2.plot(lines[0][:, 0], lines[0][:, 1])
fig2.savefig('contours2.png')
contours1.png:
contours2.png:
plt.contour returns a QuadContourSet which holds the data you're after.
See Get coordinates from the contour in matplotlib? (which this question is probably a duplicate of...)
Or more specifically, how can I change the [659] on the upper-right corner to '659 degrees' or something like that ?
I have checked all the threads mentioned in the following reply: matplotlib values under cursor. However, all of them seem to address the x,y location of the cursor. I am interested in changing the data-value. I could not find a reply or related documentation in the api.
I have tried both format_coord(x, y) and format_cursor_data(data) but neither of them seem to be working.
Thanks,
Sarith
PS: My code is in multiple modules and is a part of gui application. I can share relevant sections if that would be of any help in answering this.
One line solution:
ax.format_coord = lambda x, y: 'x={:.2f}, y={:.2f}, z={:.2f}'.format(x,y,data[int(y + 0.5),int(x + 0.5)])
I had the same problem (I wanted to get rid of the data and send it to somewhere else in a tkinter widget).
I figured out the ax.format_coord was'nt being called, the one you have to change is the one at matplotlib.artist.Artist
this worked for me:
def format_cursor_data(self,data):
return 'new_data'
matplotlib.artist.Artist.format_cursor_data=format_cursor_data
By modifying an example from matplotlib I got this code:
This displays x,y, and z value with a degrees after z.
You should be able to easily modify it, or copy the relevant functions to make it work on your side.
You said you already tried format_coord, maybe you forgot to set the funtion? (second last line)
"""
Show how to modify the coordinate formatter to report the image "z"
value of the nearest pixel given x and y
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
X = 10*np.random.rand(5, 3)
fig, ax = plt.subplots()
ax.imshow(X, cmap=cm.jet, interpolation='nearest')
numrows, numcols = X.shape
def format_coord(x, y):
col = int(x + 0.5)
row = int(y + 0.5)
if col >= 0 and col < numcols and row >= 0 and row < numrows:
#get z from your data, given x and y
z = X[row, col]
#this only leaves two decimal points for readability
[x,y,z]=map("{0:.2f}".format,[x,y,z])
#change return string of x,y and z to whatever you want
return 'x='+str(x)+', y='+str(y)+', z='+str(z)+" degrees"
else:
return 'x=%1.4f, y=%1.4f' % (x, y)
#Set the function as the one used for display
ax.format_coord = format_coord
plt.show()
Emmm, Sarith, I am also facing the problem but a little trick helped me out. I still used this function:
your_imshow.format_coord = lambda x, y: 'x={:.5f}, y={:.2f}, amplitude='.format(x,y)
It pretends to add label before the bracket. Yeah, it is an easy but not essential way to change the presentation form, but it works to me. I hope this could also benefit others.