matplotlib plot numpy array of images as markers - python

There are a lot of posts on plotting pre-saved png images as scatter plot markers but I'm wondering if there's a way to take an array of 2D arrays (lets naively call them images) and, given x and y coordinates, use them as scatter markers without having to save out as pngs and then read in.
For example, say you have these rather dull 'images':
import numpy as np
images = np.random.uniform(0,1,(5,10,10))
... that is, we have 5 lots of 10 by 10, 2D images.
If we want to plot these 'images' as markers at the following 5 locations specified by x and y coordinates:
x, y, = np.array([0, 2, -3, 6, 6.5]), np.array([10, 3, -2, -1, 0.2])
... what is the best way to go about doing this?
Closest example I have tried but failed to make work:
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from matplotlib.offsetbox import AnnotationBbox, OffsetImage
# Convert the images into PIL images
# How? Using: https://stackoverflow.com/a/62144323/8188120
# Why? Maybe PIL images are nice and easy for plotting as markers.. I'm grasping at straws really
pil_images = []
for i in range(images.shape[0]):
pil_images.append(Image.fromarray(np.uint8(images[i])).convert('RGB'))
# Try plotting the images as markers
# Why this method? Saw it in this thread and continued grasping for more straws: https://stackoverflow.com/a/53851017/8188120
fig, ax = plt.subplots()
for i in range(len(pil_images)):
ab = AnnotationBbox(OffsetImage(pil_images[i]), x[i], y[i], frameon=False)
ax.add_artist(ab)
fig.savefig(image_path + 'funny_markers.png')
plt.close('all')

You need to set the limits of the axis accordingly, or they would default to just (0,1):
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
fig, ax = plt.subplots()
for x0, y0, img in zip(x, y, images):
ab = AnnotationBbox(OffsetImage(img, zoom=5, cmap='gray'), (x0, y0), frameon=False)
ax.add_artist(ab)
plt.xlim(x.min(), x.max()+1)
plt.ylim(y.min(), y.max()+1)
plt.show()
Output:

Related

Plotting and assigning axes to a PNG-file in Python

I have several png-files showing some 2d-graphs which I would like to apply axes to. I already know the extent of the files (i.e. x-range and y-range), but do not know how to properly add axes. My current approach is to load the image using
import matplotlib.image as mpimg
img = mpimg.imread('fig_6a.png')
which gives me an rgba-matrix. In addition I generate two vectors
import numpy as np
x_shape = img.shape[1]
y_shape = img.shape[0]
x_vals = np.linspace(x_min, x_max, x_shape)
y_vals = np.linspace(y_min, y_max, y_shape)
create a meshgrid
X, Y = np.meshgrid(x_vals, y_vals)
and would like to use functions such as
import matplotlib.pyplot as plt
fig = plt.pcolormesh(X, Y, img)
plt.show()
to create the final picture. Unfortunately, pcolormesh does not accept rgba-arrays. What would be the best approach to be able to add these axes to my image, and plot it afterwards?

plot_trisurface with custom color array

I basically want to "imshow" the pdf of a three-dimensional Dirichlet distribution on its support. Function simplex below computes regular points on that support, which are stored in the array sim. The array pdf holds a scalar density for each row in sim.
First thing I thought of was to use a triangulation. However, the color argument of plot_trisurface supports only one single color for all triangles. Setting cmap colors the triangles based on the z-coordinate values (See Fig. 1). Also plot_trisurface ignores the facecolors kwarg. What I want, however, is to color the surface based on pdf.
As a workaround I found, that I could interpolated the surface as 3d scatter plot. This generally gives the desired visualization, yet I ist clearly visible that it's a scatter plot; especially on the borders. (See Fig 2.)
Is there a way to plot the projection of the pdf onto the simplex?
import itertools
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
def simplex(n_vals):
base = np.linspace(0, 1, n_vals, endpoint=False)
coords = np.asarray(list(itertools.product(base, repeat=3)))
return coords[np.isclose(coords.sum(axis=-1), 1.0)]
sim = simplex(20)
pdf = stats.dirichlet([1.1, 1.5, 1.3]).pdf(sim.T)
fig1 = plt.figure()
ax1 = fig1.add_subplot(1, 2, 1, projection='3d', azim=20)
ax2 = fig1.add_subplot(1, 2, 2, projection='3d', azim=20)
ax1.plot_trisurf(x, y, z, color='k')
ax2.plot_trisurf(x, y, z, cmap='Spectral')
fig2 = plt.figure()
ax21 = fig2.add_subplot(projection='3d', azim=20)
ax21.scatter3D(*sim.T, s=50, alpha=.5, c=pdf, cmap='Spectral')
This is a way to do so by coloring each triangle in a triangulation object with the right color. Is this what you were looking for? The only thing is that each patch has a uniform color which make the patches somewhat visible.
# Setup is the same
import itertools
import matplotlib.pyplot as plt
from pylab import get_cmap
from matplotlib.tri import Triangulation, LinearTriInterpolator
import numpy as np
from scipy import stats
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
def simplex(n_vals):
base = np.linspace(0, 1, n_vals, endpoint=False)
coords = np.asarray(list(itertools.product(base, repeat=3)))
return coords[np.isclose(coords.sum(axis=-1), 1.0)]
sim = simplex(20)
pdf = stats.dirichlet([1.1, 1.5, 1.3]).pdf(sim.T)
# For shorter notation we define x, y and z:
x = sim[:, 0]
y = sim[:, 1]
z = sim[:, 2]
# Creating a triangulation object and using it to extract the actual triangles.
# Note if it is necessary that no patch will be vertical (i.e. along the z direction)
tri = Triangulation(x, y)
triangle_vertices = np.array([np.array([[x[T[0]], y[T[0]], z[T[0]]],
[x[T[1]], y[T[1]], z[T[1]]],
[x[T[2]], y[T[2]], z[T[2]]]]) for T in tri.triangles])
# Finding coordinate for the midpoints of each triangle.
# This will be used to extract the color
midpoints = np.average(triangle_vertices, axis = 1)
midx = midpoints[:, 0]
midy = midpoints[:, 1]
# Interpolating the pdf and using it with the selected cmap to produce the color RGB vector for each face.
# Some roundoff and normalization are needed
face_color_function = LinearTriInterpolator(tri, pdf)
face_color_index = face_color_function(midx, midy)
face_color_index[face_color_index < 0] = 0
face_color_index /= np.max(pdf)
cmap = get_cmap('Spectral')
# Creating the patches and plotting
collection = Poly3DCollection(triangle_vertices, facecolors=cmap(face_color_index), edgecolors=None)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(collection)
plt.show()
Obviously increasing the resolution would make the plot smoother.
This figure, complete with a colorbar,
was produced by the following script — the function map_colors, defined at the end of the script, could interest the general reader.
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from itertools import product as Π
# the distribution that we want to study
dirichlet = stats.dirichlet([1.1, 1.5, 1.3])
# generate the "mesh"
N = 30 # no. of triangles along an edge
s = np.linspace(0, 1, N+1)
x, y, z = np.array([(x,y,1-x-y) for x,y in Π(s,s) if x+y<1+1E-6]).T
# plot as usual
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d', azim=20)
p3dc = ax.plot_trisurf(x, y, z)
########## change the face colors ####################
mappable = map_colors(p3dc, dirichlet.pdf, 'Spectral')
# ####################################################
# possibly add a colormap
plt.colorbar(mappable, shrink=0.67, aspect=16.7)
# we are done
plt.show()
def map_colors(p3dc, func, cmap='viridis'):
"""
Color a tri-mesh according to a function evaluated in each barycentre.
p3dc: a Poly3DCollection, as returned e.g. by ax.plot_trisurf
func: a single-valued function of 3 arrays: x, y, z
cmap: a colormap NAME, as a string
Returns a ScalarMappable that can be used to instantiate a colorbar.
"""
from matplotlib.cm import ScalarMappable, get_cmap
from matplotlib.colors import Normalize
from numpy import array
# reconstruct the triangles from internal data
x, y, z, _ = p3dc._vec
slices = p3dc._segslices
triangles = array([array((x[s],y[s],z[s])).T for s in slices])
# compute the barycentres for each triangle
xb, yb, zb = triangles.mean(axis=1).T
# compute the function in the barycentres
values = func(xb, yb, zb)
# usual stuff
norm = Normalize()
colors = get_cmap(cmap)(norm(values))
# set the face colors of the Poly3DCollection
p3dc.set_fc(colors)
# if the caller wants a colorbar, they need this
return ScalarMappable(cmap=cmap, norm=norm)

Python Contour Plot/HeatMap

I have x and y coordinates in a df from LoL matches and i want to create a contour plot or heat map to show where the player normally moves in a match.
Does any one know how can I do it?
A contour plot or heat map needs 3 values. You have to provide x, y and z values in order to plot a contour since x and y give the position and z gives the value of the variable you want to show the contour of as a variable of x and y.
If you want to show the movement of the players as a function of time you should look at matplotlib's animations. Or if you want to show the "players density field" you have to calculate it.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import scipy
from scipy.stats.kde import gaussian_kde
from scipy import ndimage
from matplotlib import cm
#select the x and y coordinates
x = df['x']
y = df['y']
nbins= 512
k = gaussian_kde(np.vstack([x,y]))
xi, yi = np.mgrid[0:512, 0:512] #size of the image/map in px
zi = k(np.vstack([xi.flatten(), yi.flatten()]))
im = mpimg.imread("map.png")#Put he background image
fig = plt.figure(figsize=(9,9))
ax2 = fig.add_subplot()
ax2.contourf(xi, yi, zi.reshape(xi.shape), alpha=0.5, cmap=cm.jet, extent=[1, -1, 1, -1])
ax2.set_xlim(0, 512)
ax2.set_ylim(0, 512)
ax2.axis('off')
plt.imshow(im, extent=[0, 512, 0, 512])
plt.savefig(f'Enemies/Clausura/{team}/{team} Stats/{summoner[1]} Early.png', dpi=None, bbox_inches='tight', pad_inches=0)

3D scatter plot of multiple files with each file having unique color

I have seen this thread but my data are a little different. I want to create a 3D plot of multiple files containing x,y,z coordinates and color code each file with a unique color, not each point coordinate
Code thus far:
import meshio
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('mesh_files/*.vtk'))
mesh = []
fig = plt.figure(figsize = (16, 10))
ax = plt.axes(projection = '3d')
colors = cm.rainbow(np.linspace(0, 1, 16))
for file in files:
mesh.append(meshio.read(file))
x = [m.points[:, 0] for m in mesh]
y = [m.points[:, 1] for m in mesh]
z = [m.points[:, 2] for m in mesh]
for a,b,c,d in zip(x,y,z,colors):
plt.scatter(a,b,c,color=d)
Background
x, y and z are all lists containing numpy arrays
<<len(x)
16
<<len(x[0])
99937
<<x[0].shape
(99937,)
<<type(x)
<class 'list'>
<<type(x[0])
<class 'numpy.ndarray'>
I believe the issue is with the colors and a possible mismatch in sizes
<<len(colors)
16
<<len(colors[0])
4
Error
RuntimeWarning: invalid value encountered in sqrt
EDIT: I can individually call scatter and manually enter a different color to create the below plot, but this would take forever with 10+ files, so I want it in a loop or function of some sort.
EDIT2: I was able to get this plot, which is nice that the colors are different for each files' data, but the z scale is too small, compared to the first plot, and it looks like data are missing, it should like like the first plot in terms of z depth values, but with 16 unique colors as in the second plot. The first plot is only plotting 3 files manually
If you don't need the meshes afterwards you can avoid allocating a bunch of memory
...
colors = iter(cm.rainbow(np.linspace(0, 1, 16)))
for file in files:
plt.scatter(*meshio.read(file).points.T, c=[next(colors)], label=file)
plt.legend()
plt.show()
or, if you need the meshes afterwards we can use a container
...
meshes = []
colors = iter(cm.rainbow(np.linspace(0, 1, 16)))
for file in files:
meshes.append(meshio.read(file))
plt.scatter(*meshes[-1].points.T, c=[next(colors)], label=file)
plt.legend()
plt.show()
NB scatter in 3D needs x, y and z, all with shape (N,), while meshobj.points has shape (N, 3) so we first transpose it (shape is now (3, N)) and finally we unpack (using the star "*" operator) the 2D array to get the requested three (N,) arrays.
I think you mistake comes from the mesh list that you are updating at every step. You plot the whole mesh list every step, such that your first file is plotted 16 times, in 16 different colors.
The simplest code could be:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('mesh_files/*.vtk'))
fig = plt.figure(figsize = (16, 10))
ax = plt.axes(projection = '3d')
colors = cm.rainbow(np.linspace(0, 1, len(files)))
for file in files:
data = meshio.read(file).points
x = data[:, 0]
y = data[:, 1]
z = data[:, 2]
plt.scatter(x, y, z, color = colors[files.index(file)])
If you want to store all the points in a list called mesh, you can modify it as :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('mesh_files/*.vtk'))
mesh = []
fig = plt.figure(figsize = (16, 10))
ax = plt.axes(projection = '3d')
colors = cm.rainbow(np.linspace(0, 1, len(files)))
for file in files:
mesh.append(meshio.read(file).points)
x = mesh[-1][:, 0]
y = mesh[-1][:, 1]
z = mesh[-1][:, 2]
plt.scatter(x, y, z, color = colors[files.index(file)])
such that you only plot the points corresponding the file you just read at every step.
As was mentioned previously, the problem you're experiencing is which loop the color selection is occurring in.
color = iter(cm.rainbow(np.linspace(0, 1, len(files))))
for file in files:
d = next(color) #set the color for each file instead of inside the loop
mesh.append(meshio.read(file))
x = [m.points[:, 0] for m in mesh]
y = [m.points[:, 1] for m in mesh]
z = [m.points[:, 2] for m in mesh]
for a,b,c in zip(x,y,z):
plt.scatter(a,b,c,color=d)
This code below is currently working for me, for the most part.
I changed plt.scatter... to ax.scatter... and it fixed the z-axis scaling issue that I mentioned in EDIT 2 above.
I also changed to ax = Axes3D(fig)
Thanks to everyone's help! I will work with this for now.
import meshio
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('path/to/vtk/files/*_mesh.vtk'))
meshes = []
fig = plt.figure(figsize = (16, 10))
ax = Axes3D(fig)
colors = iter(cm.rainbow(np.linspace(0, 1, len(files))))
for fyle in files:
ax.scatter(*meshio.read(fyle).points.T, c=[next(colors)])
plt.legend() #legend isn't plotting, will have to fix this
plt.show()

Stereo-Image and Depthmap to 3D-Scatterplot with Python and Matplotlib

I have a stereo-image and a depthmap of said image. I would like to make a scatterplot representing a 3d-Image of the picture. This is what i tried, but I get several errors, like the dimensions not fitting, etc.
The Problem is: the Scatter plot wants quadratic inputs. So I use the same length and widht. When i plot the picture I only see a line of points instead of the picture. What am I doing wrong?
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
import cv2
from mpl_toolkits.mplot3d import Axes3D
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
img = cv2.imread('helmet.jpg', 1)
dmap = cv2.imread('dmap_real.png', 1)
xarr = np.arange(3632)
yarr = np.arange(3632)
c = img[xarr,yarr,:] / 256
z = dmap[xarr, yarr, 0]
ax.scatter(xarr, xarr, z, c=c, label='point cloud')
ax.legend()
plt.show()
Here are the used Pictures as reference:
depthmap: http://i.imgur.com/1OzNBIn.png
stereo-image: http://i.imgur.com/LMiek3H.jpg
The numpy function meshgrid might be what you're looking for. That will give you the x and y values for a grid the size of your image. If you plot every point in the image with scatter, you won't see your original image and it will be slow. Here's an example of plotting points from an image over an image:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
# Example image
image_file = cbook.get_sample_data('grace_hopper.png')
image = plt.imread(image_file)
(r, c, b) = np.shape(image)
# X and Y coordinates of points in the image, spaced by 10.
(X, Y) = np.meshgrid(range(0, c, 10), range(0, r, 10))
# Display the image
plt.imshow(image)
# Plot points from the image.
plt.scatter(X, Y, image[Y,X])
plt.show()

Categories