Skew/rotate/otherwise transform 3D text in Matplotlib - python

I have a graph in Matplotlib that looks like this:
I would like to skew the text (by an x-shear of 10.3 and a y shear of 9.0) in order to make it appear to be parallel with the bars. Here is my code that I am using:
_x = [0,1,2,3]
_y = [1, 2]
_xx, _yy = np.meshgrid(_x, _y)
x, y = _xx.ravel(), _yy.ravel()
top = [1, 0, 0, 3, 0, 0, 0, 0]
bottom = np.zeros_like(top)
width = 1
depth = 0.25
# graph is set up here
for i, iXZ in enumerate(zip(x, top)):
iX, iZ = iXZ
if iZ:
t = ax1.text(iX + 0.5, 1, -0.85, ('U\n' if i == 0 else 'E\n') * iZ, (0, 0, 0),
ha='center', va='bottom', fontfamily="Century Gothic", color='black', fontsize=40)
# skew by 10.3 and 9.0
t.set_transform(mtrns.CompositeGenericTransform(t.get_transform(), mtrns.Affine2D().skew(10.3, 9.0)))
However, when this code is run, matplotlib outputs this:
I feel like I am using the wrong type of transform, since Affine2D implies that it's only for "2D" objects. Unfortunately, I cannot find the "right" method in the documentation. How would I be able to skew the text effectively?

Related

How to join 2 surfaces on Z acess into one mesh?

So I have 2 surfaces (PolyData in PyVista) one on top of another:
They are shaped a little differently on Z access yet whenever a top one has a Z value on X, Y plane we are sure a-bottom one has the same. So how one can merge two surfaces X, Y aligned into one solid mesh?
What I try:
import numpy as np
import pyvista as pv
import vtk
def extruder(mesh, val_z):
extrude = vtk.vtkLinearExtrusionFilter()
extrude.SetInputData(mesh)
extrude.SetVector(0, 0, val_z)
extrude.Update()
extruded_mesh = pv.wrap(extrude.GetOutput())
return extruded_mesh
# generate two sheets of input data
noise = pv.perlin_noise(2, (0.2, 0.2, 0.2), (0, 0, 0))
bounds_2d = (-10, 10, -10, 10)
dim = (40, 50, 1)
bottom, top = [
pv.sample_function(noise, dim=dim, bounds=bounds_2d + (z, z)).warp_by_scalar()
for z in [-5, 5]
]
bottom = bottom.extract_surface(nonlinear_subdivision=5)
top = top.extract_surface(nonlinear_subdivision=5)
top = extruder(top, -50).triangulate()
bottom = extruder(bottom, 50).triangulate()
intersection = bottom.boolean_cut(top)
#top = top.clip_surface(bottom, invert=False, compute_distance=True)
#top = top.extrude([0, 0, -50]).triangulate()
#bottom = bottom.extrude([0, 0, 50]).triangulate()
#intersection = bottom.boolean_cut(top).triangulate()
p = pv.Plotter()
p.add_mesh(top, cmap="hot", opacity=0.15)
p.add_mesh(bottom, cmap="RdYlBu", opacity=0.15)
p.add_mesh(intersection, cmap="Dark2", opacity=1)
p.show()
What do I get:
What I expected:
only middle to be filled.
So had to do this:
import numpy as np
import pyvista as pv
# generate two sheets of input data
noise = pv.perlin_noise(2, (0.2, 0.2, 0.2), (0, 0, 0))
bounds_2d = (-10, 10, -10, 10)
dim = (40, 50, 1)
bottom, top = [
pv.sample_function(noise, dim=dim, bounds=bounds_2d + (z, z)).warp_by_scalar()
for z in [-5, 5]
]
bottom = bottom.extract_surface()
top = top.extract_surface()
topm = top.extrude([0, 0, -50]).triangulate().clean()
bottomm = bottom.extrude([0, 0, 50]).triangulate().clean()
topm = topm.clip_surface(bottom, invert=False)
bottomm = bottomm.clip_surface(top, invert=True)
intersection = topm.boolean_add(bottomm).triangulate().clean().subdivide(2).clean()
p = pv.Plotter()
#p.add_mesh(topm, cmap="hot", opacity=0.15)
#p.add_mesh(bottomm, cmap="gnuplot2", opacity=0.15)
p.add_mesh(intersection, cmap="Dark2", opacity=1)
p.show()
the resulting mesh is really bad, yet it has desired shape and gets to be computed in usable time:

How to rotate a 1D line graph array in python/numpy by angle?

I’d like to rotate a line graph horizontally. So far, I have the target angle but I’m not able to rotate the graph array (the blue graph in the blot).
import matplotlib.pyplot as plt
import numpy as np
x = [5, 6.5, 7, 8, 6, 5, 3, 4, 3, 0]
y = range(len(x))
best_fit_line = np.poly1d(np.polyfit(y, x, 1))(y)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], best_fit_line[-1] - best_fit_line[0]))
print("angle: " + str(angle))
plt.figure(figsize=(8, 6))
plt.plot(x)
plt.plot(best_fit_line, "--", color="r")
plt.show()
The target calculations of the array should look like this (please ignore the red line):
If you have some advice, please let me know. Thanks.
This question is very helpful, in particular the answer by #Mr Tsjolder. Adapting that to your question, I had to subtract 90 from the angle you calculated to get the result you want:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import transforms
x = [5, 6.5, 7, 8, 6, 5, 3, 4, 3, 0]
y = range(len(x))
best_fit_line = np.poly1d(np.polyfit(y, x, 1))(y)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], best_fit_line[-1] - best_fit_line[0]))
print("angle: " + str(angle))
plt.figure(figsize=(8, 6))
base = plt.gca().transData
rotation = transforms.Affine2D().rotate_deg(angle - 90)
plt.plot(x, transform = rotation + base)
plt.plot(best_fit_line, "--", color="r", transform = rotation + base)
Follow-up question: What if we just need the numerical values of the rotated points?
Then the matplotlib approach can still be useful. From the rotation object we introduced above, matplotlib can extract the transformation matrix, which we can use to transform any point:
# extract transformation matrix from the rotation object
M = transforms.Affine2DBase.get_matrix(rotation)[:2, :2]
# example: transform the first point
print((M * [0, 5])[:, 1])
[-2.60096617 4.27024297]
The slicing was done to get the dimensions we're interested in, since the rotation happens only in 2D. You can see that the first point from your original data gets transformed to (-2.6, 4.3), agreeing with my plot of the rotated graph above.
In this way you can rotate any point you're interested in, or write a loop to catch them all.
Arne's awnser is perfect if you like to rotate the graph with matplotlib. If not, you can take a look a this code:
import matplotlib.pyplot as plt
import numpy as np
def rotate_vector(data, angle):
# source:
# https://datascience.stackexchange.com/questions/57226/how-to-rotate-the-plot-and-find-minimum-point
# make rotation matrix
theta = np.radians(angle)
co = np.cos(theta)
si = np.sin(theta)
rotation_matrix = np.array(((co, -si), (si, co)))
# rotate data vector
rotated_vector = data.dot(rotation_matrix)
return rotated_vector
x = [5, 6.5, 7, 8, 6, 5, 3, 4, 3, 0]
y = range(len(x))
best_fit_line = np.poly1d(np.polyfit(y, x, 1))(y)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], best_fit_line[-1] - best_fit_line[0]))
print("angle:", angle)
# rotate blue line
d = np.hstack((np.vstack(y), np.vstack(x)))
xr = rotate_vector(d, -(angle - 90))
# rotate red line
dd = np.hstack((np.vstack(y), np.vstack(best_fit_line)))
xxr = rotate_vector(dd, -(angle - 90))
plt.figure(figsize=(8, 6))
plt.plot(xr[:, 1]) # or plt.plot(xr[:, 0], xr[:, 1])
plt.plot(xxr[:, 1], "--", color="r")
plt.show()

Malfunction of the translated code InterX to python

I want to use the function InterX to find the intersection of two curves. However the function does not return the expected result. The function is availabel here
The function always return the point of intersection as P = None, None. When a valid point was expected.
import numpy as np
import pandas as pd
from InterX import InterX
x_t = np.linspace(0, 10, 10, True)
z_t = np.array((0, 0, 0, 0, 0, 0, 0.055, 0.41, 1.23, 4))
X_P = np.array((2,4))
Z_P = np.array((3,-1))
Line = pd.DataFrame(np.array((X_P,Z_P)))
Curve = pd.DataFrame(np.array([x_t,z_t]))
Curve = Curve.T
P = InterX(Line[0],Line[1],Curve[0],Curve[1])
In this script the expected result was P = [3.5,0]. However, the resulting point P is P = [None,None]
The short answer - use:
P = InterX(L1, L1, L2, L2)
or
P = InterX(L1.iloc[:,0].to_frame(),L1.iloc[:,1].to_frame(),L2.iloc[:,0].to_frame(),L2.iloc[:,1].to_frame())
For a detailed answer see the following that refers to the code of your original question.
This refers to the code of the original question:
You need two pass two dataframes with x and y values (it would be of course much more logical if InterX would accept 4 Series or 2 DataFrames respectively).
InterX then gets the x and y values in a very convoluted way from these dataframes in lines 90 through 119 (which could be done far more easyly). So the working solution is:
import numpy as np
import pandas as pd
from InterX import InterX
x_t = np.linspace(0, 10, 10, True)
z_t = np.array((0, 0, 0, 0, 0, 0, 0.055, 0.41, 1.23, 4))
x_P = np.array((2,4))
z_P = np.array((3,-1))
curve_x = pd.DataFrame(x_t)
curve_z = pd.DataFrame(z_t)
line_x = pd.DataFrame(X_P)
line_z = pd.DataFrame(Z_P)
p = InterX(line_x, line_z, curve_x, curve_z)
Output of print(p):
xs ys
0 3.5 0.0
Please note that according to the python naming convention (PEP8) function and variable names should be lowercase, with words separated by underscores.
I find the code of InterX very difficult to understand, a much cleaner solution (along with a nice plot) is this one.
With
x_t = np.linspace(0, 10, 10, True)
z_t = np.array((0, 0, 0, 0, 0, 0, 0.055, 0.41, 1.23, 4))
X_P = np.array((2,4))
Z_P = np.array((3,-1))
x,y = intersection(x_t,z_t,X_P,Z_P)
print(x,y)
plt.plot(x_t,z_t,c='r')
plt.plot(X_P,Z_P,c='g')
plt.plot(x,y,'*k')
plt.show()
we get [3.5] [-0.] and this picture:

Create matplotlib-style 3d scattergraph z-axis in Vispy

I am conducting PhD research into extremely high density lidar point clouds(1M points +), and am having real difficulty plotting these files on a 3D scattergraph. Matplotlib isn't optimised for datasets that large, so I am attempting to use Vispy to achieve this. Right now, I am having real trouble trying to get a scattergraph-style plot up and running, so I can start building my pipeline.
I'm after something like this:
matplotlib 3D scattergraph
Using Vispy, I am finding it difficult to apply a z-axis and I am having trouble finding what part of documentation can help me with this. Here's my code:
"""
This example demonstrates the use of the SurfacePlot visual.
"""
import sys
import numpy as np
from vispy import app, scene
from vispy.util.filter import gaussian_filter
canvas = scene.SceneCanvas(keys='interactive', bgcolor='w')
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(up='z', fov=60)
# Simple surface plot example
# x, y values are not specified, so assumed to be 0:50
z = np.random.normal(size=(250, 250), scale=200)
z[100, 100] += 50000
z = gaussian_filter(z, (10, 10))
print("This is z {0}".format(z))
p1 = scene.visuals.SurfacePlot(z=z, color=(0.3, 0.3, 1, 1))
p1.transform = scene.transforms.MatrixTransform()
p1.transform.scale([1/249., 1/249., 1/249.])
p1.transform.translate([-0.5, -0.5, 0])
view.add(p1)
# p1._update_data() # cheating.
# cf = scene.filters.ZColormapFilter('fire', zrange=(z.max(), z.min()))
# p1.attach(cf)
xax = scene.Axis(pos=[[-0.5, -0.5], [0.5, -0.5]], tick_direction=(0, -1),
font_size=16, axis_color='k', tick_color='k', text_color='k',
parent=view.scene)
xax.transform = scene.STTransform(translate=(0, 0, -0.2))
yax = scene.Axis(pos=[[-0.5, -0.5], [-0.5, 0.5]], tick_direction=(-1, 0),
font_size=16, axis_color='k', tick_color='k', text_color='k',
parent=view.scene)
yax.transform = scene.STTransform(translate=(0, 0, -0.2))
zax = scene.Axis(pos=[[1.0, 1.0], [-1.0, 1.0]], tick_direction=(-1, 0),
font_size=16, axis_color='k', tick_color='k', text_color='k',
parent=view.scene)
zax.transform = scene.STTransform(translate=(0.0, 0.0, -0.2))
# Add a 3D axis to keep us oriented
axis = scene.visuals.XYZAxis(parent=view.scene)
if __name__ == '__main__':
canvas.show()
if sys.flags.interactive == 0:
app.run()
And it makes a graph that looks like this:
vispy_example_plot
You can see in the above image my problem. I want to orient the third axis perpendicular to the others, and a grid to the to the walls of the plot, so the data is more defined.
Unfortunately sub-sampling and other tricks to display the data using matplotlib, are not optimal for me because I have to display the original data, rather than showing a subset. Of course, if there are other strategies I can use to display my data as I would like, then I am all ears.
Thanks in advance, hopefully someone can help
i faced the same issue doing research on camera depth images and cfar radar data. since vispy is supporting 2d only for axes, you could rotate the z-axis after creation. here, a slightly modified version of your code:
import sys
import numpy as np
from vispy import app, scene
from vispy.util.filter import gaussian_filter
canvas = scene.SceneCanvas(keys='interactive', bgcolor='w')
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(up='z', fov=60)
z = np.random.normal(size=(250, 250), scale=200)
z[100, 100] += 50000
z = gaussian_filter(z, (10, 10))
p1 = scene.visuals.SurfacePlot(z=z, color=(0.3, 0.3, 1, 1))
p1.transform = scene.transforms.MatrixTransform()
p1.transform.scale([1/249., 1/249., 1/249.])
view.add(p1)
xax = scene.Axis(pos=[[0, 0], [1, 0]], tick_direction=(0, -1), axis_color='r', tick_color='r', text_color='r', font_size=16, parent=view.scene)
yax = scene.Axis(pos=[[0, 0], [0, 1]], tick_direction=(-1, 0), axis_color='g', tick_color='g', text_color='g', font_size=16, parent=view.scene)
zax = scene.Axis(pos=[[0, 0], [-1, 0]], tick_direction=(0, -1), axis_color='b', tick_color='b', text_color='b', font_size=16, parent=view.scene)
zax.transform = scene.transforms.MatrixTransform() # its acutally an inverted xaxis
zax.transform.rotate(90, (0, 1, 0)) # rotate cw around yaxis
zax.transform.rotate(-45, (0, 0, 1)) # tick direction towards (-1,-1)
if __name__ == '__main__':
canvas.show()
if sys.flags.interactive == 0:
app.run()
Regards

Plotting sort of a heatmap whose colors are a result of a function x,y -> r,g,b

I have a dictionary which maps XY tuples to RGB tuples. For example,
d = {
(0, 0): (0, 0, 0),
(0, 1): (0, 0, 200),
}
I wish to plot some sort of heatmap, which, at a given XY coordinate, will have color which is the average of the colors in the dict, weighted by their reciprocal distances; as if they were "sources of light" or so.
In the given example, the coordinate (0, 0.5) should be colored with (0, 0, 100) and the coordinate (0, 0.1) should be colored with (0, 0, 20).
My question is rather technical: how do I let pyplot plot a pixel image with colors retrieved from a function f(x, y) -> (r, g, b)?
If you have your X-Y grid:
import numpy
from matplotlib import pyplot as plt
width, height = 300, 500
xs = numpy.arange(width)
ys = numpy.arange(height)
data = numpy.dstack(numpy.meshgrid(xs, ys))
You should just map these to (r, g, b) tuples. The following is pretty slow, but how to speed it up depends on what your function does.
from colorsys import hsv_to_rgb
import random
def data_to_color(x, y):
return (
(x/width)**(0.5+random.random()*2),
(y/height)**3,
(x/width*y/height)*0.6 + random.random()*0.4
)
colors = [[data_to_color(x, y) for x, y in row] for row in data]
colors = numpy.array(colors)
colors.shape
#>>> (500, 300, 3)
Then imshow can give the wanted output:
plt.imshow(colors, origin='lower')
plt.show()
Now, if you want to interpolate from your points as you say, you can use scipy.interpolate. I'll make a dictionary to extrapolate from the function above:
from scipy.interpolate import griddata
gridpoints = data.reshape(width*height, 2)
d = {(x, y): data_to_color(x, y) for x, y in gridpoints if not random.randint(0, 1000)}
len(d)
#>>> 142
Extract the dictionary into numpy arrays, and separate the colours (it might be possible to avoid the separation, but you can test that yourself):
points, values = zip(*d.items())
points = numpy.array(points)
values = numpy.array(values)
reds = values[:, 0]
greens = values[:, 1]
blues = values[:, 2]
Then run griddata on the points:
new_reds = griddata(points, reds, (data[:, :, 0], data[:, :, 1]), method='linear')
new_greens = griddata(points, greens, (data[:, :, 0], data[:, :, 1]), method='linear')
new_blues = griddata(points, blues, (data[:, :, 0], data[:, :, 1]), method='linear')
new_colors = numpy.dstack([new_reds, new_greens, new_blues])
new_colors[numpy.isnan(new_colors)] = 0.5
And plot:
plt.triplot(points[:,0], points[:,1], 'k-', linewidth=1, alpha=0.5)
plt.imshow(new_colors, extent=(0, width, 0, height), origin='lower')
plt.show()
And, finally, if you want extrapolation too, I copied some code from here:
import scipy
def extrapolate_nans(x, y, v):
'''
Extrapolate the NaNs or masked values in a grid INPLACE using nearest
value.
.. warning:: Replaces the NaN or masked values of the original array!
Parameters:
* x, y : 1D arrays
Arrays with the x and y coordinates of the data points.
* v : 1D array
Array with the scalar value assigned to the data points.
Returns:
* v : 1D array
The array with NaNs or masked values extrapolated.
'''
if numpy.ma.is_masked(v):
nans = v.mask
else:
nans = numpy.isnan(v)
notnans = numpy.logical_not(nans)
v[nans] = scipy.interpolate.griddata((x[notnans], y[notnans]), v[notnans],
(x[nans], y[nans]), method='nearest').ravel()
return v
new_reds = extrapolate_nans(data[:, :, 0], data[:, :, 1], new_reds)
new_greens = extrapolate_nans(data[:, :, 0], data[:, :, 1], new_greens)
new_blues = extrapolate_nans(data[:, :, 0], data[:, :, 1], new_blues)
new_colors = numpy.dstack([new_reds, new_greens, new_blues])
plt.imshow(new_colors, extent=(0, width, 0, height), origin='lower')
plt.show()
EDIT: Maybe something more like
import numpy
from matplotlib import pyplot as plt
from numpy.core.umath_tests import inner1d
width, height = 300, 500
xs, ys = numpy.mgrid[:width, :height]
coordinates = numpy.dstack([xs, ys])
light_sources = {
(0, 0): (0, 0, 0),
(300, 0): (0, 0, 0),
(0, 0): (0, 0, 0),
(300, 500): (0, 0, 0),
(100, 0): (0, 0, 200),
(200, 150): (100, 70, 0),
(220, 400): (255, 255, 255),
(80, 220): (255, 0, 0),
}
weights = numpy.zeros([width, height])
values = numpy.zeros([width, height, 3])
For each light source:
for coordinate, value in light_sources.items():
Compute the (inverse) distances. Use +1e9 to prevent infinities, although this will have silly failures so a more rigorous fix would be important later:
shifted_coordinates = coordinates - coordinate + 1e-9
inverse_distances = (shifted_coordinates ** 2).sum(axis=-1) ** (-1/2)
Add it to the sum and the sum weighting:
weights += inverse_distances
values += inverse_distances[:, :, numpy.newaxis].repeat(3, axis=-1) * value / 255
Divide by the weighting to have the average:
values /= weights[..., numpy.newaxis]
And show...
plt.imshow(values, origin='lower')
plt.show()
For this:
The reason I didn't go for this originally is because the value at (0, 0.1) in your example is not (0, 0, 20) but:
distances = [0.9, 0.1]
inverse_distances = [10/9, 10]
sum_weighting = 100 / 9
blue_levels = 200 / (109/90) = 18
so it should be (0, 0, 18) by this definition.

Categories

Resources