Python Mayavi - set size of scatter point - python

I've a very simply script with which I'm trying to plot 2 points with a set size:
from mayavi.mlab import *
x = [0.,3.]
y = [0.,0.]
z = [0.,0.]
scalars = [1.5,1.5]
pts = points3d(x, y, z, scalars, scale_factor = 1)
However, I can't figure out how, with this simple example, to set the size of the two points so that the points just touch each other. I want to set the size in the same units as the coordinates of the points. So I separate the two points by 3 units and set the size of the two points to 1.5.
However, in the image attached, the two points don't touch like expected.
Any idea why?

In mayavi, the scale of spheres determines their diameter and not their radius.
Use
pts = points3d(x, y, z, scalars, scale_factor=2, resolution=100)
the resolution argument makes a smoother sphere (number of angular points). Beware of high values of resolutions if you intend to display many spheres.

Related

Calculate the area enclosed by a 2D array of unordered points in python

I am trying to calculate the area of a shape enclosed by a large set of unordered points in python. I have a 2D array of points which I can plot as a scatterplot like this.
There are several ways to calculate the area enclosed by points, but these all assume ordered points, such as here and here. This method calculates the area unordered points, but it doesn't appear to work for complex shapes, as seen here. How would I calculate this area from unordered points in python?
Sample data looks like this:
[[225.93459 -27.25677 ]
[226.98128 -32.001945]
[223.3623 -34.119724]
[225.84741 -34.416553]]
From pen and paper one can see that this shape contains an area of ~12 (unitless) but putting these coordinates into one of the algorithms linked to previously returns an area of ~0.78.
Let's first mention that in the question How would I calculate this area from unordered points in python? used phrase 'unordered points' in the context of calculation of an area usually means that given are points of a contour enclosing an area which area is to calculate.
But in the question provided data sample are not points of a contour but just a cloud of points, which if visualized using a scatterplot results in a visually perceivable area.
The above is the reason why in the question provided links to algorithms calculating areas from 'unordered points' don't apply at all to what the question is about.
In other words, the actual title of the question I will answer below will be:
Calculate the visually perceivable area a cloud of (x,y) points is forming when visualized as a scatterplot
One of the possible options is mentioned in a comment to the question:
Honestly, you might consider taking THAT graph as a bitmap, and counting the number of non-white pixels in it. That is probably as close as you can get. – Tim Roberts
Given the image perfectly covering (without any margin) all the non-white pixels you can calculate the area the image rectangle is covering in units used in the underlying (x,y) data by calculating the area TA of the rectangle visible in the image from the underlying list of points P with (x,y) point coordinates ( P = [(x1,y1), (x2,y2), ...] ) as follows:
X = [x for x,y in P]
Y = [y for x,y in P]
TA = (max(X)-min(X))*(max(Y)-min(Y))
Assuming N_white is the number of all white pixels in the image with N pixels the actual area A covered by non-white pixels expressed in units used in the list of points P will be:
A = TA*(N-N_white)/N
Another approach using a list of points P with (x,y) point coordinates only ( without creation of an image ) consists of following steps:
decide which area Ap a point is covering and calculate half of the size h2 of a rectangle with this area around that point ( h2 = 0.5*sqrt(Ap) )
create a list R with rectangles around all points in the list P: R = [(x-h2, y+h2, x+h2, y-h2) for x,y in P]
use the code provided through a link listed in the stackoverflow question
Area of Union Of Rectangles using Segment Trees to calculate the total area covered by the rectangles in the list R.
The above approach has the advantage over the graphical one obtained from the scatterplot that with the choice of the area covered by a point you directly influence the used precision/resolution/granularity for the area calculation.
Given a 2D array of points the area covered by the points can be calculated with help of the return value of the same hist2d() function provided in the matplotlib module (as matplotlib.pyplot.hist2d()) which is used to show the scatterplot.
The 'trick' is to set the cmin parameter value of the function to 1 ( cmin=1 ) and then calculate the number of numpy.nan values in the by the function returned array setting them in relation to entire amount of array values.
In other words all what is necessary to calculate the area when creating the scatterplot is already there for easy use in a simple area calculation formulas if you know that the histogram creating function provide as return value all what is therefore necessary.
Below code of a ready to use function for the area calculation along with demonstration of function usage:
def area_of_points(points, grid_size = [1000, 1000]):
"""
Returns the area covered by N 2D-points provided in a 'points' array
points = [ (x1,y1), (x2,y2), ... , (xN, yN) ]
'grid_size' gives the number of grid cells in x and y direction the
'points' bounding box is divided into for calculation of the area.
Larger 'grid_size' values mean smaller grid cells, higher precision
of the area calculation and longer runtime.
area_of_points() requires installed matplotlib module. """
import matplotlib.pyplot as plt
import numpy as np
pts_x = [x for x,y in points]
pts_y = [y for x,y in points]
pts_bb_area = (max(pts_x)-min(pts_x))*(max(pts_y)-min(pts_y))
h2D,_,_,_ = plt.hist2d( pts_x, pts_y, bins = grid_size, cmin=1)
numberOfWhiteBins = np.count_nonzero(np.isnan(h2D))
numberOfAll2Dbins = h2D.shape[0]*h2D.shape[1]
areaFactor = 1.0 - numberOfWhiteBins/numberOfAll2Dbins
pts_pts_area = areaFactor * pts_bb_area
print(f'Areas: b-box = {pts_bb_area:8.4f}, points = {pts_pts_area:8.4f}')
plt.show()
return pts_pts_area
#:def area_of_points(points, grid_size = [1000, 1000])
import numpy as np
np.random.seed(12345)
x = np.random.normal(size=100000)
y = x + np.random.normal(size=100000)
pts = [[xi,yi] for xi,yi in zip(x,y)]
print(area_of_points(pts))
# ^-- prints: Areas: b-box = 114.5797, points = 7.8001
# ^-- prints: 7.800126875291629
The above code creates following scatterplot:
Notice that the printed output Areas: b-box = 114.5797, points = 7.8001 and the by the function returned area value 7.800126875291629 give the area in units in which the x,y coordinates in the array of points are specified.
Instead of usage of a function when utilizing the know how you can play around with the parameter of the scatterplot calculating the area of what can be seen in the scatterplot.
Below code which changes the displayed scatterplot using the same underlying point data:
import numpy as np
np.random.seed(12345)
x = np.random.normal(size=100000)
y = x + np.random.normal(size=100000)
pts = [[xi,yi] for xi,yi in zip(x,y)]
pts_values_example = \
[[0.53005, 2.79209],
[0.73751, 0.18978],
... ,
[-0.6633, -2.0404],
[1.51470, 0.86644]]
# ---
pts_x = [x for x,y in pts]
pts_y = [y for x,y in pts]
pts_bb_area = (max(pts_x)-min(pts_x))*(max(pts_y)-min(pts_y))
# ---
import matplotlib.pyplot as plt
bins = [320, 300] # resolution of the grid (for the scatter plot)
# ^-- resolution of precision for the calculation of area
pltRetVal = plt.hist2d( pts_x, pts_y, bins = bins, cmin=1, cmax=15 )
plt.colorbar() # display the colorbar (for a 2d density histogram)
plt.show()
# ---
h2D, xedges1D, yedges1D, h2DhistogramObject = pltRetVal
numberOfWhiteBins = np.count_nonzero(np.isnan(h2D))
numberOfAll2Dbins = (len(xedges1D)-1)*(len(yedges1D)-1)
areaFactor = 1.0 - numberOfWhiteBins/numberOfAll2Dbins
area = areaFactor * pts_bb_area
print(f'Areas: b-box = {pts_bb_area:8.4f}, points = {area:8.4f}')
# prints "Areas: b-box = 114.5797, points = 20.7174"
creating following scatterplot:
Notice that the calculated area is now larger due to smaller values used for grid resolution resulting in more of the area colored.

space varying vertical coordinates of model data-- writing netCDF file (xarray)

Consider using a numerical flow model to simulate a simple 1D advection-diffusion case, in hydraulic engineering, e.g.: evolution of salt concentration (Cs). The domain has no y-dimension, but only X and Z dimension, meaning that the flow is not depth-averaged and, for 1 timestep, I have the salt concentration:
Cs = Cs(x, z)
where
x: space coordinate (equally spaced vector), and z: vertical coordinate (non equally spaced vector).
or more in general, including time, :
Cs = Cs(x, z, t)
Now, x is constant over time and space (meaning that the x-grid is not "moving" slightly back and forth), but z (i.e. the vertical coordinate of each "Layer") is indeed adapting and oscillating up and down.
For each time-step, the model spits out the actual numerical value of the salt concentration, AND the vertical coordinate for each layer (i.e. at each given depth of the fluid). IN Python I could easily "merge" contour the salinity over x and z, by staggering each z on top of its previous level.
Therefore, cords matrix in Python has a number of lines that equals the number of vertical layers and a number of rows that equals the number of x grid points. By so doing, coordinates and Salinity matrices are consistent and may be plotted (contours).
My question now is:
In Python, I want to use x-array to generate/output a netcdf file to read that in with Paraview. I could get started and created a netCDF file, containing (for now) only 1 time-step, ingest that into Paraview and plot it.
However, my problem is that z = z(x), which means that each level height varies along the horzinotal coordinate. So far, I could only apply an equally spaced vertical vector to describe vertical coordinate, and that vector is not depending on x, so it is not varying along the horizontal coordinate.
How can I achieve my goal?
nx = np.shape(Xp)[1]
nz = Nvert+1
#X vector is linearly spaced, constant dx, and it is OK like that!
xmin = 0
xmax = 10
X = np.linspace(xmin, xmax, nx)
#Z is equally spaced too, by it should NOT be! might vary over vertical AND/or over X!!!
zmin = 0
zmax = 0.5
Z = np.linspace(zmin, zmax, nz)
# merge quantity "qname", at timestep 0, given Nvert vertical layers
q0 = merge_quant(Nvert, t[0], qname)
#create two matrix coordinates, xk and zk, being consistent is size with q0
xk, zk = merge_xnadz(Nvert, t[0])
#assign:
vals = q0
#preallocate data using xarray
ds = xr.Dataset(
{qname: (("z", "x"), vals)},
coords={
"x": X,
"z": Z #---this here should vary but is instead constant..?!?
}
)
#save to disk
ds.to_netcdf("Testout.nc")
How can I add another dimension? Could I just add the matrix of coordinates in xarray before saving to netCDF? That would really solve my problem.
Any help is appreciated.
Thank you!
Marco
Edit:
Two images, one for model results; and one obtained in PARAVIEW

Masking a 3D numpy array with a tilted disc

I'm working in Python2.7 with 3D numpy arrays, and trying to retrieve only pixels who fall on a 2D tilted disc.
Here is my code to plot the border of the disc (= a circle) I am interested in
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#creating a 3d numpy array (empty in this example, but will represent a binary 3D image in my application)
space=np.zeros((40,40,20))
r = 8 #radius of the circle
theta = np.pi / 4 # "tilt" of the circle
phirange = np.linspace(0, 2 * np.pi) #to make a full circle
#center of the circle
center=[20,20,10]
#computing the values of the circle in spherical coordinates and converting them
#back to cartesian
for phi in phirange:
x = r * np.cos(theta) * np.cos(phi) + center[0]
y= r*np.sin(phi) + center[1]
z= r*np.sin(theta)* np.cos(phi) + center[2]
space[int(round(x)),int(round(y)),int(round(z))]=1
x,y,z = space.nonzero()
#plotting
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.show()
The plot gives the following figure :
which is a good start, but now I want a way to retrieve only the values of the pixels of space which are located in the disc defined by the circle : the ones in the pink zone in the following image (in my application, space will be a 3D binary image, here it is numpy.zeros() just to be able to plot and show you the disc I want):
How should I procede ?
I guess there is some numpy masking involved, an I understand how you would do it in 2D (like this question) but I'm having trouble applying this to 3D.
One easy way would be to calculate the normal vector to your disc plane. You can use your spherical coordinates for that. Be sure not to add the centre, set phi at zero and swap cos and sin theta, also stick a minus sign to the sin.
lets call that vector v. The plane is given by v0*x0 + v1*x1 + v2*x2 == c you can calculate c by inserting a point from your circle for x.
Next you can make a 2d grid for x0 and x1 and solve for x2. this gives you the height x2 as a function of the x0, x1 mesh. for these points you can calculate the distance from your disc centre and discard the points that are too far off. This you would indeed do using a mask.
Finally, depending on how precisely you want to plot you could round the x2 values to grid units, but for example for a surface plot I wouldn't do that.
To get a 3d mask as you describe you would round x2 and then starting from an all zero space set the disc pixels using space[x0, x1, x2] = True. This assumes that you have masked x0, x1, x2 as described earlier.
Well that is a math problem, you should ask it in the Mathematics Stack Exchange site.
From my perspective, you should first find the surface your disc is in, and do the area calculation within that surface, by, for example, the method you mentioned in the linked question.
numpy or matplotlib here definitely do not responsible for the projection, you do.
Without clearly point out which (or which kind of) surface they are in, and the equation does not guarantee it is a plane, the area does not mean anything.

Coordinating basemap quiver and matplotlib arrow

I am trying to compare vectors of wind in matplotlib between gridded model output locations (via quiver on a basemap map) and scattered stations (via matplotlib arrow). The locations for both are in lat/lon, but wind vectors are in m/s.
When combined, I want the colors and lengths to vary by magnitude and for both qualities to be scaled the same way for the quiver and arrow data. I have given an example below where the quiver plot looks OK and is scaled in absolute length (inches). I don't know what to do to for arrow() to match. In the example I've divided it by SCALE to give a sense of what I'd like the final image to look like.
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.basemap import Basemap
X, Y = np.meshgrid(np.arange(-123,-121,0.3),np.arange(37,39,0.3))
U = np.cos(X+123)*12
V = np.sin(Y-37)*12
mag = np.hypot(U,V)
fig,ax=plt.subplots(1)
m=Basemap(projection ='cyl',resolution='f',llcrnrlat=37,llcrnrlon=-123,
urcrnrlat=39,urcrnrlon=-121,ax=ax)
quiv = m.quiver(X,Y,U,V,mag,zorder=2,latlon=True,scale=30,scale_units='inches')
# Scattered points won't be on the grid
x0=X[2,2] - 0.025
y0=Y[2,2]
u0=U[2,2]
v0=V[2,2] + 0.5
SCALE = 72.
plt.arrow(x0,y0,u0/SCALE,v0/SCALE)
plt.show()
It's not terribly clear from the matplotlib documentation (in my opinion), but quiver does accept 1D arrays for all of X, Y, U and V, which do not need to be uniformly spaced. The basemap documentation gets that wrong, or is at least even more unclear. So as long as you form your scattered stations data into 1D arrays, you should be fine.
I added some random arrows to your plot by replacing your scattered points section with this (if you use the same seed you should get the same arrows):
# Make scattered locations
np.random.seed(33)
x0 = np.random.rand(5)*2.0 - 123
y0 = np.random.rand(5)*2.0 + 37
# Make some velocities
u0 = np.random.randn(5)*3 + 10
v0 = np.random.randn(5)*3 + 10
q2 = m.quiver(x0, y0, u0, v0, latlon=True, scale=30, scale_units='inches')
And this is the plot that I get (I use the YlGnBu_r colormap by default).
Be aware that if you start to use anything other than a cylindrical-type projection (and if your U and V are expressed in east-west and north-south) you will need to rotate the vectors to match the projection using the rotate_vector method.
You need to convert map coordinates into Cartesian coordinates by x,y = m(lon, lat), after this, plt.quiver(x,y, u, v) or m.quiver(x,y,u,v) will do the same job.

Plotting a 3D Cylindrical Surface plot in Python

I am trying to create a cylindrical 3D surface plot using Python, where my independent variables are z and theta, and the dependent variable is radius (i.e., radius is a function of vertical position and azimuth angle).
So far, I have only been able to find ways to create a 3D surface plot that:
has z as a function of r and theta
has r as a function of z, but does not change with theta (so, the end product looks like a revolved contour; for example, the case of r = sin(z) + 1 ).
I would like to have r as a function of z and theta, because my function will produce a shape that, at any given height, will be a complex function of theta.
On top of that, I need the surface plot be able to have (but does not have to have, depending on the properties of the function) an open top or bottom. For example, if r is constant from z = 0 to z = 1 (a perfect cylinder), I would want a surface plot that would only consist of the side of the cylinder, not the top or bottom. The plot should look like a hollow shell.
I already have the function r defined.
Thanks for any help!
Apparently, after some trial and error, the best/easiest thing to do in this case is to just to convert the r, theta, and z data points (defined as 2D arrays, just like for an x,y,z plot) into cartesian coordinates:
# convert to rectangular
x = r*numpy.cos(theta)
y = r*numpy.sin(theta)
z = z
The new x,y,z arrays can be plotted just like any other x,y,z arrays generated from a polynomial where z is a function of x,y. I had originally thought that the data points would get screwed up because of overlapping z values or maybe the adjacent data points would not be connected correctly, but apparently that is not the case.

Categories