I've been trying to use scipy.interpolate.bisplrep() and scipy.interpolate.interp2d() to find interpolants for data on my (218x135) 2D spherical-polar grid. To these I pass 2D arrays, X and Y, of the Cartesian positions of my grid nodes. I keep getting errors like the following (for linear interp. with interp2d):
"Warning: No more knots can be added because the additional knot would coincide
with an old one. Probably cause: s too small or too large a weight
to an inaccurate data point. (fp>s)
kx,ky=1,1 nx,ny=4,5 m=29430 fp=1390609718.902140 s=0.000000"
I get a similar result for bivariate splines with the default value of the smoothing parameter s etc. My data are smooth. I've attached my code below in case I'm doing something obviously wrong.
Any ideas?
Thanks!
Kyle
class Field(object):
Nr = 0
Ntheta = 0
grid = np.array([])
def __init__(self, Nr, Ntheta, f):
self.Nr = Nr
self.Ntheta = Ntheta
self.grid = np.empty([Nr, Ntheta])
for i in range(Nr):
for j in range(Ntheta):
self.grid[i,j] = f[i*Ntheta + j]
def calculate_lines(filename):
ri,ti,r,t,Br,Bt,Bphi,Bmag = np.loadtxt(filename, skiprows=3,\
usecols=(1,2,3,4,5,6,7,9), unpack=True)
Nr = int(max(ri)) + 1
Ntheta = int(max(ti)) + 1
### Initialise coordinate grids ###
X = np.empty([Nr, Ntheta])
Y = np.empty([Nr, Ntheta])
for i in range(Nr):
for j in range(Ntheta):
indx = i*Ntheta + j
X[i,j] = r[indx]*sin(t[indx])
Y[i,j] = r[indx]*cos(t[indx])
### Initialise field objects ###
Bradial = Field(Nr=Nr, Ntheta=Ntheta, f=Br)
### Interpolate the fields ###
intp_Br = interpolate.interp2d(X, Y, Bradial.grid, kind='linear')
#rbf_0 = interpolate.Rbf(X,Y, Bradial.grid, epsilon=2)
return
Added 27Aug: Kyle followed this up on a
scipy-user thread.
30Aug: #Kyle, it looks as though there's a mixup between Cartesion X,Y and polar Xnew,Ynew.
See "polar" in the too-long notes below.
# griddata vs SmoothBivariateSpline
# http://stackoverflow.com/questions/3526514/
# problem-with-2d-interpolation-in-scipy-non-rectangular-grid
# http://www.scipy.org/Cookbook/Matplotlib/Gridding_irregularly_spaced_data
# http://en.wikipedia.org/wiki/Natural_neighbor
# http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html
from __future__ import division
import sys
import numpy as np
from scipy.interpolate import SmoothBivariateSpline # $scipy/interpolate/fitpack2.py
from matplotlib.mlab import griddata
__date__ = "2010-10-08 Oct" # plot diffs, ypow
# "2010-09-13 Sep" # smooth relative
def avminmax( X ):
absx = np.abs( X[ - np.isnan(X) ])
av = np.mean(absx)
m, M = np.nanmin(X), np.nanmax(X)
histo = np.histogram( X, bins=5, range=(m,M) ) [0]
return "av %.2g min %.2g max %.2g histo %s" % (av, m, M, histo)
def cosr( x, y ):
return 10 * np.cos( np.hypot(x,y) / np.sqrt(2) * 2*np.pi * cycle )
def cosx( x, y ):
return 10 * np.cos( x * 2*np.pi * cycle )
def dipole( x, y ):
r = .1 + np.hypot( x, y )
t = np.arctan2( y, x )
return np.cos(t) / r**3
#...............................................................................
testfunc = cosx
Nx = Ny = 20 # interpolate random Nx x Ny points -> Newx x Newy grid
Newx = Newy = 100
cycle = 3
noise = 0
ypow = 2 # denser => smaller error
imclip = (-5., 5.) # plot trierr, splineerr to same scale
kx = ky = 3
smooth = .01 # Spline s = smooth * z2sum, see note
# s is a target for sum (Z() - spline())**2 ~ Ndata and Z**2;
# smooth is relative, s absolute
# s too small => interpolate/fitpack2.py:580: UserWarning: ier=988, junk out
# grr error message once only per ipython session
seed = 1
plot = 0
exec "\n".join( sys.argv[1:] ) # run this.py N= ...
np.random.seed(seed)
np.set_printoptions( 1, threshold=100, suppress=True ) # .1f
print 80 * "-"
print "%s Nx %d Ny %d -> Newx %d Newy %d cycle %.2g noise %.2g kx %d ky %d smooth %s" % (
testfunc.__name__, Nx, Ny, Newx, Newy, cycle, noise, kx, ky, smooth)
#...............................................................................
# interpolate X Y Z to xnew x ynew --
X, Y = np.random.uniform( size=(Nx*Ny, 2) ) .T
Y **= ypow
# 1d xlin ylin -> 2d X Y Z, Ny x Nx --
# xlin = np.linspace( 0, 1, Nx )
# ylin = np.linspace( 0, 1, Ny )
# X, Y = np.meshgrid( xlin, ylin )
Z = testfunc( X, Y ) # Ny x Nx
if noise:
Z += np.random.normal( 0, noise, Z.shape )
# print "Z:\n", Z
z2sum = np.sum( Z**2 )
xnew = np.linspace( 0, 1, Newx )
ynew = np.linspace( 0, 1, Newy )
Zexact = testfunc( *np.meshgrid( xnew, ynew ))
if imclip is None:
imclip = np.min(Zexact), np.max(Zexact)
xflat, yflat, zflat = X.flatten(), Y.flatten(), Z.flatten()
#...............................................................................
print "SmoothBivariateSpline:"
fit = SmoothBivariateSpline( xflat, yflat, zflat, kx=kx, ky=ky, s = smooth * z2sum )
Zspline = fit( xnew, ynew ) .T # .T ??
splineerr = Zspline - Zexact
print "Zspline - Z:", avminmax(splineerr)
print "Zspline: ", avminmax(Zspline)
print "Z: ", avminmax(Zexact)
res = fit.get_residual()
print "residual %.0f res/z2sum %.2g" % (res, res / z2sum)
# print "knots:", fit.get_knots()
# print "Zspline:", Zspline.shape, "\n", Zspline
print ""
#...............................................................................
print "griddata:"
Ztri = griddata( xflat, yflat, zflat, xnew, ynew )
# 1d x y z -> 2d Ztri on meshgrid(xnew,ynew)
nmask = np.ma.count_masked(Ztri)
if nmask > 0:
print "info: griddata: %d of %d points are masked, not interpolated" % (
nmask, Ztri.size)
Ztri = Ztri.data # Nans outside convex hull
trierr = Ztri - Zexact
print "Ztri - Z:", avminmax(trierr)
print "Ztri: ", avminmax(Ztri)
print "Z: ", avminmax(Zexact)
print ""
#...............................................................................
if plot:
import pylab as pl
nplot = 2
fig = pl.figure( figsize=(10, 10/nplot + .5) )
pl.suptitle( "Interpolation error: griddata - %s, BivariateSpline - %s" % (
testfunc.__name__, testfunc.__name__ ), fontsize=11 )
def subplot( z, jplot, label ):
ax = pl.subplot( 1, nplot, jplot )
im = pl.imshow(
np.clip( z, *imclip ), # plot to same scale
cmap=pl.cm.RdYlBu,
interpolation="nearest" )
# nearest: squares, else imshow interpolates too
# todo: centre the pixels
ny, nx = z.shape
pl.scatter( X*nx, Y*ny, edgecolor="y", s=1 ) # for random XY
pl.xlabel(label)
return [ax, im]
subplot( trierr, 1,
"griddata, Delaunay triangulation + Natural neighbor: max %.2g" %
np.nanmax(np.abs(trierr)) )
ax, im = subplot( splineerr, 2,
"SmoothBivariateSpline kx %d ky %d smooth %.3g: max %.2g" % (
kx, ky, smooth, np.nanmax(np.abs(splineerr)) ))
pl.subplots_adjust( .02, .01, .92, .98, .05, .05 ) # l b r t
cax = pl.axes([.95, .05, .02, .9]) # l b w h
pl.colorbar( im, cax=cax ) # -1.5 .. 9 ??
if plot >= 2:
pl.savefig( "tmp.png" )
pl.show()
Notes on 2d interpolation, BivariateSpline vs. griddata.
scipy.interpolate.*BivariateSpline and matplotlib.mlab.griddata
both take 1d arrays as arguments:
Znew = griddata( X,Y,Z, Xnew,Ynew )
# 1d X Y Z Xnew Ynew -> interpolated 2d Znew on meshgrid(Xnew,Ynew)
assert X.ndim == Y.ndim == Z.ndim == 1 and len(X) == len(Y) == len(Z)
The inputs X,Y,Z describe a surface or cloud of points in 3-space:
X,Y (or latitude,longitude or ...) points in a plane,
and Z a surface or terrain above that.
X,Y may fill most of the rectangle [Xmin .. Xmax] x [Ymin .. Ymax],
or may be just a squiggly S or Y inside it.
The Z surface may be smooth, or smooth + a bit of noise,
or not smooth at all, rough volcanic mountains.
Xnew and Ynew are usually also 1d, describing a rectangular grid
of |Xnew| x |Ynew| points where you want to interpolate or estimate Z.
Znew = griddata(...) returns a 2d array over this grid, np.meshgrid(Xnew,Ynew):
Znew[Xnew0,Ynew0], Znew[Xnew1,Ynew0], Znew[Xnew2,Ynew0] ...
Znew[Xnew0,Ynew1] ...
Znew[Xnew0,Ynew2] ...
...
Xnew,Ynew points far from any of the input X,Y s spell trouble.
griddata checks this:
A masked array is returned if any grid points are outside convex
hull defined by input data (no extrapolation is done).
("Convex hull" is the area inside an imaginary
rubber band stretched around all the X,Y points.)
griddata works by first constructing a Delaunay triangulation
of the input X,Y, then doing
Natural neighbor
interpolation. This is robust and quite fast.
BivariateSpline, though, can extrapolate,
generating wild swings without warning.
Furthermore, all the *Spline routines in Fitpack
are very sensitive to smoothing parameter S.
Dierckx's book (books.google isbn 019853440X p. 89) says:
if S is too small, the spline approximation is too wiggly
and picks up too much noise (overfit);
if S is too large the spline will be too smooth
and signal will be lost (underfit).
Interpolation of scattered data is hard, smoothing not easy, both together really hard.
What should an interpolator do with big holes in XY, or with very noisy Z ?
("If you want to sell it, you're going to have to describe it.")
Yet more notes, fine print:
1d vs 2d: Some interpolators take X,Y,Z either 1d or 2d.
Others take 1d only, so flatten before interpolating:
Xmesh, Ymesh = np.meshgrid( np.linspace(0,1,Nx), np.linspace(0,1,Ny) )
Z = f( Xmesh, Ymesh ) # Nx x Ny
Znew = griddata( Xmesh.flatten(), Ymesh.flatten(), Z.flatten(), Xnew, Ynew )
On masked arrays: matplotlib handles them just fine,
plotting only unmasked / non-NaN points.
But I wouldn't bet that that a bozo numpy/scipy functions would work at all.
Check for interpolation outside the convex hull of X,Y like this:
Znew = griddata(...)
nmask = np.ma.count_masked(Znew)
if nmask > 0:
print "info: griddata: %d of %d points are masked, not interpolated" % (
nmask, Znew.size)
# Znew = Znew.data # array with NaNs
On polar coordinates:
X,Y and Xnew,Ynew should be in the same space,
both Cartesion, or both in [rmin .. rmax] x [tmin .. tmax].
To plot (r, theta, z) points in 3d:
from mpl_toolkits.mplot3d import Axes3D
Znew = griddata( R,T,Z, Rnew,Tnew )
ax = Axes3D(fig)
ax.plot_surface( Rnew * np.cos(Tnew), Rnew * np.sin(Tnew), Znew )
See also (haven't tried this):
ax = subplot(1,1,1, projection="polar", aspect=1.)
ax.pcolormesh(theta, r, Z)
Two tips for the wary programmer:
check for outliers, or funny scaling:
def minavmax( X ):
m = np.nanmin(X)
M = np.nanmax(X)
av = np.mean( X[ - np.isnan(X) ]) # masked ?
histo = np.histogram( X, bins=5, range=(m,M) ) [0]
return "min %.2g av %.2g max %.2g histo %s" % (m, av, M, histo)
for nm, x in zip( "X Y Z Xnew Ynew Znew".split(),
(X,Y,Z, Xnew,Ynew,Znew) ):
print nm, minavmax(x)
check interpolation with simple data:
interpolate( X,Y,Z, X,Y ) -- interpolate at the same points
interpolate( X,Y, np.ones(len(X)), Xnew,Ynew ) -- constant 1 ?
Related
By using python, I want to plot 6 points in same diagram where first 3 points are 3 initial (x,y,z) position points and other 3 points are 3 updated position points (X,Y,Z).
The updated points formulas are X =𝑥+𝑟𝜇𝑥 , Y=𝑦+𝑟𝜇y , and Z=𝑧+𝑟*𝜇z .
I plotted the 3 initial points. The problem is that all the 3 updated points have same value, then same point in the diagram. How can I make them different?
I think the problem is that in the Updated xyz positions, it takes only this point (x,y,z)=(3,6,9). How can I make these 3 updated position points different?
# Updated Positions and Direction Cosines
############################################
# Request libraries
############################################
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import random
from random import gauss
import math
from math import log, cos, acos
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
############################################
# Initial Positions and Direction Cosines
############################################
# Photons Number
PhotonsTotalNumber = 3
#PhotonsUniqueNumber= np.array(0,PhotonsTotalNumber)
# Initial Direction Cosines
Initial_ux= math.cos(math.radians(90))
Initial_uy= math.cos(math.radians(90))
Initial_uz= math.cos(math.radians(0))
# Initial xyz Positions
Initialx = np.array([1,2,3])
Initialy = np.array([4,5,6])
Initialz = np.array([7,8,9])
for x, y, z in zip(Initialx, Initialy, Initialz):
text = '({},{}, {})'.format(x,y,z)
ax.text(x,y,z, text, zdir=(1, 1, 1))
print(x,y,z)
############################################
# Photon Propagation Equations
############################################
# Random Numbers Function
def get_rand_number(min_value, max_value):
range = max_value - min_value
choice = random.uniform(0,1)
return min_value + range*choice
# Photon path length
r = -math.log(get_rand_number(0,1))/0.15 # 0.15 attenuation coefficients for clear water
# The anisotropy factor g
values = [1,-1]
g = np.random.choice(values)
g = int(g)
# Scattering Angles (theta and phi)
theta = (((1 + g*g - ((1 - g*g)/(1 - g + 2*g*get_rand_number(0,1)))**2)/(2*g)))
phi = 2 * math.radians(180) * get_rand_number(0,1)
Theta = gauss( 0, theta)
Phi = gauss( 0, phi)
############################################
# Updated Positions and Direction Cosines
############################################
# Updated direction cosines
New_ux = math.sin(Theta) * math.cos(Phi)
New_uy = math.sin(Theta) * math.sin(Phi)
New_uz = (Initial_uz/abs(Initial_uz))*math.cos(Theta)
# Updated xyz positions
Updatedx = x + (r * New_ux)
Updatedy = y + (r * New_uy)
Updatedz = z + (r * New_uz)
UpdatedX = np.array ([Updatedx,Updatedx,Updatedx])
UpdatedY = np.array ([Updatedy,Updatedy,Updatedy])
UpdatedZ = np.array ([Updatedz,Updatedz,Updatedz])
for X, Y, Z in zip(UpdatedX, UpdatedY, UpdatedZ):
text = '({},{}, {})'.format(X,Y,Z)
ax.text(X,Y,Z, text, zdir=(1, 1, 1))
ax.scatter(Initialx,Initialy,Initialz, s=100,c='b', label='True Position')
ax.scatter(UpdatedX,UpdatedY,UpdatedZ, s=100,c='g', label='True Position')
ax.set_xlabel("x axis")
ax.set_ylabel("y axis")
ax.set_zlabel("z axis")
plt.show()
####################
I want to plot the motion of a positive charge in a cylindrically symmetric magnetic field.
I am assuming a cylinder around the z-axis, with the magnetic field going in clockwise direction. The B-field has magnitude of 6T and the distance R from the z-axis is 3m. The charged particle is launched in positive direction along the z-axis and has the energy 2 MeV.
I am uncertain of how to simulate this B-field correctly. I was thinking to create the B-field in cylindrical coordinates,
cylinder from 0 to 2pi:
theta=numpy.linspace(0, 2*numpy.pi, 360)
x=r*numpy.cos(theta)
y=r*numpy.sin(theta)
Bx=B0*(numpy.cos(numpy.arctan2(y,x)
By=B0*(-numpy.sin(numpy.arctan2(y,x)))
Bz=0
And then create a vector B=[Bx, By, Bz] from which I would calculate the acceleration using Lorentz force for a timespan t.
But I think I am going in circles with this. Is there another way to create a cylindrically symmetric magnetic field?
With solve_ivp():
There are only two functions, the first one, B() takes care of the geometry of the magnetic field (rotationally invariant relative to the z axis and radially invariant, with always the same magnitude at each point), and the second one f() takes care of the physics, providing the velocity and the Lorentz's acceleration generated by the magnetic field B(), calculated at each point. This latter function is the right hand side of the differential equations of motion, which goes into solve_ivp().
'''
Dynamics in cylindrical homogeneous magnetic field
'''
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
def B(y, B_templet):
r = np.array([y[0], y[1], 0]) # vector aligned with the x,y projection of the position vector y
r = r / np.sqrt(r[0]**2 + r[1]**2) # unit vector aligned with the x,y projection of the position vector y
r_perp = np.array([-r[1], r[0], 0]) # unit vector perpendicular to the x,y projection of position vector y
B_field = B_templet[0] * r + B_templet[1] * r_perp
B_field[2] = B_templet[2]
return B_field
def f(t, y, parameters):
mass, charge, B_templet = parameters
return np.concatenate( (y[3:6], (charge/mass)*np.cross(y[3:6], B(y[0:3], B_templet))) )
charge = 1
mass = 1
B_direction = np.array([0.3,1,0.1])
B_magnitude = 1
xv_start = np.array([3, 0, 0, 0, 0, 2])
time_step = 0.01
n_iter = 5000
t_span=[0,n_iter*time_step]
B_direction = B_magnitude * B_direction / np.sqrt(B_direction.dot(B_direction))
sol = solve_ivp(fun = lambda t, y : f(t, y, (mass, charge, B_direction)), t_span=t_span, y0=xv_start, max_step=time_step)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
r = 35
ax.set_xlim((-r, r))
ax.set_ylim((-r, r))
ax.set_zlim((-r, r))
ax.plot(sol.y[0,:], sol.y[1,:], sol.y[2,:], 'r-')
ax.plot(sol.y[0,0], sol.y[1,0], sol.y[2,0], 'bo')
ax.plot(sol.y[0,-1], sol.y[1,-1], sol.y[2,-1], 'go')
plt.show()
Maybe this is what you want?
'''
Dynamics in a cylindrical magnetic field
'''
import numpy as np
import matplotlib.pyplot as plt
def B(x, B_templet):
r = np.array([x[0], x[1], 0]) # vector aligned with the x,y projection of the position vector x
r = r / np.sqrt(r[0]**2 + r[1]**2) # unit vector aligned with the x,y projection of the position vector x
r_perp = np.array([-r[1], r[0], 0]) # unit vector perpendicular to the x,y projection of the position vector x
B_field = B_templet[0] * r + B_templet[1] * r_perp
B_field[2] = B_templet[2]
return B_field
def f(x, mass, charge, B_templet):
return np.concatenate( (x[3:6], (charge/mass)*np.cross(x[3:6], B(x[0:3], B_templet))) )
def time_step_f(x, mass, charge, B_templet, t_step):
k1 = f(x, mass, charge, B_templet)
k2 = f(x + t_step*k1/2, mass, charge, B_templet)
k3 = f(x + t_step*k2/2, mass, charge, B_templet)
k4 = f(x + t_step*k3, mass, charge, B_templet)
return x + t_step * (k1 + 2*k2 + 2*k3 + k4) / 6
def flow_f(x_initial, mass, charge, B_templet, t_step, n_iter):
traj = np.empty( (n_iter, x_initial.shape[0]), dtype = float )
traj[0, :] = x_initial
for m in range(n_iter-1):
traj[m+1,:] = time_step_f(traj[m,:], mass, charge, B_templet, t_step)
return traj
charge = 1
mass = 1
B_direction = np.array([0.3,1,0.1])
B_magnitude = 3
xv_start = np.array([3, 0, 0, 0, 0, 2])
time_step = 0.01
n_iter = 5000
B_direction = B_magnitude * B_direction / np.sqrt(B_direction.dot(B_direction))
xv = flow_f(xv_start, mass, charge, B_direction, time_step, n_iter)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
r = 20
ax.set_xlim((-r, r))
ax.set_ylim((-r, r))
ax.set_zlim((-r, r))
ax.plot(xv[:,0], xv[:,1], xv[:,2], 'r-')
ax.plot(xv[0,0], xv[0,1], xv[0,2], 'bo')
ax.plot(xv[-1,0], xv[-1,1], xv[-1,2], 'go')
plt.show()
I have spectroscopy data with some very sharp peaks as seen in blue curve. I would like to make the peaks a bit more smooth like the orange curve in the plot.
I thought the easiest way to do this is to convolve my data points with Gaussians. I know both numpy and scipy have convolve functions but I am not sure if I need 1D or 2D convolution to get what I need. So far I tried convolve1d and gaussian_filter1d from scipy and convolve from numpy. None of them improved the sharp lines connecting the data points. I also don't know how to choose the correct sigma or weights...
The text file containing the data points is here.
The orange curve is generated from a visualisation programme and I wish to be able to generate it myself with python rather than using the programme.
EDIT:
New link for file
Looks like you want a "kernel density estimator" which is implemented by:
from scipy.stats import gaussian_kde
X = np.random.rand(50) * 3500
Y = np.random.rand(50) * 50
xi = linspace(0, 3500, 1000)
kde = gaussian_kde(X, weights = Y, bw_method = .01) #tune `bw_method` to get the bandwidth you want
plt.plot(xi, kde.pdf(xi))
You may also need to adjust the y scaling of the graph to match your requirements
This is manually reproducing the orange curve given in the OP. Turns out it is convoluted with a Lorentzian not Gaussian.
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import find_peaks
from scipy.optimize import curve_fit
def gs( x, x0, a, s ):
return a * np.exp( -( x - x0 )**2 / ( 2 * s**2 ) )
def cs( x, x0, a, s ):
return a / ( ( x - x0 )**2 + s**2 )
conrange = 40000
### gasiian is no good
# ~condata = np.fromiter( ( gs(x, 0, 1, 1800 ) for x in np.arange( -5000, 5000 ) ), np.float )
### Cauchy looks much better
condata = np.fromiter(
(
cs( x, 0, 1, 2000 ) for x in np.arange( -conrange, conrange )
), np.float
)
### shift can be zero.
### Amplitude does not matter as it will be scaled later anyway
### width matters of course, but is adjusted manually for the moment.
data = np.loadtxt("ir_data.txt")
xdata = data[:, 0]
ydata = data[:, 1]
xdataint = np.fromiter( ( int( x* 100 ) for x in xdata ), int )
xmin = xdataint[0]
xmax = xdataint[-1]
xfilled = np.arange( xmin , xdataint[-1] + 1 )
yfilled = np.zeros( len( xfilled ), dtype=np.float )
xfloat = np.fromiter( ( x / 100. for x in xfilled), float )
for x, y in zip( xdataint, ydata ):
yfilled[ x - xmin ] = y
### just putting a manual scale here, but the real one can be calculated
### from the convolution properties
yc = 1e6 * np.convolve( condata, yfilled, mode="full" )
xfull = np.arange(
-conrange + xmin, xmin + conrange + len( xfilled ) - 1
)
xfloat = np.fromiter( ( 0.01 * x for x in xfull ), float )
fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1 )
ax.plot( xdata, ydata, ls='', marker='o', ms=2 )
ax.plot( xfloat, yc, ls='-')
plt.show()
Disclaimer
This are preliminary results and only posted due to request from the author of the OP. The might be some refinement.
To make a long story short, I'd like to plot a generic 3D triangle mesh in python. Matplotlib seems to be the ideal candidate, but I'd go with any 3D rendering that can do what I'm about to describe.
Suppose I have a triangle mesh defined by X, Y, and Z, the 3D coordinates of a point cloud, each a vector of length n, and UVW, a 2D m-x-3 matrix in which each row is a triplet of indices into the point cloud. This triplet represents an individual triangle. In other words, I have m triangles over n points. In Matlab, to generated a 3D plot, I just do:
trisurf(UVW, X, Y, Z)
Does anyone have any experience with this? In particular, can mplots trisurf be shoehorned to work?
Depending on your performance needs, mayavi is likely to be best suited for this - as per Davis comment.
However, matplotlib comes with plot_trisurf to which you can perfectly pass generic UVW, X, Y , Z as you describe.
Exemple with a torus mesh:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
R = 1.
r = 0.8
n = 50
m = 50
def torus_triangles(n, m):
""" Returns triangles to mesh a (n, m) torus """
tri = []
for i in range(n):
for j in range(m):
a = i + j*(n)
b = ((i+1) % n) + j*n
d = i + ((j+1) % m) * n
c = ((i+1) % n) + ((j+1) % m) * n
tri += [[a, b, d], [b, c, d]]
return np.array(tri, dtype=np.int32)
theta0 = np.linspace(0, (2*np.pi), n, endpoint=False)
phi0 = np.linspace(0, (2*np.pi), m, endpoint=False)
theta, phi = np.meshgrid(theta0, phi0)
x = (R + r * np.sin(phi)) * np.cos(theta)
y = (R + r * np.sin(phi)) * np.sin(theta)
z = r * np.cos(phi)
triangles = torus_triangles(n , m)
triang = mtri.Triangulation(x.ravel(), y.ravel(), triangles)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(triang, z.ravel(), lw=0.2, edgecolor="black", color="grey",
alpha=0.5)
plt.show()
I was also looking for a solution to this problem and this discussion helped me to succeed. Here is how it works:
A solution to very similar problem was already given as a link here in the comment from GBy (see above: Colouring the surface of a sphere with a set of scalar values in matplotlib)
Transfering the knowledge to the problem here it results in creating an additional array containing the amplitudes and assigning it to the "underlying ScalarMappable through set_array method". The corresponding python code looks like this:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib import pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
colors = np.mean(CorticalImage[Face], axis=1)
collec = ax.plot_trisurf(Xcoordinates, Ycoordinates, Zcoordinates, triangles=Face, cmap=cm.jet, linewidth=0.2)
collec.set_array(colors)
collec.autoscale()
ax.view_init(30, 0)
cbar = fig.colorbar(collec)
The arrays Xcoordinates, Ycoordinates, Zcoordinates contain the X, Y and Z coordinates of the mesh nodes. When checking their shape with e.g. Xcoordinates.shape it should look like this (750,), where 750 is the number of mesh nodes. The matrix Face is the same as the matrix UVW in the original question asked by Larry. It is "a 2D m-x-3 matrix in which each row is a triplet of indices into the point cloud". If you check the shape of the matrix Face it should be something like (1496, 3), where 1496 is the number of triangles in the mesh and 3 is the number of nodes in one triangle. Finally, the array CorticalImage contains the amplitudes for every node in the mesh and these are the values, which we want to use for the colors of the mesh (and not the Z values). The shape of that array should be like the shapes of the coordinates arrays, i.e. (750,).
IMPORTANT!!! You can see that the number of nodes and the number of triangles are not equal. This is almost always the case. Additionally, the amplitudes are usually given for the nodes and not for the triangles. Consequently, the amplitudes should be calculated for the triangles in order to get the right colors in the plot. This is done in the line colors = np.mean(CorticalImage[Face], axis=1).
Plotly has an open-source trisurf Python implementation that is closer to MATLAB's trisurf().
Python code and examples here:
https://plot.ly/python/tri-surf/
Thought for completeness sake I would add a mayavi example here, using the mesh from GBy's answer.
import numpy as np
from mayavi import mlab
from tvtk.api import tvtk
R = 1.
r = 0.8
n = 50
m = 50
def torus_triangles(n, m):
""" Returns triangles to mesh a (n, m) torus """
tri = []
for i in range(n):
for j in range(m):
a = i + j*(n)
b = ((i+1) % n) + j*n
d = i + ((j+1) % m) * n
c = ((i+1) % n) + ((j+1) % m) * n
tri += [[a, b, d], [b, c, d]]
return np.array(tri, dtype=np.int32)
theta0 = np.linspace(0, (2*np.pi), n, endpoint=False)
phi0 = np.linspace(0, (2*np.pi), m, endpoint=False)
theta, phi = np.meshgrid(theta0, phi0)
x = (R + r * np.sin(phi)) * np.cos(theta)
y = (R + r * np.sin(phi)) * np.sin(theta)
z = r * np.cos(phi)
triangles = torus_triangles(n , m)
mesh = mlab.triangular_mesh(x,y,z, triangles, representation='wireframe',color=(0,0,1) )
mlab.show()
Yielding:
I have a bunch of cross plots with two sets of data and have been looking for a matploltib way of highlighting their plotted regions with smoothed polygon outlines.
At the moment i just use Adobe Illustrator and amend saved plot, but this is not ideal. Example:
I'd be grateful for any pointers/links to examples.
Cheers
Here, you have an example. I was written the main ideas, but obviously, you could do it better.
A short explanations:
1) You need to compute the convex-hull (http://en.wikipedia.org/wiki/Convex_hull)
2) With the hull, you could scale it to keep all your data inside.
3) You must to interpolate the resulting curve.
The first part was done in http://wiki.scipy.org/Cookbook/Finding_Convex_Hull. The second one is trivial. The third one is very general, and you could perform any method, there are a lot of different ways to do the same. I took the #Jaime's approach (Smooth spline representation of an arbitrary contour, f(length) --> x,y), which I think it's a very good method.
I hope it help you...
#Taken from http://wiki.scipy.org/Cookbook/Finding_Convex_Hull
import numpy as n, pylab as p, time
def _angle_to_point(point, centre):
'''calculate angle in 2-D between points and x axis'''
delta = point - centre
res = n.arctan(delta[1] / delta[0])
if delta[0] < 0:
res += n.pi
return res
def _draw_triangle(p1, p2, p3, **kwargs):
tmp = n.vstack((p1,p2,p3))
x,y = [x[0] for x in zip(tmp.transpose())]
p.fill(x,y, **kwargs)
def area_of_triangle(p1, p2, p3):
'''calculate area of any triangle given co-ordinates of the corners'''
return n.linalg.norm(n.cross((p2 - p1), (p3 - p1)))/2.
def convex_hull(points, graphic=False, smidgen=0.0075):
'''
Calculate subset of points that make a convex hull around points
Recursively eliminates points that lie inside two neighbouring points until only convex hull is remaining.
:Parameters:
points : ndarray (2 x m)
array of points for which to find hull
graphic : bool
use pylab to show progress?
smidgen : float
offset for graphic number labels - useful values depend on your data range
:Returns:
hull_points : ndarray (2 x n)
convex hull surrounding points
'''
if graphic:
p.clf()
p.plot(points[0], points[1], 'ro')
n_pts = points.shape[1]
assert(n_pts > 5)
centre = points.mean(1)
if graphic: p.plot((centre[0],),(centre[1],),'bo')
angles = n.apply_along_axis(_angle_to_point, 0, points, centre)
pts_ord = points[:,angles.argsort()]
if graphic:
for i in xrange(n_pts):
p.text(pts_ord[0,i] + smidgen, pts_ord[1,i] + smidgen, \
'%d' % i)
pts = [x[0] for x in zip(pts_ord.transpose())]
prev_pts = len(pts) + 1
k = 0
while prev_pts > n_pts:
prev_pts = n_pts
n_pts = len(pts)
if graphic: p.gca().patches = []
i = -2
while i < (n_pts - 2):
Aij = area_of_triangle(centre, pts[i], pts[(i + 1) % n_pts])
Ajk = area_of_triangle(centre, pts[(i + 1) % n_pts], \
pts[(i + 2) % n_pts])
Aik = area_of_triangle(centre, pts[i], pts[(i + 2) % n_pts])
if graphic:
_draw_triangle(centre, pts[i], pts[(i + 1) % n_pts], \
facecolor='blue', alpha = 0.2)
_draw_triangle(centre, pts[(i + 1) % n_pts], \
pts[(i + 2) % n_pts], \
facecolor='green', alpha = 0.2)
_draw_triangle(centre, pts[i], pts[(i + 2) % n_pts], \
facecolor='red', alpha = 0.2)
if Aij + Ajk < Aik:
if graphic: p.plot((pts[i + 1][0],),(pts[i + 1][1],),'go')
del pts[i+1]
i += 1
n_pts = len(pts)
k += 1
return n.asarray(pts)
if __name__ == "__main__":
import scipy.interpolate as interpolate
# fig = p.figure(figsize=(10,10))
theta = 2*n.pi*n.random.rand(1000)
r = n.random.rand(1000)**0.5
x,y = r*p.cos(theta),r*p.sin(theta)
points = n.ndarray((2,len(x)))
points[0,:],points[1,:] = x,y
scale = 1.03
hull_pts = scale*convex_hull(points)
p.plot(x,y,'ko')
x,y = [],[]
convex = scale*hull_pts
for point in convex:
x.append(point[0])
y.append(point[1])
x.append(convex[0][0])
y.append(convex[0][1])
x,y = n.array(x),n.array(y)
#Taken from https://stackoverflow.com/questions/14344099/numpy-scipy-smooth-spline-representation-of-an-arbitrary-contour-flength
nt = n.linspace(0, 1, 100)
t = n.zeros(x.shape)
t[1:] = n.sqrt((x[1:] - x[:-1])**2 + (y[1:] - y[:-1])**2)
t = n.cumsum(t)
t /= t[-1]
x2 = interpolate.spline(t, x, nt)
y2 = interpolate.spline(t, y, nt)
p.plot(x2, y2,'r--',linewidth=2)
p.show()
There are some useful papers, eg.:
http://repositorium.sdum.uminho.pt/bitstream/1822/6429/1/ConcaveHull_ACM_MYS.pdf
Also, you could try with: http://resources.arcgis.com/en/help/main/10.1/index.html#//007000000013000000
I don't know nothing about arcgis, but it looks fine.
I came across this and implemented easy to use functions as well as a couple of alternatives/improvements.
Improvements:
use a periodic interpolation which ensures smooth
use quadratic interpolation
now works for only positive points as well
using an alternative to the deprecated scipy.interpolate.spline function
Alternatives:
many different and configurable interpolation schemes
a rounded-corner convex hull version
Hope this helps someone along the way.
import sklearn.preprocessing
import sklearn.pipeline
import scipy.spatial
import numpy as np
def calculate_hull(
X,
scale=1.1,
padding="scale",
n_interpolate=100,
interpolation="quadratic_periodic",
return_hull_points=False):
"""
Calculates a "smooth" hull around given points in `X`.
The different settings have different drawbacks but the given defaults work reasonably well.
Parameters
----------
X : np.ndarray
2d-array with 2 columns and `n` rows
scale : float, optional
padding strength, by default 1.1
padding : str, optional
padding mode, by default "scale"
n_interpolate : int, optional
number of interpolation points, by default 100
interpolation : str or callable(ix,iy,x), optional
interpolation mode, by default "quadratic_periodic"
Inspired by: https://stackoverflow.com/a/17557853/991496
"""
if padding == "scale":
# scaling based padding
scaler = sklearn.pipeline.make_pipeline(
sklearn.preprocessing.StandardScaler(with_std=False),
sklearn.preprocessing.MinMaxScaler(feature_range=(-1,1)))
points_scaled = scaler.fit_transform(X) * scale
hull_scaled = scipy.spatial.ConvexHull(points_scaled, incremental=True)
hull_points_scaled = points_scaled[hull_scaled.vertices]
hull_points = scaler.inverse_transform(hull_points_scaled)
hull_points = np.concatenate([hull_points, hull_points[:1]])
elif padding == "extend" or isinstance(padding, (float, int)):
# extension based padding
# TODO: remove?
if padding == "extend":
add = (scale - 1) * np.max([
X[:,0].max() - X[:,0].min(),
X[:,1].max() - X[:,1].min()])
else:
add = padding
points_added = np.concatenate([
X + [0,add],
X - [0,add],
X + [add, 0],
X - [add, 0]])
hull = scipy.spatial.ConvexHull(points_added)
hull_points = points_added[hull.vertices]
hull_points = np.concatenate([hull_points, hull_points[:1]])
else:
raise ValueError(f"Unknown padding mode: {padding}")
# number of interpolated points
nt = np.linspace(0, 1, n_interpolate)
x, y = hull_points[:,0], hull_points[:,1]
# ensures the same spacing of points between all hull points
t = np.zeros(x.shape)
t[1:] = np.sqrt((x[1:] - x[:-1])**2 + (y[1:] - y[:-1])**2)
t = np.cumsum(t)
t /= t[-1]
# interpolation types
if interpolation is None or interpolation == "linear":
x2 = scipy.interpolate.interp1d(t, x, kind="linear")(nt)
y2 = scipy.interpolate.interp1d(t, y, kind="linear")(nt)
elif interpolation == "quadratic":
x2 = scipy.interpolate.interp1d(t, x, kind="quadratic")(nt)
y2 = scipy.interpolate.interp1d(t, y, kind="quadratic")(nt)
elif interpolation == "quadratic_periodic":
x2 = scipy.interpolate.splev(nt, scipy.interpolate.splrep(t, x, per=True, k=4))
y2 = scipy.interpolate.splev(nt, scipy.interpolate.splrep(t, y, per=True, k=4))
elif interpolation == "cubic":
x2 = scipy.interpolate.CubicSpline(t, x, bc_type="periodic")(nt)
y2 = scipy.interpolate.CubicSpline(t, y, bc_type="periodic")(nt)
else:
x2 = interpolation(t, x, nt)
y2 = interpolation(t, y, nt)
X_hull = np.concatenate([x2.reshape(-1,1), y2.reshape(-1,1)], axis=1)
if return_hull_points:
return X_hull, hull_points
else:
return X_hull
def draw_hull(
X,
scale=1.1,
padding="scale",
n_interpolate=100,
interpolation="quadratic_periodic",
plot_kwargs=None,
ax=None):
"""Uses `calculate_hull` to draw a hull around given points.
Parameters
----------
X : np.ndarray
2d-array with 2 columns and `n` rows
scale : float, optional
padding strength, by default 1.1
padding : str, optional
padding mode, by default "scale"
n_interpolate : int, optional
number of interpolation points, by default 100
interpolation : str or callable(ix,iy,x), optional
interpolation mode, by default "quadratic_periodic"
plot_kwargs : dict, optional
`matplotlib.pyplot.plot` kwargs, by default None
ax : `matplotlib.axes.Axes`, optional
[description], by default None
"""
if plot_kwargs is None:
plot_kwargs = {}
X_hull = calculate_hull(
X, scale=scale, padding=padding, n_interpolate=n_interpolate, interpolation=interpolation)
if ax is None:
ax= plt.gca()
plt.plot(X_hull[:,0], X_hull[:,1], **plot_kwargs)
def draw_rounded_hull(X, padding=0.1, line_kwargs=None, ax=None):
"""Plots a convex hull around points with rounded corners and a given padding.
Parameters
----------
X : np.array
2d array with two columns and n rows
padding : float, optional
padding between hull and points, by default 0.1
line_kwargs : dict, optional
line kwargs (used for `matplotlib.pyplot.plot` and `matplotlib.patches.Arc`), by default None
ax : matplotlib.axes.Axes, optional
axes to plat on, by default None
"""
default_line_kwargs = dict(
color="black",
linewidth=1
)
if line_kwargs is None:
line_kwargs = default_line_kwargs
else:
line_kwargs = {**default_line_kwargs, **line_kwargs}
if ax is None:
ax = plt.gca()
hull = scipy.spatial.ConvexHull(X)
hull_points = X[hull.vertices]
hull_points = np.concatenate([hull_points[[-1]], hull_points, hull_points[[0]]])
diameter = padding * 2
for i in range(1, hull_points.shape[0] - 1):
# line
# source: https://stackoverflow.com/a/1243676/991496
norm_next = np.flip(hull_points[i] - hull_points[i + 1]) * [-1, 1]
norm_next /= np.linalg.norm(norm_next)
norm_prev = np.flip(hull_points[i - 1] - hull_points[i]) * [-1, 1]
norm_prev /= np.linalg.norm(norm_prev)
# plot line
line = hull_points[i:i+2] + norm_next * diameter / 2
ax.plot(line[:,0], line[:,1], **line_kwargs)
# arc
angle_next = np.rad2deg(np.arccos(np.dot(norm_next, [1,0])))
if norm_next[1] < 0:
angle_next = 360 - angle_next
angle_prev = np.rad2deg(np.arccos(np.dot(norm_prev, [1,0])))
if norm_prev[1] < 0:
angle_prev = 360 - angle_prev
arc = patches.Arc(
hull_points[i],
diameter, diameter,
angle=0, fill=False, theta1=angle_prev, theta2=angle_next,
**line_kwargs)
ax.add_patch(arc)
if __name__ == '__main__':
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import patches
# np.random.seed(42)
X = np.random.random((20,2))
fig, ax = plt.subplots(1,1, figsize=(10,10))
ax.scatter(X[:,0], X[:,1])
draw_rounded_hull(X, padding=0.1)
draw_hull(X)
ax.set(xlim=[-1,2], ylim= [-1,2])
fig.savefig("_out/test.png")