Python-VTK 3D Spline Regression through STL model of vascular tree - python

I need to create a spline or polyline representation of a vascular tree model (see below).
The model is in a STL format, thus I have the x-y-z coordinates of all vertices. The lines should run through the center of the vessel mesh thus I thought that the best approach would be a spline regression through the vertex cloud. In addition it would be great if I can have the radius of the vessel at given points, e.g. the coordinates of the polyline.
I looked through this forum and the VTK website (assuming they have a straightforward implementation for this sort of thing) but so far I haven't found something I can use. Does anyone know of a Python module or VTK class (which I would call from Python) that can do this? The python modules I found on this are all for 2D data.
Thanks a lot!
EDIT:
I came across this library called VMTK that deals almost exclusively with vessel segmentation and has functionality for what they call 'centerline calculation'. However, they usually require the vessels to be 'cut' at their ends and 'source points' to be defined. In the case of my model, however, one can see that the end points are 'capped' which makes matters more complicated. If I find a solution I'll post here

I don't know any software or python classes exactly on your problem.
Maybe python interpolate.splev will help you with a single vessel.
You may try the following code as an example:
from scipy import interpolate
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
# 3D example
total_rad = 10
z_factor = 3
noise = 0.1
num_true_pts = 200
s_true = np.linspace(0, total_rad, num_true_pts)
x_true = np.cos(s_true)
y_true = np.sin(s_true)
z_true = s_true/z_factor
num_sample_pts = 100
s_sample = np.linspace(0, total_rad, num_sample_pts)
x_sample = np.cos(s_sample) + noise * np.random.randn(num_sample_pts)
y_sample = np.sin(s_sample) + noise * np.random.randn(num_sample_pts)
z_sample = s_sample/z_factor + noise * np.random.randn(num_sample_pts)
tck, u = interpolate.splprep([x_sample,y_sample,z_sample], s=2)
x_knots, y_knots, z_knots = interpolate.splev(tck[0], tck)
u_fine = np.linspace(0,1,num_true_pts)
x_fine, y_fine, z_fine = interpolate.splev(u_fine, tck)
fig2 = plt.figure(2)
ax3d = fig2.add_subplot(111, projection='3d')
# blue line shows true helix
ax3d.plot(x_true, y_true, z_true, 'b')
# red stars show distorted sample around a blue line
ax3d.plot(x_sample, y_sample, z_sample, 'r*')
# green line and dots show fitted curve
ax3d.plot(x_knots, y_knots, z_knots, 'go')
ax3d.plot(x_fine, y_fine, z_fine, 'g')
plt.show()
This code uses noisy centerline path of a single vessel and fit it with a smooth curve (see the result below):
interolation result
Usually, two user seeds are used to mark centerline ends, in the case of centerline representation as in VMTK.
The other way to get centerlines automatically is to voxelize your stl mesh, costruct a voxel skeleton, and separate skeletal segment to represent each vessel. Then you can interpolate each centerline to get the smooth curves. Unprocessed skeletal segments usualy have zigzags.

Related

Python 3d analysis (vedo): find largest spheres that can fit inside of a mesh?

I am using vedo to visualize and work with 3D models taken from a Lidar scan; they are .obj files that I open up.
In my application, I want to find the largest spheres the can 'fit inside' of my scanned object, if that makes sense. Imagine I have a room or hallway - basically I am trying to measure the amount of free space available.
So I want to fit in a bunch of spheres, and then see how big those spheres are. It's important that I can automate this process for whatever .obj file I load in, otherwise I would manually measure things...
Here is an example .obj file I open up, a crudely scanned hallway:
I'm not sure how to go about this with vedo, the closest example I found was 'Fit a Sphere' from the example website, which inspired my solution idea:
https://vedo.embl.es/#quick_start
https://github.com/marcomusy/vedo/blob/master/examples/advanced/fitspheres1.py
But then how would I define constraints on the spheres that they "fit inside" the mesh (hallway / room)?
For reference, here is my starter code, all it really does is read and display the mesh:
import vedo
import numpy as np
# https://vedo.embl.es/#quick_start
mesh_str = 'meshes/mesh1_indoor_scene_1.obj'
# Load a polygonal mesh, make it white and glossy:
scene = vedo.Mesh(mesh_str)
scene.c('white').lighting('glossy')
# Create two points:
p1 = vedo.Point([ 1,0,1], c='yellow')
p2 = vedo.Point([-1,0,2], c='red')
# Add colored light sources at the point positions:
l1 = vedo.Light(p1, c='yellow')
l2 = vedo.Light(p2, c='red')
# Show everything in one go:
vedo.show(scene, l1, l2, p1, p2, mesh_str, axes=True)
Appreciate any help, and open to thinking about other feasible solutions that can be implemented in python code.
If you can reconstruct a surface from the point cloud you can directly use surf.volume() or maybe generate a grid of points and use insidePoints() (instead of fitting spheres), e.g.:
from vedo import *
import numpy as np
surf = Mesh(dataurl+'cow.vtk').wireframe()
printc("true volume: ", surf.volume())
pts = []
step = 0.05
b = surf.bounds()
for x in np.arange(b[0], b[1], step):
for y in np.arange(b[2], b[3], step):
for z in np.arange(b[4], b[5], step):
pts.append([x,y,z])
pts = np.array(pts)
ipts = surf.inside_points(pts)
printc("approx volume:", ipts.npoints*step**3)
show(surf, ipts, N=2, axes=1)
true volume: 0.8346111674784022
approx volume: 0.8287500000000002
You can also post an issue here.

How to generate a paraboloid surface and make it respond to event

I am new to vispy and computer graphics. I have to generate a paraboloid according to a certain equation whose center and parameters vary according to the user input. I have gone through vispy documentation and examples and got some idea regarding the package.
The paraboloid that I need to generate should have rotational symmetry such as shown in the figure below:
Whereas what I got is here
My code is given below. I have modified the isosurface.py example in the vispy examples.
import sys
import numpy as np
from vispy import app, scene
from matplotlib import pyplot as plt
# Create a canvas with a 3D viewport
canvas = scene.SceneCanvas(keys='interactive')
view = canvas.central_widget.add_view()
## Define a scalar field from which we will generate an isosurface
def psi3(i, j, k, offset=(25, 25, 25)):
x = i-offset[0]
y = j-offset[1]
z = k-offset[2]
r = (0.2*x**2 + 0.2*y**2 - 4*z)
return r
# Create isosurface visual
data = np.fromfunction(psi3, (50, 50, 50))
surface = scene.visuals.Isosurface(data, level=data.max() / 4., color=(0.5, 0.6, 1, 1), shading='smooth', parent=view.scene)
surface.transform = scene.transforms.STTransform(translate=(-25, -25, -25))
# Add a 3D axis to keep us oriented
axis = scene.visuals.XYZAxis(parent=view.scene)
# Use a 3D camera
# Manual bounds; Mesh visual does not provide bounds yet
# Note how you can set bounds before assigning the camera to the viewbox
cam = scene.TurntableCamera(elevation=30, azimuth=30)
cam.set_range((-10, 10), (-10, 10), (-10, 10))
view.camera = cam
if __name__ == '__main__':
canvas.show()
if sys.flags.interactive == 0:
app.run()
My queries are the following:
How do I make the paraboloid look like in the first image (without the edges getting clipped off)
Is there a better way to draw the paraboloid other than using isosurfaces. The coefficients of the paraboloid should be varied by the user.
How to make the paraboloid respond to mouse events: hover, drag-drop etc. I understand from the documentation that I have to couple it to the Node class. I am unable to figure out the exact way to do this as I am a newbie.
Edit:
Here is the corresponding code using matplotlib for generating the required paraboloid. Also I am able to create a paraboloidal strip in matplotlib.
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
# Create the surface
radius = 5
hole_radius = 4
# Generate the grid in cylindrical coordinates
r = np.linspace(0, radius, 100)
theta = np.linspace(0, 2 * np.pi, 100)
R, THETA = np.meshgrid(r, theta)
X, Y = R * np.cos(THETA), R * np.sin(THETA)
a=0.6;b=0.6;c=0.6
Z1 = (X/a)**2+(Y/b)**2 # Elliptic paraboloid
# Do not plot the inner region
x = np.where(X**2+Y**2<=hole_radius**2,np.NAN,X)
y = np.where(X**2+Y**2<=hole_radius**2,np.NAN,Y)
# Plot the surface
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(x, y, Z1, cmap=cm.coolwarm, linewidth=0, antialiased=True, cstride=2, rstride=2)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
plt.show()
This results in the following:
The difference between the surface plot of vispy and matplotlib is that the latter work by accepting 2D arrays for x and y, whereas vispy's SurfacePlot() accepts only 1D vectors in both x and y.
Since the grid in cylindrical coordinates and converting them to cartesian coordinates for plotting, the grid cannot be generated by replicating the 1D x and y vectors.
Update:
As pointed by #djhoesem, isosurface is not the correct method to do this.
I'm the maintainer of vispy, but have very little isosurface experience. Let's see what I can answer:
The easiest thing I saw to do this is to make levels even smaller (I made it zero for testing). I'm not sure how this effects performance or the output exactly but the isosurface function mentions the paper it is based on. Maybe that can tell you more.
See Paul Bourke, "Polygonising a Scalar Field"
(http://paulbourke.net/geometry/polygonise/)
To make the parameters controllable by the user you could subclass the existing Isosurface class and add properties to control these. However, this will probably perform poorly if you want immediate feedback since you'd have to regenerate the numpy array and rerun all the other calculations on the CPU. The IsosurfaceVisual class expects volumetric data and then converts it to an isosurface. It generates a mesh that the MeshVisual understands (IsosurfaceVisual is a subclass of MeshVisual). If you want anything better you'd probably have to write your own shader code to do it. Depends what your exact requirements are (do you have to accept any formula with controllable coefficients?).
The scene.visuals Visual classes are already subclasses of the Node class so you don't have to do anything extra there. The other SceneCanvas-based examples should give you some ideas of how you can handle mouse events. That said, you mentioned "drag-drop", I'm not sure that would be something you'd handle in VisPy land but more likely in PyQt5 land (if that's the backend you are using).
Added the feature via pull request: https://github.com/vispy/vispy/pull/1863#event-3344873987
Made a small modification to the code and resolved the issue.
The corresponding example can be found here:
https://github.com/vispy/vispy/blob/master/examples/basics/visuals/axially_symmetric_surfaces.py

Artifact in image translation by Fourier phase shift in NumPy

The following code is creating an artefact when shifting images by Fourier phase shift:
The code of the phase shift itself is:
def phase_shift(fimage, dx, dy):
# Shift the phase of the fourier transform of an image
dims = fimage.shape
x, y = np.meshgrid(np.arange(-dims[1] / 2, dims[1] / 2), np.arange(-dims[0] / 2, dims[0] / 2))
kx = -1j * 2 * np.pi * x / dims[1]
ky = -1j * 2 * np.pi * y / dims[0]
shifted_fimage = fimage * np.exp(-(kx * dx + ky * dy))
return shifted_fimage
Usage to actually shift the image and get the shifted image:
def translate_by_phase_shift(image, dx, dy):
# Get the fourier transform
fimage = np.fft.fftshift(np.fft.fftn(image))
# Phase shift
shifted_fimage = phase_shift(fimage, dx, dy)
# Inverse transform -> translated image
shifted_image = np.real(np.fft.ifftn(np.fft.ifftshift(shifted_fimage)))
return shifted_image
The artifact is shown in the images below (image has even dimensions). Top row is context (entire image), bottom is the close-up in the red rectangle. Left: reference image. Middle: shifted with the above code and subject to artifact. Right: what it looks when using cv2.warpAffine() using the same shifts.
What am I doing wrong in the code above that creates this artifact?
[UPDATE] One of the comment suggested to use scipy.ndimage.fourier.fourier_shift(). So I did just that:
fourier_shifted_image = fourier_shift(np.fft.fftn(image), shift)
shifted_image = np.fft.ifftn(fourier_shifted_image)
and plotted the real part (shifted_image.real)
In fact, it also produces the exact same artifact (see image below, right-hand side), which I guess rule out a mistake in my custom code phase_shift() above?
[UPDATE] Now that we ruled out my phase_shift() function, here's a reproducible code, provided that you download the image array from here:
https://www.dropbox.com/s/dmbv56xfqkv8qqz/image.npy?dl=0
import os
import numpy as np
import matplotlib
matplotlib.use('TKAgg')
import matplotlib.pyplot as plt
from scipy.ndimage.fourier import fourier_shift
# Load the image (update path according to your case)
image = np.load(os.path.expanduser('~/DDS/46P_Wirtanen/image.npy'))
# Shift vector
shift = np.array([-3.75, -7.5 ])
# Phase-shift
fourier_shifted_image = fourier_shift(np.fft.fftn(image), shift)
shifted_image = np.fft.ifftn(fourier_shifted_image)
interp_method = 'hanning'
zoomfov = [1525, 1750, 1010, 1225]
vmin = np.percentile(image, 0.1)
vmax = np.percentile(image, 99.8)
fig, ax = plt.subplots(1,2, figsize=(14, 6), sharex=True,sharey=True)
ax[0].imshow(image, origin='lower', cmap='gray', vmin=vmin, vmax=vmax, interpolation=interp_method)
ax[0].set_title('Original image')
ax[1].imshow(shifted_image.real, origin='lower', cmap='gray', vmin=vmin, vmax=vmax, interpolation=interp_method)
ax[1].set_title('with scipy.ndimage.fourier.fourier_shift()')
plt.axis(zoomfov)
plt.tight_layout()
plt.show()
And the output looks like this:
[UPDATE]
Following the reply from Cris, I played with other interpolation methods from opencv with a logarithmic scaling of the intensity, I arrive to similar conclusions: the artifact is indeed also present with the Lanczos flag in cv2.warpAffine() - although very faint - and the cubic one clearly works better for this case of undersampled objects (here, stars):
The code to get to this:
# Compare interpolation methods
import cv2
# Fourier phase shift.
fourier_shifted = fourier_shift(np.fft.fftn(image), shift)
fourier_shifted_image = np.fft.ifftn(fourier_shifted).real
# Use opencv
Mtrans = np.float32([[1,0,shift[1]],[0,1, shift[0]]])
shifted_image_cubic = cv2.warpAffine(image, Mtrans, image.shape[::-1], flags=cv2.INTER_CUBIC)
shifted_image_lanczos = cv2.warpAffine(image, Mtrans, image.shape[::-1], flags=cv2.INTER_LANCZOS4)
zoomfov = [1525, 1750, 1010, 1225]
pmin = 2
pmax = 99.999
fig, ax = plt.subplots(1,3, figsize=(19, 7), sharex=True,sharey=True)
ax[0].imshow(fourier_shifted_image, origin='lower', cmap='gray',
vmin=np.percentile(fourier_shifted_image, pmin), vmax=np.percentile(fourier_shifted_image, pmax),
interpolation=interp_method, norm=LogNorm())
add_rectangle(zoomfov, ax[0])
ax[0].set_title('shifted with Fourier phase shift')
ax[1].imshow(shifted_image_cubic, origin='lower', cmap='gray',
vmin=np.percentile(shifted_image_cubic, pmin), vmax=np.percentile(shifted_image_cubic, pmax),
interpolation=interp_method, norm=LogNorm())
add_rectangle(zoomfov, ax[1])
ax[1].set_title('with cv2.warpAffine(...,flags=cv2.INTER_CUBIC)')
ax[2].imshow(shifted_image_lanczos, origin='lower', cmap='gray',
vmin=np.percentile(shifted_image_lanczos, pmin), vmax=np.percentile(shifted_image_lanczos, pmax),
interpolation=interp_method, norm=LogNorm())
#ax[2].imshow(shifted_image.real, origin='lower', cmap='gray', vmin=np.percentile(Llights_prep[frame], pmin), vmax=np.percentile(Llights_prep[frame], pmax), interpolation=interp_method)
add_rectangle(zoomfov, ax[2])
ax[2].set_title('with cv2.warpAffine(...,flags=cv2.INTER_LANCZOS4) ')
plt.axis(zoomfov)
plt.tight_layout()
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0.1, hspace=None)
plt.show()
And to reply to Cris' questions, indeed undersampled stars is of course inescapable with our modest amateur imaging systems (a poor 130 mm diameter), and I naively applied the same algorithm than what I use for professional, bigger instruments where this problem did not show.
The issue here is related to the way that the image is displayed, and to undersampling of the image. The code is correct, but inappropriate for the image.
1. Undersampling
The image has some very sharp transitions. Some stars show only in one single pixel. This is the hallmark of undersampling. In a properly sampled image, a single point of light (no matter how small) appears as an Airy disk (in the case of an ideal lens) in the image, and should occupy several pixels to prevent aliasing.
I'm assuming that the imaging cannot be changed, and is optimized for the application.
However, it is important to note how the image is sampled to be able to chose appropriate image processing tools.
In this case, the undersampled transitions mean that Fourier-based interpolation is not ideal.
2. Fourier-based interpolation
When shifting or scaling the image through the Fourier domain, a sinc interpolator is used. This is the ideal interpolator, and corresponds to a rectangular window in the Fourier domain. The sinc interpolator extends infinitely (or at least to the edges of the image), and decays with 1/x, which is quite slow. It is therefore not ideal in the case of undersampled images.
Because the undersampled image has sharp transitions, the sinc interpolator causes ringing (as do many other interpolators). And because of the slow decay of the sinc function, this ringing carries very far.
For example, the artificial sharp transition in this figure (blue), when interpolated through the Fourier domain (red), shows strong ringing that carries very far. This figure contrasts that with other interpolators that carry the ringing to different distances.
3. Image display
The image is displayed in the question by stretching the contrast very strongly. This is meant to allow observation of dim stars, but also strongly enhances the ringing caused the sharp transitions at those stars. In the plot above, imagine stretching and clipping the y-axis so you only see the region y=[0,0.01]. The ringing will look like a black-and-white pattern.
4. Alternative interpolators
The plot above shows the effect of different interpolators on a sharp transition. When applied to shift the image in the question, this is the result:
For the three methods on the bottom row, the ringing is not observable because it happens in a region that is fully saturated in the image display. Using a different range of grey-values in the display might show some ringing here too.
All these interpolators are designed to approximate the ideal sinc interpolator, but with a shorter spatial footprint so that they are cheaper to compute. Therefore, they all show some ringing at undersampled transitions.
The only interpolators that do not cause ringing at sharp edges are linear interpolation and nearest neighbor interpolation. Whether these are suitable for your application depends on the application, I cannot say.
This is the code I used to make the graph above:
a = double((0:99)<50);
b = resample(a,20,0,'ft');
c = resample(a,20,0,'3-cubic');
d = resample(a,20,0,'lanczos8');
a = resample(a,20,0,'nn');
plot(a)
hold on
plot(b)
plot(c)
plot(d)
legend({'input','sinc','cubic','Lanczos (8)'})
set(gca,'xlim',[600,1400],'ylim',[-0.2,1.2])
set(gca,'fontsize',16)
set(gca,'linewidth',1)
set(get(gca,'children'),'linewidth',2)
set(gca,'Position',[0.07,0.11,0.9,0.815])
The function resample is in DIPimage, you could use imresize instead, except for the 'ft' method, which simply pads the frequency domain with zeros, leading to a sinc interpolation.
Take a look at ndimage.fourier_shift, as far as I know that does not create any artefacts.

Matplotlib: How to increase colormap/linewidth quality in streamplot?

I have the following code to generate a streamplot based on an interp1d-Interpolation of discrete data:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from scipy.interpolate import interp1d
# CSV Import
a1array=pd.read_csv('a1.csv', sep=',',header=None).values
rv=a1array[:,0]
a1v=a1array[:,1]
da1vM=a1array[:,2]
a1 = interp1d(rv, a1v)
da1M = interp1d(rv, da1vM)
# Bx and By vector components
def bx(x ,y):
rad = np.sqrt(x**2+y**2)
if rad == 0:
return 0
else:
return x*y/rad**4*(-2*a1(rad)+rad*da1M(rad))/2.87445E-19*1E-12
def by(x ,y):
rad = np.sqrt(x**2+y**2)
if rad == 0:
return 4.02995937E-04/2.87445E-19*1E-12
else:
return -1/rad**4*(2*a1(rad)*y**2+rad*da1M(rad)*x**2)/2.87445E-19*1E-12
Bx = np.vectorize(bx, otypes=[np.float])
By = np.vectorize(by, otypes=[np.float])
# Grid
num_steps = 11
Y, X = np.mgrid[-25:25:(num_steps * 1j), 0:25:(num_steps * 1j)]
Vx = Bx(X, Y)
Vy = By(X, Y)
speed = np.sqrt(Bx(X, Y)**2+By(X, Y)**2)
lw = 2*speed / speed.max()+.5
# Star Radius
circle3 = plt.Circle((0, 0), 16.3473140, color='black', fill=False)
# Plot
fig0, ax0 = plt.subplots(num=None, figsize=(11,9), dpi=80, facecolor='w', edgecolor='k')
strm = ax0.streamplot(X, Y, Vx, Vy, color=speed, linewidth=lw,density=[1,2], cmap=plt.cm.jet)
ax0.streamplot(-X, Y, -Vx, Vy, color=speed, linewidth=lw,density=[1,2], cmap=plt.cm.jet)
ax0.add_artist(circle3)
cbar=fig0.colorbar(strm.lines,fraction=0.046, pad=0.04)
cbar.set_label('B[GT]', rotation=270, labelpad=8)
cbar.set_clim(0,1500)
cbar.draw_all()
ax0.set_ylim([-25,25])
ax0.set_xlim([-25,25])
ax0.set_xlabel('x [km]')
ax0.set_ylabel('z [km]')
ax0.set_aspect(1)
plt.title('polyEos(0.05,2), M/R=0.2, B_r(0,0)=1402GT', y=1.01)
plt.savefig('MR02Br1402.pdf',bbox_inches=0)
plt.show(fig0)
I uploaded the csv-file here if you want to try some stuff https://www.dropbox.com/s/4t7jixpglt0mkl5/a1.csv?dl=0.
Which generates the following plot:
I am actually pretty happy with the result except for one small detail, which I can not figure out: If one looks closely the linewidth and the color change in rather big steps, which is especially visible at the center:
Is there some way/option with which I can decrease the size of this steps to especially make the colormap smother?
I had another look at this and it wasnt as painful as I thought it might be.
Add:
subdiv = 15
points = np.arange(len(t[0]))
interp_points = np.linspace(0, len(t[0]), subdiv * len(t[0]))
tgx = np.interp(interp_points, points, tgx)
tgy = np.interp(interp_points, points, tgy)
tx = np.interp(interp_points, points, tx)
ty = np.interp(interp_points, points, ty)
after ty is initialised in the trajectories loop (line 164 in my version). Just substitute whatever number of subdivisions you want for subdiv = 15. All the segments in the streamplot will be subdivided into as many equally sized segments as you choose. The colors and linewidths for each will still be properly obtained from interpolating the data.
Its not as neat as changing the integration step but it does plot exactly the same trajectories.
If you don't mind changing the streamplot code (matplotlib/streamplot.py), you could simply decrease the size of the integration steps. Inside _integrate_rk12() the maximum step size is defined as:
maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
If you decrease that, lets say:
maxds = 0.1 * min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
I get this result (left = new, right = original):
Of course, this makes the code about 10x slower, and I haven't thoroughly tested it, but it seems to work (as a quick hack) for this example.
About the density (mentioned in the comments): I personally don't see the problem of that. It's not like we are trying to visualize the actual path line of (e.g.) a particle; the density is already some arbitrary (controllable) choice, and yes it is influenced by choices in the integration, but I don't thing that it changes the (not quite sure how to call this) required visualization we're after.
The results (density) do seem to converge a bit for decreasing step sizes, this shows the results for decreasing the integration step with a factor {1,5,10,20}:
You could increase the density parameter to get more smooth color transitions,
but then use the start_points parameter to reduce your overall clutter.
The start_points parameter allows you to explicity choose the location and
number of trajectories to draw. It overrides the default, which is to plot
as many as possible to fill up the entire plot.
But first you need one little fix to your existing code:
According to the streamplot documentation, the X and Y args should be 1d arrays, not 2d arrays as produced by mgrid.
It looks like passing in 2d arrays is supported, but it is undocumented
and it is currently not compatible with the start_points parameter.
Here is how I revised your X, Y, Vx, Vy and speed:
# Grid
num_steps = 11
Y = np.linspace(-25, 25, num_steps)
X = np.linspace(0, 25, num_steps)
Ygrid, Xgrid = np.mgrid[-25:25:(num_steps * 1j), 0:25:(num_steps * 1j)]
Vx = Bx(Xgrid, Ygrid)
Vy = By(Xgrid, Ygrid)
speed = np.hypot(Vx, Vy)
lw = 3*speed / speed.max()+.5
Now you can explicitly set your start_points parameter. The start points are actually
"seed" points. Any given stream trajectory will grow in both directions
from the seed point. So if you put a seed point right in the center of
the example plot, it will grow both up and down to produce a vertical
stream line.
Besides controlling the number of trajectories, using the
start_points parameter also controls the order they are
drawn. This is important when considering how trajectories terminate.
They will either hit the border of the plot, or they will terminate if
they hit a cell of the plot that already has a trajectory. That means
your first seeds will tend to grow longer and your later seeds will tend
to get limited by previous ones. Some of the later seeds may not grow
at all. The default seeding strategy is to plant a seed at every cell,
which is pretty obnoxious if you have a high density. It also orders
them by planting seeds first along the plot borders and spiraling inward.
This may not be ideal for your particular case. I found a very simple
strategy for your example was to just plant a few seeds between those
two points of zero velocity, y=0 and x from -10 to 10. Those trajectories
grow to their fullest and fill in most of the plot without clutter.
Here is how I create the seed points and set the density:
num_streams = 8
stptsy = np.zeros((num_streams,), np.float)
stptsx_left = np.linspace(0, -10.0, num_streams)
stptsx_right = np.linspace(0, 10.0, num_streams)
stpts_left = np.column_stack((stptsx_left, stptsy))
stpts_right = np.column_stack((stptsx_right, stptsy))
density = (3,6)
And here is how I modify the calls to streamplot:
strm = ax0.streamplot(X, Y, Vx, Vy, color=speed, linewidth=lw, density=density,
cmap=plt.cm.jet, start_points=stpts_right)
ax0.streamplot(-X, Y, -Vx, Vy, color=speed, linewidth=lw,density=density,
cmap=plt.cm.jet, start_points=stpts_left)
The result basically looks like the original, but with smoother color transitions and only 15 stream lines. (sorry no reputation to inline the image)
I think your best bet is to use a colormap other than jet. Perhaps cmap=plt.cmap.plasma.
Wierd looking graphs obscure understanding of the data.
For data which is ordered in some way, like by the speed vector magnitude in this case, uniform sequential colormaps will always look smoother. The brightness of sequential maps varies monotonically over the color range, removing large percieved color changes over small ranges of data. The uniform maps vary linearly over their whole range which makes the main features in the data much more visually apparent.
(source: matplotlib.org)
The jet colormap spans a very wide variety of brightnesses over its range with in inflexion in the middle. This is responsible for the particularly egregious red to blue transition around the center region of your graph.
(source: matplotlib.org)
The matplotlib user guide on choosing a color map has a few recomendations for about selecting an appropriate map for a given data set.
I dont think there is much else you can do to improve this by just changing parameters in your plot.
The streamplot divides the graph into cells with 30*density[x,y] in each direction, at most one streamline goes through each cell. The only setting which directly increases the number of segments is the density of the grid matplotlib uses. Increasing the Y density will decrease the segment length so that the middle region may transition more smoothly. The cost of this is an inevitable cluttering of the graph in regions where the streamlines are horizontal.
You could also try to normalise the speeds differently so the the change is artifically lowered in near the center. At the end of the day though it seems like it defeats the point of the graph. The graph should provide a useful view of the data for a human to understand. Using a colormap with strange inflexions or warping the data so that it looks nicer removes some understanding which could otherwise be obtained from looking at the graph.
A more detailed discussion about the issues with colormaps like jet can be found on this blog.

spline interpolation coefficients of a line curve in 3d space

I am new to python.
I have a line curve in the 3D space defined by a set of given points.
Can anyone suggest how I can use the interpolate with spline functions of the scipy package to get the spline coefficients of the curve just like the spline.coeff function in MATLAB?
Thank you!
EDIT:
I have used the
tck = interpolate.SmoothBivariateSpline(pts2[:,0], pts2[:,1], pts2[:,2])
test_pts = pts2[:,2]-tck.ev(pts2[:,0], pts2[:,1])
print test_pts
but this is for surfaces apparently and not for line curves pts2 is a Nx3 numpy array containing the coordinates of the points
ok I figured out what I was doing wrong. my input points where too few. now I have another question. The function get_coeffs is supposed to return the spline coefficients at every not. In which order those coefficients are returned? I have an array of 79 tx and 79 ty which represent the knots and I get an array of 1x5625 when I call the function to call the knots
I too am new to python, but my recent searching led me to a very helpful scipy interpolation tutorial. From my reading of this I concur that the BivariateSpline family of classes/functions are intended for interpolating 3D surfaces rather than 3D curves.
For my 3D curve fitting problem (which I believe is very similar to yours, but with the addition of wanting to smooth out noise) I ended up using scipy.interpolate.splprep (not to be confused with scipy.interpolate.splrep). From the tutorial linked above, the spline coefficients your are looking for are returned by splprep.
The normal output is a 3-tuple, (t,c,k) , containing the
knot-points, t , the coefficients c and the order k of the spline.
The docs keep referring to these procedural functions as an "older, non object-oriented wrapping of FITPACK" in contrast to the "newer, object-oriented" UnivariateSpline and BivariateSpline classes. I would have preferred "newer, object-oriented" myself, but as far as I can tell UnivariateSpline only handles the 1-D case whereas splprep handles N-D data directly.
Below is a simple test-case that I used to figure out these functions:
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
from mpl_toolkits.mplot3d import Axes3D
# 3D example
total_rad = 10
z_factor = 3
noise = 0.1
num_true_pts = 200
s_true = np.linspace(0, total_rad, num_true_pts)
x_true = np.cos(s_true)
y_true = np.sin(s_true)
z_true = s_true/z_factor
num_sample_pts = 80
s_sample = np.linspace(0, total_rad, num_sample_pts)
x_sample = np.cos(s_sample) + noise * np.random.randn(num_sample_pts)
y_sample = np.sin(s_sample) + noise * np.random.randn(num_sample_pts)
z_sample = s_sample/z_factor + noise * np.random.randn(num_sample_pts)
tck, u = interpolate.splprep([x_sample,y_sample,z_sample], s=2)
x_knots, y_knots, z_knots = interpolate.splev(tck[0], tck)
u_fine = np.linspace(0,1,num_true_pts)
x_fine, y_fine, z_fine = interpolate.splev(u_fine, tck)
fig2 = plt.figure(2)
ax3d = fig2.add_subplot(111, projection='3d')
ax3d.plot(x_true, y_true, z_true, 'b')
ax3d.plot(x_sample, y_sample, z_sample, 'r*')
ax3d.plot(x_knots, y_knots, z_knots, 'go')
ax3d.plot(x_fine, y_fine, z_fine, 'g')
fig2.show()
plt.show()

Categories

Resources