Plotting a 2D plane through a 3D surface - python

I'm trying to visualise a 2D plane cutting through a 3D graph with Numpy and Matplotlib to explain the intuition of partial derivatives.
Specifically, the function I'm using is J(θ1,θ2) = θ1^2 + θ2^2, and I want to plot a θ1-J(θ1,θ2) plane at θ2=0.
I have managed to plot a 2D plane with the below code but the superposition of the 2D plane and the 3D graph isn't quite right and the 2D plane is slightly off, as I want the plane to look like it's cutting the 3D at θ2=0.
It would be great if I can borrow your expertise on this, thanks.
def f(theta1, theta2):
return theta1**2 + theta2**2
fig, ax = plt.subplots(figsize=(6, 6),
subplot_kw={'projection': '3d'})
x,z = np.meshgrid(np.linspace(-1,1,100), np.linspace(0,2,100))
X = x.T
Z = z.T
Y = 0 * np.ones((100, 100))
ax.plot_surface(X, Y, Z)
r = np.linspace(-1,1,100)
theta1_grid, theta2_grid = np.meshgrid(r,r)
J_grid = f(theta1_grid, theta2_grid)
ax.contour3D(theta1_grid,theta2_grid,J_grid,500,cmap='binary')
ax.set_xlabel(r'$\theta_1$',fontsize='large')
ax.set_ylabel(r'$\theta_2$',fontsize='large')
ax.set_zlabel(r'$J(\theta_1,\theta_2)$',fontsize='large')
ax.set_title(r'Fig.2 $J(\theta_1,\theta_2)=(\theta_1^2+\theta_2^2)$',fontsize='x-large')
plt.tight_layout()
plt.show()
This is the image output by the code:

As #ImportanceOfBeingErnest noted in a comment, your code is fine but matplotlib has a 2d engine, so 3d plots easily show weird artifacts. In particular, objects are rendered one at a time, so two 3d objects are typically either fully in front of or fully behind one another, which makes the visualization of interlocking 3d objects near impossible using matplotlib.
My personal alternative suggestion would be mayavi (incredible flexibility and visualizations, pretty steep learning curve), however I would like to show a trick with which the problem can often be removed altogether. The idea is to turn your two independent objects into a single one using an invisible bridge between your surfaces. Possible downsides of the approach are that
you need to plot both surfaces as surfaces rather than a contour3D, and
the output relies heavily on transparency, so you need a backend that can handle that.
Disclaimer: I learned this trick from a contributor to the matplotlib topic of the now-defunct Stack Overflow Documentation project, but unfortunately I don't remember who that user was.
In order to use this trick for your use case, we essentially have to turn that contour3D call to another plot_surface one. I don't think this is overall that bad; you perhaps need to reconsider the density of your cutting plane if you see that the resulting figure has too many faces for interactive use. We also have to explicitly define a point-by-point colormap, the alpha channel of which contributes the transparent bridge between your two surfaces. Since we need to stitch the two surfaces together, at least one "in-plane" dimension of the surfaces have to match; in this case I made sure that the points along "y" are the same in the two cases.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def f(theta1, theta2):
return theta1**2 + theta2**2
fig, ax = plt.subplots(figsize=(6, 6),
subplot_kw={'projection': '3d'})
# plane data: X, Y, Z, C (first three shaped (nx,ny), last one shaped (nx,ny,4))
x,z = np.meshgrid(np.linspace(-1,1,100), np.linspace(0,2,100)) # <-- you can probably reduce these sizes
X = x.T
Z = z.T
Y = 0 * np.ones((100, 100))
# colormap for the plane: need shape (nx,ny,4) for RGBA values
C = np.full(X.shape + (4,), [0,0,0.5,1]) # dark blue plane, fully opaque
# surface data: theta1_grid, theta2_grid, J_grid, CJ (shaped (nx',ny) or (nx',ny,4))
r = np.linspace(-1,1,X.shape[1]) # <-- we are going to stitch the surface along the y dimension, sizes have to match
theta1_grid, theta2_grid = np.meshgrid(r,r)
J_grid = f(theta1_grid, theta2_grid)
# colormap for the surface; scale data to between 0 and 1 for scaling
CJ = plt.get_cmap('binary')((J_grid - J_grid.min())/J_grid.ptp())
# construct a common dataset with an invisible bridge, shape (2,ny) or (2,ny,4)
X_bridge = np.vstack([X[-1,:],theta1_grid[0,:]])
Y_bridge = np.vstack([Y[-1,:],theta2_grid[0,:]])
Z_bridge = np.vstack([Z[-1,:],J_grid[0,:]])
C_bridge = np.full(Z_bridge.shape + (4,), [1,1,1,0]) # 0 opacity == transparent; probably needs a backend that supports transparency!
# join the datasets
X_surf = np.vstack([X,X_bridge,theta1_grid])
Y_surf = np.vstack([Y,Y_bridge,theta2_grid])
Z_surf = np.vstack([Z,Z_bridge,J_grid])
C_surf = np.vstack([C,C_bridge,CJ])
# plot the joint datasets as a single surface, pass colors explicitly, set strides to 1
ax.plot_surface(X_surf, Y_surf, Z_surf, facecolors=C_surf, rstride=1, cstride=1)
ax.set_xlabel(r'$\theta_1$',fontsize='large')
ax.set_ylabel(r'$\theta_2$',fontsize='large')
ax.set_zlabel(r'$J(\theta_1,\theta_2)$',fontsize='large')
ax.set_title(r'Fig.2 $J(\theta_1,\theta_2)=(\theta_1^2+\theta_2^2)$',fontsize='x-large')
plt.tight_layout()
plt.show()
The result from two angles:
As you can see, the result is pretty decent. You can start playing around with the individual transparencies of your surfaces to see if you can make that cross-section more visible. You can also switch the opacity of the bridge to 1 to see how your surfaces are actually stitched together. All in all what we had to do was take your existing data, make sure their sizes match, and define explicit colormaps and the auxiliary bridge between the surfaces.

Related

Interpolate continuous curve from noisy data

I am trying to estimate/interpolate a curve from noisy data like the circle in the example. My data consists of more than circles but this should be a good starting point for solving the other structures as well.
I have a noisy binary image and I am trying to fit a continuous curve/skeleton to it (each pixel has 2 neighbours, except maybe start and end pixel, if the shape is not circular).
I had some success fitting the x,y coordinates separately, using the distance to a starting point as x values and the coordinates as y value and then interpolating distances in small steps. Then I checked if the coordinates were all connected. In some extreme cases the new interpolated points are not connected and I have to use smaller steps for the interpolation. This often also leads to pixels with more than 2 neighbours and other weird artifacts.
Is there an easier way to fit these values to a curve and to get a continuous curve as a result?
import numpy as np
from skimage import draw
from matplotlib import pyplot as plt
image = np.zeros((200,200), dtype=np.uint8)
coords = np.array(draw.circle_perimeter(100,100,50))
noise = np.random.normal(0,2,coords.shape).astype(np.int64)
coords += noise
image[coords[0], coords[1]] = 1
plt.imshow(image, cmap="gray")
plt.show()
To fit data, you need a model. There are any number of ways of fitting a circle. The one I've had the most success with is Ian Coope's linearized solution. The paper is available here: https://ir.canterbury.ac.nz/handle/10092/11104
I've made a python implementation of it in a linearized fitting library called scikit-guess. The function is skg.nsphere_fit. Given your (2, n) array coords, you would use it like this:
from skg import nsphere_fit
radius, center = nsphere_fit(coords, axis=0)
To plot over your image, you can use matplotlib.patches.Circle:
from matplotlib.patches import Circle
fig, ax = plt.subplots()
ax.imshow(image, cmap='gray')
ax.add_patch(Circle(center[::-1], radius, edgecolor='red', facecolor='none'))
You need to reverse center because your input coordinates are (row, col), while Circle expects (x, y), which is (col, row).
To fit a different model, you would need a different method. For arbitrary models, you might want to look into scipy.optimize and lmfit.
Fitting a circle to noisy data is very simple :
This method comes from https://fr.scribd.com/doc/14819165/Regressions-coniques-quadriques-circulaire-spherique

NumPy histogram2d with a rotated, non-orthogonal binning grid

I need to compute (and plot) a histogram2d but my binning grid is rotated and also non-orthogonal.
A way of doing this could be to apply a transformation to my data so I get it into a cartesian system, compute my histogram2d and then apply the inverse transformation.
Can this be done directly without this overhead transformation ?
I guess my question is: how do I define the bins for my histogram2d in this case ? (AFAIK, histogram2d will only accept x and y aligned bins)
My data is 2 huge lists of points (10k~100k each), the coordinates of which are given in a cartesian coordinate system (actually a projected CRS because these are real-world locations) but they are organized in a regular grid that is not aligned to X and Y axis (rotated) and that may or may not be orthogonal. The binning grid will be derived from it so it will be a (rotated) regular quadrilaterals grid.
I have seen that matplotlib has a QuadMesh object (see here) so I'm being hopeful but I'm not sure how to handle this in NumPy.
Basically this is what I want to achieve:
After some testing, I came to the conclusion that the overhead of transforming the coordinates into a Cartesian grid to compute the histogram and back for plotting is acceptable. Matrix operations in NumPy are fairly efficient and I can handle 115+ million points in less than 7 sec.
However, the "back" part can be handled by Matplotlib directly with matplotlib.transforms.
pcolormesh, hist2d and imshow all accept a transform keyword which can be used to plot the Cartesian data into the desired coordinates like so:
# set I, J, bins (in the Cartesian system) and cmap
# a, b, c, d, e, f are values of the transformation matrix
transform = matplotlib.transforms.Affine2D.from_values(a, b, c, f, d, e, f)
fig, ax = plt.subplots(figsize=figsize)
_, _, _, im = ax.hist2d(I, J, bins=bins, cmap=cmap, transform=transform + ax.transData)
fig.colorbar(im)
ax.autoscale()
It is not really much faster than handling the "back" conversion with NumPy but it can make the code lighter as it only requires 1 additional line and 1 additional keyword.
imshow can be a little bit of a pain as it won't update the display extent after using ax.autoscale() and it handles coordinates as images or matrix so the transform has to be adjusted accordingly. For these reasons, I prefer hist2d.
References:
https://matplotlib.org/3.1.1/api/transformations.html#module-matplotlib.transforms
https://matplotlib.org/3.1.1/tutorials/advanced/transforms_tutorial.html

Study of a vector field

I have an input data with 6 columns where the three first are the position x, y, z and the rest are vector components of a vector field. I could only make a 3d graph with quiver3d of the mayavi library in python (x,y,z,px,py,pz) creating 6 numpy arrays x,y,z,px,py,pz just to visualize it.
It would be great to get a 3D graph by any mean where I could insert cut planes where the vectors that are contained on it would be shown, or planes where I could see a color map that would help me to understand its beahaviour. Any help?
Here is the input:
https://pastebin.com/raw/pmGguxUc
Here is the code I used to get the visualization with quiver3d function.
import numpy as np
import mayavi.mlab as mlab
data = np.loadtxt("vectorfield.dat", dtype = float)
dataTranspuesta=data.T
x=dataTranspuesta[0]
y=dataTranspuesta[1]
z=dataTranspuesta[2]
px=dataTranspuesta[3]
py=dataTranspuesta[4]
pz=dataTranspuesta[5]
mlab.quiver3d(x, y, z, px, py, pz, line_width=0.7 ,scale_mode="vector" ,scale_factor=0.0045,mask_points=7 ,mode="arrow", colormap="seismic" )
mlab.show()
It is easier to organize everything using mayavi's pipeline. They are basically the same as using mlab plotting functions, but organize your visualization tasks in a pipeline fashion.
Pfld = mlab.pipeline.vector_field(x, y, z, px, py, pz)
Quiver = mlab.pipeline.vectors(Pfld)
Pcut = mlab.pipeline.vector_cut_plane(Quiver, plane_orientation='x_axes')
You can also draw isosurface contours for the vectors' magnitude
Pmag = mlab.pipeline.extract_vector_norm(Pfld)
Piso = mlab.pipeline.iso_surface(Pmag)
and plane cuts of the scalar field can be achieved through mlab.pipeline.scalar_cut_plane(Pmag) or mlab.pipeline.image_plane_widget(Pmag)
See documentations for more details on the allowed arguments, decorations, etc.
Also examples 1 and
exmaples 2 may fit your needs.

Fourier transform or fit of sines and cosines to a 2D surface from discrete point cloud data

I have x,y,z data that define a surface (x and y position, z height).
The data is imperfect, in that it contains some noise, i.e. not every point lies precisely on the plane I wish to model, just very close to it.
I only have data within a triangular region, not the full x,y, plane.
Here is an example with z represented by colour:
In this example the data has been sampled in the centres of triangles on a mesh like this (each blue dot is a sample):
If it is necessary, the samples could be evenly spaced on an x,y grid, though a solution where this is not required is preferable.
I want to represent this data as a sum of sines and cosines in order to manipulate it mathematically. Ideally using as few terms as are needed to keep the error of the fit acceptably low.
If this were a square region I would take the 2D Fourier transform and discard higher frequency terms.
However I think this situation has two key differences that make this approach not viable:
Ideally I want to use samples at the points indicated by the blue dots in my grid above. I could instead use a regular x,y grid if there is no alternative, but this is not an ideal solution
I do not have data for the whole x,y, plane. The white areas in the first image above do not contain data that should be considered in the fit.
So in summary my question is thus:
Is there a way to extract coefficients for a best-fit of this data using a linear combination of sines and cosines?
Ideally using python.
My apologies if this is more of a mathematics question and stack overflow is not the correct place to post this!
EDIT: Here is an example dataset in python style [x,y,z] form - sorry it's huge but apparently I can't use pastebin?:
[[1.7500000000000001e-08, 1.0103629710818452e-08, 14939.866751020554],
[1.7500000000000001e-08, 2.0207259421636904e-08, 3563.2218207404617],
[8.7500000000000006e-09, 5.0518148554092277e-09, 24529.964593228644],
[2.625e-08, 5.0518148554092261e-09, 24529.961688158553],
[1.7500000000000001e-08, 5.0518148554092261e-09, 21956.74682671843],
[2.1874999999999999e-08, 1.2629537138523066e-08, 10818.190869824304],
[1.3125000000000003e-08, 1.2629537138523066e-08, 10818.186813746233],
[1.7500000000000001e-08, 2.5259074277046132e-08, 3008.9480862705223],
[1.3125e-08, 1.7681351993932294e-08, 5630.9978116591838],
[2.1874999999999999e-08, 1.768135199393229e-08, 5630.9969846863969],
[8.7500000000000006e-09, 1.0103629710818454e-08, 13498.380006002562],
[4.3750000000000003e-09, 2.5259074277046151e-09, 40376.866196753763],
[1.3125e-08, 2.5259074277046143e-09, 26503.432370909999],
[2.625e-08, 1.0103629710818452e-08, 13498.379635232159],
[2.1874999999999999e-08, 2.5259074277046139e-09, 26503.430698738041],
[3.0625000000000005e-08, 2.525907427704613e-09, 40376.867011915041],
[8.7500000000000006e-09, 1.2629537138523066e-08, 11900.832515759088],
[6.5625e-09, 8.8406759969661469e-09, 17422.002946526718],
[1.09375e-08, 8.8406759969661469e-09, 17275.788904632376],
[4.3750000000000003e-09, 5.0518148554092285e-09, 30222.756636780832],
[2.1875000000000001e-09, 1.2629537138523088e-09, 64247.241146490327],
[6.5625e-09, 1.2629537138523084e-09, 35176.652106572205],
[1.3125e-08, 5.0518148554092277e-09, 22623.574247287044],
[1.09375e-08, 1.2629537138523082e-09, 27617.700396641056],
[1.5312500000000002e-08, 1.2629537138523078e-09, 25316.907231576402],
[2.625e-08, 1.2629537138523066e-08, 11900.834523905782],
[2.4062500000000001e-08, 8.8406759969661469e-09, 17275.796410700641],
[2.8437500000000002e-08, 8.8406759969661452e-09, 17422.004617294893],
[2.1874999999999999e-08, 5.0518148554092269e-09, 22623.570035270699],
[1.96875e-08, 1.2629537138523076e-09, 25316.9042194055],
[2.4062500000000001e-08, 1.2629537138523071e-09, 27617.700160860692],
[3.0625000000000005e-08, 5.0518148554092261e-09, 30222.765972585737],
[2.8437500000000002e-08, 1.2629537138523069e-09, 35176.65151453446],
[3.2812500000000003e-08, 1.2629537138523065e-09, 64247.246775422129],
[2.1875000000000001e-09, 2.5259074277046151e-09, 46711.23463223876],
[1.0937500000000001e-09, 6.3147685692615553e-10, 101789.89315354674],
[3.28125e-09, 6.3147685692615543e-10, 52869.788364220134],
[3.2812500000000003e-08, 2.525907427704613e-09, 46711.229428833962],
[3.1718750000000001e-08, 6.3147685692615347e-10, 52869.79233902022],
[3.3906250000000006e-08, 6.3147685692615326e-10, 101789.92509671643],
[1.0937500000000001e-09, 1.2629537138523088e-09, 82527.848790063814],
[5.4687500000000004e-10, 3.1573842846307901e-10, 137060.87432327325],
[1.640625e-09, 3.157384284630789e-10, 71884.380087542726],
[3.3906250000000006e-08, 1.2629537138523065e-09, 82527.861035177877],
[3.3359375000000005e-08, 3.1573842846307673e-10, 71884.398689011548],
[3.4453125000000001e-08, 3.1573842846307663e-10, 137060.96214950032],
[4.3750000000000003e-09, 6.3147685692615347e-09, 18611.868317256733],
[3.28125e-09, 4.4203379984830751e-09, 27005.961455364879],
[5.4687499999999998e-09, 4.4203379984830751e-09, 28655.126635802204],
[3.0625000000000005e-08, 6.314768569261533e-09, 18611.869287539808],
[2.9531250000000002e-08, 4.4203379984830734e-09, 28655.119850641502],
[3.1718750000000001e-08, 4.4203379984830726e-09, 27005.959731047784]]
Nothing stops you from doing normal linear least squares with whatever basis you like. (You'll have to work out the periodicity you want, as mikuszefski said.) The lack of samples outside the triangle will naturally blind the method to the function's behavior out there. You probably want to weight the samples according to the area of their mesh cell, to avoid overfitting the corners.
Here some code that might help to fit periodic spikes. That also shows the use of the base x, x/2+ sqrt(3)/2 * y. The flat part can then be handled by low order Fourier. I hope that gives an idea. (BTW I agree with Davis Herring that area weighting is a good idea). For the fit, I guess, good initial guesses are crucial.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
def gauss(x,s):
return np.exp(-x**2/(2.*s**2))
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.15)
Y = np.arange(-5, 5, 0.15)
X, Y = np.meshgrid(X, Y)
kX=np.sin(X)
kY=np.sin(0.5*X+0.5*np.sqrt(3.)*Y)
R = np.sqrt(kX**2 + kY**2)
Z = gauss(R,.4)
#~ surf = ax.plot_wireframe(X, Y, Z, linewidth=1)
surf= ax.plot_surface(X, Y, Z, rstride=1, cstride=1,linewidth=0, antialiased=False)
plt.show()
Output:

Vertical gradient on matplotlib polygon

I am looking to generate a linear gradient for a polycollection in matplotlib. I have the following code that gives me a single block color
v = []
for k in range(0, len(xs) - 1):
x = [xs[k], xs[k+1], xs[k+1], xs[k]]
y = [ys[k], ys[k+1], ys[k+1], ys[k]]
z = [zs[k], zs[k+1], h, h]
v.append(zip(x, y, z))
poly3dCollection = Poly3DCollection(v, facecolor='yellow', edgecolors='none')
I have played around with colormaps and interpolation but I can't quite seem to get this to work in the way I want. Specifically I need a linear gradient based on a number of greyscale values I supply (so no default jet color scheme). The gradient can be set once i.e. top x% of each polygon black then various shades of gray for same x% rather than this needing to be dynamically calculated for each polygon.
I did get the effect I wanted by saving to SVG and manually editing the image, but I need to do this all in code.
It strikes me this should be very straight forward so im not sure what I am missing

Categories