Defining a 2D object and using its area as Boolean - python

I have defined two space dimesions ( x and z ) and I was able to manually "draw" an object to use it as a boolen for solving an equation. I defined it as it follows:
A = np.zeros((nz,nx))
object = np.ones_like(A)
object[ int(5/dz):int(10/dz) , int(5/dx):int(10/dz) ] = 2
object = object == 2
By doing that I can define an square 5x10 in z dimesion and 5x10 in x dimesion , and apply the algorythim which understands this as an area , I think. But when it comes to draw complex areas it ends up being hard doing it by little squares and rectangles.
So I want to automatize an area generation by mouse clicking and I want to be able to use this area as a boolean.
I was able to draw a polygon using:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Polygon
fig, ax = plt.subplots()
object = np.array(plt.ginput(n=-100,mouse_stop=2))
p = Polygon(object, alpha=0.5)
plt.gca().add_artist(p)
plt.draw()
plt.show()
But this outputs z and x coordinates of the vertices, and I tried to use it as boleean but I could'nt write it so that python uderstands it as the area defined by those points.
Is this problem easy to solve?

If you just want to calculate the area of a general polygon, you can use for example the Shapely python package like this:
import numpy as np
import matplotlib.pyplot as plt
from shapely.ops import Polygon
from matplotlib.patches import Polygon as PltPolygon
# Get the coordinate input
canvas_size = np.array([1, 1])
canvas_lim = np.array([[0, canvas_size[0]], [0, canvas_size[1]]])
fig, ax = plt.subplots()
plt.xlim(canvas_lim[0])
plt.ylim(canvas_lim[1])
ax.set_aspect("equal")
coordinates = np.array(plt.ginput(n=-100, mouse_stop=2))
# Use shapely.ops.Polygon to calculate the area
poly = Polygon(coordinates)
area = poly.area
print("The area is {} units^2".format(area))
# Draw the polygon
p = PltPolygon(coordinates, alpha=0.5)
ax.add_artist(p)
plt.show()
If you definitely need the mask, here's one way to rasterize it using numpy and matplotlib.path. For details see the comments in the code:
import numpy as np
import matplotlib.path as mpltPath
import matplotlib.pyplot as plt
# Define the limits of our polygon
canvas_desired_size = np.array([110, 100])
# The pixel size with which we calculate (number of points to consider)
# The higher this number, the more we have to calculate, but the
# closer the approximation will be
pixel_size = 0.1
# Cacluate the actual size of the canvas
num_pxiels = np.ceil(canvas_desired_size / pixel_size).astype(int)
canvas_actual_size = num_pxiels * pixel_size
# Let's create a grid where each pixel's value is it's position in our 2d image
x_coords = np.linspace(
start=0,
stop=canvas_actual_size[0],
endpoint=False,
num=canvas_desired_size[0] / pixel_size,
)
y_coords = np.linspace(
start=0,
stop=canvas_actual_size[1],
endpoint=False,
num=canvas_desired_size[1] / pixel_size,
)
# Since it makes more sense to check if the middle of the pixel is in the
# polygion, we shift everything with half pixel size
pixel_offset = pixel_size / 2
x_centers = x_coords + pixel_offset
y_centers = y_coords + pixel_offset
xx, yy = np.meshgrid(x_centers, y_centers, indexing="ij")
# Flatten our xx and yy matrixes to an N * 2 array, which contains
# every point in our grid
pixel_centers = np.array(
list(zip(xx.flatten(), yy.flatten())), dtype=np.dtype("float64")
)
# Now prompt for the imput shape
canvas_lim = np.array([[0, canvas_actual_size[0]], [0, canvas_actual_size[1]]])
fig, ax = plt.subplots()
plt.xlim(canvas_lim[0])
plt.ylim(canvas_lim[1])
ax.set_aspect("equal")
shape_points = np.array(plt.ginput(n=-100, mouse_stop=2))
# Create a Path object
shape = mpltPath.Path(shape_points)
# Use Path.contains_points to calculate if each point is
# within our shape
shape_contains = shape.contains_points(pixel_centers)
# Reshape the result to be a matrix again
mask = np.reshape(shape_contains, num_pxiels)
# Calculate area
print(
"The shape area is roughly {} units^2".format(
np.sum(shape_contains) * pixel_size ** 2
)
)
# Show the rasterized shape to confirm it looks correct
plt.imshow(np.transpose(mask), aspect="equal", origin="lower")
plt.xlim([0, num_pxiels[0]])
plt.ylim([0, num_pxiels[1]])
plt.show()
Alternatively, a simpler solution would be using your plot as an image and thresholding it to get a boolean mask. There should be plent of examples of how to do this on google.

Related

How to plot lines between points, and change their color based on specific values in Python?

Context:
3x35 values array that associates 1 value per segment
4x35x2 matpos array that gathers the coordinates of 4x35 points (hence 3x35 segments).
Question:
How can I define each segment's color based on their values from the values array ?
Code attempt:
# Array of values for each point
values = np.random.rand(3,35)
# Generate array of positions
x = np.arange(0,35)
y = np.arange(0,4)
matpos = np.array([[(y[i], x[j]) for j in range(0,len(x))] for i in range(0,len(y))])
# plot the figure
plt.figure()
for i in range(len(y)-1):
for j in range(len(x)):
# plot each segment
plt.plot(matpos[i:i+2,j,0],matpos[i:i+2,j,1]) #color = values[i,j]
If your values are just along a grid, you might as well just use plt.imshow(values).
Updated code for desired result:
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# Array of values for each point
values = np.random.rand(3,35)
# Transform value to colors depending on colormap
color_norm = mpl.colors.Normalize(np.min(values), np.max(values))
color_map = mpl.cm.get_cmap('viridis')
colors = color_map(color_norm(values))
plt.close('all')
plt.figure()
for (y, x), value in np.ndenumerate(values):
plt.plot([x, x+1], [y, y], c = colors[y,x], linewidth = 10)

matplotlib custom path markers not displaying correctly

just wondering if anybody has experience with matplotlib custom markers
I want each marker in my plot to be a pie chart. To achieve this, my strategy was to create custom markers using the path class, method wedge.
https://matplotlib.org/stable/api/path_api.html
However is not displaying correctly, in particular with wedges defined with angles in the left quadrants. However, the path defined by the wedge class method seems to be correct and wedges are displayed correctly if using PathPatch and .add_patch()
See example below
import numpy as np
import math
import matplotlib.path as mpath
import matplotlib.cm
import matplotlib.pyplot as plt
import matplotlib.patches as patches
#Create wedges from angles
angles = np.array( [0,140,160,360] ) #Wedges angles
wedges=[]
for i in range(len(angles)-1):
angle0= angles[i]
angle1= angles[i+1]
dangle = angle1-angle0
wedge0=None
if dangle>0:
wedge0= mpath.Path.wedge(angle0, angle1)
wedge0= mpath.Path.wedge(angle0, angle1)
wedges.append(wedge0)
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax1.set_xlim(-1, 1)
ax1.set_ylim(-1, 1)
ax2 = fig.add_subplot(122)
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
tab10 = matplotlib.cm.get_cmap('tab10')
for i, w0 in enumerate(wedges):
ax1.scatter(0,0, marker=w0, c = [tab10(i)], s=20000) #Use path markers
patch = patches.PathPatch(w0, color=tab10(i)) #Use patch
ax2.add_patch(patch)
plt.show()
Notice that the wedge on the left plot is sticking out, which is not supposed to.
Is this a bug in the matplotlib markers' code?
I managed to get the pie charts to display correctly.
Scaling by doing affine transforms does not help because the path markaers are all resized, as in
line 495 of markers.py .
def _set_custom_marker(self, path):
rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
self._transform = Affine2D().scale(0.5 / rescale)
self._path = path
My solution is to modify the vertices in the created wedges by inserting new vertices that define a bounding box, slightly larger than the circle with radius 1.
Here is the modified code
import numpy as np
import matplotlib.path as mpath
import matplotlib.cm
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def getBoundedWedge(angle0, angle1):
wedge0= mpath.Path.wedge(angle0, angle1)
#print(f"wedge0:{wedge0}")
vertices = wedge0.vertices
codes = wedge0.codes
#Add ghost vertices to define bounding box
vertices= np.insert( vertices, 0, [[1.1,1.1], [-1.1,1.1] , [-1.1,-1.1], [1.1,-1.1]] , axis=0)
codes = np.insert( codes, 0, [1,1,1,1])
wedgeextra = mpath.Path(vertices, codes)
return wedgeextra
#Create wedges from angles
angles = np.array( [0,140,160,360] ) #Wedges angles
wedges=[]
for i in range(len(angles)-1):
angle0= angles[i]
angle1= angles[i+1]
dangle = angle1-angle0
wedge0=None
if dangle>0:
wedge0= getBoundedWedge(angle0, angle1)
wedges.append(wedge0)
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax1.set_xlim(-1, 1)
ax1.set_ylim(-1, 1)
ax2 = fig.add_subplot(122)
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
tab10 = matplotlib.cm.get_cmap('tab10')
for i, w0 in enumerate(wedges):
ax1.scatter(0,0, marker=w0, c = [tab10(i)], s=20000) #Use path markers
patch = patches.PathPatch(w0, color=tab10(i)) #Use patch
ax2.add_patch(patch)
plt.show()
And the output is as follows

Creating a colourmap plot in pyplot with random data and positions

I want to plot a map (let's call it testmap) of shape (100,3) with a colourmap. Each row consists of the x-position, y-position and data, all randomly drawn.
map_pos_x = np.random.randint(100, size=100)
map_pos_y = np.random.randint(100, size=100)
map_pos = np.stack((map_pos_x, map_pos_y), axis=-1)
draw = np.random.random(100)
draw = np.reshape(draw, (100,1))
testmap = np.hstack((map_pos, draw))
I do not want to use a scatterplot, since the map positions are supposed to emulate pixels of a camera. If I try something like
plt.matshow(A=testmap)
I get a 100*2 map. However, I want a 100*100 map. Positions with no data can be black. How can I do this?
edit: I have now adopted the following:
grid = np.zeros((100, 100))
i=0
for pixel in map_pos:
grid[pixel[0], pixel[1]] = draw[i]
i=i+1
This produces what I want to have. The reason why I do not draw the random numbers in the loop itself, but iterate over the existing array "draw", is that the numbers that are being drawn are first being operated on, so I want to have the freedom to manipulate "draw" independently of the loop.
This code also produces double entries/non-unique pairs, which is fine by itself, but I would like to identify these double pairs and add up "draw" for these pairs. How can I do that?
You can first create empty pixels, either with zeros (gets the "lowest" color) or NaNs (these pixels would be invisible). Then you can use numpy's smart indexing to fill in the values. For this to work, it is important that the map_pos_x and map_pos_y are integer coordinates in the correct range.
import numpy as np
import matplotlib.pyplot as plt
map_pos_x = np.random.randint(100, size=100)
map_pos_y = np.random.randint(100, size=100)
draw = np.random.random(100)
# testmap = np.full((100,100), np.nan)
testmap = np.zeros((100,100))
testmap[map_pos_x, map_pos_y] = draw
plt.matshow(testmap)
plt.show()
PS: About your new question, to count how many xy positions coincide, np.histogram2d could be used. The result can also be plotting via matshow. A benefit is that the xy values don't need to be integers: they will be summed depending on their rounded values.
If every xy position also has a value, such as the array draw in the question, it can be passed as np.histogram2d(...., weights=draw).
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1234)
N = 100
map_pos_x = np.random.randint(N, size=10000)
map_pos_y = np.random.randint(N, size=len(map_pos_x))
fig, (ax1, ax2) = plt.subplots(ncols=2)
testmap1, xedges, yedges = np.histogram2d(map_pos_x, map_pos_y, bins=N, range=[[0, N - 1], [0, N - 1]])
ax1.matshow(testmap1)
plt.show()
To show what happens, here is a test with N=10 with the matshow at the left. At right there is a scatter plot with semitransparent dots, making them darker when there are more dots coinciding.
a solution is this:
import numpy as np
import matplotlib.pyplot as plt
import random
import itertools
#gets as input the size of the axis and the number of pairs
def get_random_pairs(axis_range, count):
numbers = [i for i in range(0,axis_range)]
pairs = list(itertools.product(numbers,repeat=2))
return random.choices(pairs, k=count)
object_positions = get_random_pairs(100,100)
grid = np.zeros((100, 100))
for pixel in object_positions:
grid[pixel[0],pixel[1]] = np.random.random()
print(pixel)
plt.matshow(A=grid)
result:
edit:
since the grid is initialized to zero then just add the new value to the old one
n_pixels_x = 100
n_pixels_y = 100
map_pos_x = np.random.randint(100, size=100)
map_pos_y = np.random.randint(100, size=100)
map_pos = np.stack((map_pos_x, map_pos_y), axis=-1)
draw = np.random.random(100)
draw = np.reshape(draw, (100,1))
testmap = np.hstack((map_pos, draw))
grid = np.zeros((n_pixels_x, n_pixels_y))
for pixel in map_pos:
grid[pixel[0], pixel[1]] = grid[pixel[0], pixel[1]] + draw[i]
plt.matshow(A=grid)

Python irregular x,y data to contour plot on original domain

I have file containing points under the columns "x-cord", "y-cord", "value". These are irregularly spaced. I am trying to make a contour plot of "value" and overlay this over the original domain. I gave up trying to do this in both pgfplots and matlab and thought I would give python a go. An answer in any of these scripts would be fine. The python script is as follows
import numpy as np
from scipy.interpolate import griddata
import matplotlib.pyplot as plt
import numpy.ma as ma
from numpy.random import uniform, seed
from scipy.spatial import ConvexHull
#
# Loading data
filename = "strain.dat"
coordinates = []
x_c = []
y_c = []
z_c = []
xyz = open(filename)
title = xyz.readline()
for line in xyz:
x,y,z = line.split()
coordinates.append([float(x), float(y), float(z)])
x_c.append([float(x)])
y_c.append([float(y)])
z_c.append([float(z)])
xyz.close()
#
# Rehaping and translating data
x_c=np.ravel(np.array(x_c))
y_c=np.ravel(np.array(y_c))
z_c=np.ravel(np.array(z_c))
x_c = x_c-100.0
y_c = y_c-100.0
#
# Checking the convex hull
points=np.column_stack((x_c,y_c))
hull = ConvexHull(points);
plt.plot(points[hull.vertices,0], points[hull.vertices,1], 'r--', lw=2)
plt.scatter(x_c, y_c, marker='o', s=5, zorder=10)
#
# Mapping the irregular data onto a regular grid and plotting
xic = np.linspace(min(x_c), max(x_c), 1000)
yic = np.linspace(min(y_c), max(y_c), 1000)
zic = griddata((x_c, y_c), z_c, (xic[None,:], yic[:,None]))
CS = plt.contour(xic,yic,zic,15,linewidths=0.5,colors='k')
CS = plt.contourf(xic,yic,zic,15,cmap=plt.cm.summer)
plt.colorbar() # draw colorbar
#
#plt.scatter(x_c, y_c, marker='o', s=5, zorder=10)
plt.axis('equal')
plt.savefig('foo.pdf', bbox_inches='tight')
plt.show()
and the output looks like
The problem is that griddata uses a convex hull and this convex hull exceeds the edges of the irregular data. Is there any way to set the values of the griddata points which are outside the edges of the boundary of the original points to zero?
Edit
In the end I threw in the towel and reverted back to Matlab. I'll have to export the data to pgfplots to get a nice plot. The code I came up with was
x = strain.x;
y = strain.y;
z = strain.eps;
% Get the alpha shape (couldn't do this in python easily)
shp = alphaShape(x,y,.001);
% Get the boundary nodes
[bi, xy] = boundaryFacets(shp);
no_grid = 500;
xb=xy(:,1);
yb=xy(:,2);
[X,Y] = ndgrid(linspace(min(x),max(x),no_grid),linspace(min(y),max(y),no_grid));
Z = griddata(x,y,z,X,Y,'v4');
% Got through the regular grid and set the values which are outside the boundary of the original domain to Nans
for j = 1:no_grid
[in,on] = inpolygon(X(:,j),Y(:,j),xb,yb);
Z(~in,j) = NaN;
end
contourf(X,Y,Z,10),axis equal
colorbar
hold on
plot(xb,yb)
axis equal
hold off
Here is the resulting image.
If someone can do something similar in Python I'll happily accept the answer.
I had to plot interpolated data on a complex geometry (see the blue points on figure) P(x,z) (z is the horizontal coordinate). I used mask operations and it worked well. Without mask, the whole square (x=0..1 ; z=0..17.28) is covered by contourf.
## limiting values for geometry
xmax1=0.408
zmin1=6.
xmax2=0.064
zmin2=13.12
xmin=0.
xmax=1.
zmin=0.
zmax=17.28
# Grid for points
x1 = np.arange(xmin,xmax+dx,dx)
z1 = np.arange(zmin,zmax+dz,dz)
zi2,xi2 = np.meshgrid(z1,x1)
mask = (((zi2 > zmin2) & (xi2 > xmax2)) | ((zi2 > zmin1) & (zi2 <= zmin2) & (xi2 > xmax1)))
zim=np.ma.masked_array(zi2,mask)
xim=np.ma.masked_array(xi2,mask)
# Grid for P values
# npz=z coordinates of data, npx is the x coordinates and npp is P values
grid_p = scipy.interpolate.griddata((npz, npx), npp, (zim,xim),method='nearest')
pm=np.ma.masked_array(grid_p,mask)
# plot
plt.contour(zim, xim, pm, 25, linewidths=0.5, colors='k',corner_mask=False)
plt.contourf(zim, xim, pm, 25,vmax=grid_p.max(), vmin=grid_p.min(),corner_mask=False)
plt.colorbar()
# Scatter plot to check
plt.scatter(npz,npr, marker='x', s=2)
plt.show()
enter image description here

Some questions on dendrogram - python (Scipy)

I am new to scipy but I managed to get the expected dendrogram. I am some more questions;
In the dendrogram, distance between some points are 0 but its not
visible due to image border. How can I remove the border and make
the lower limit of y-axis to -1, so that it is clearly visible.
e.g. distance between these points are 0 (13,17), (2,10), (4,8,19)
How can I prune/truncate on a particular distance. for e.g. prune at 0.4
How to write these clusters(after pruning) to a file
My python code:
import scipy
import pylab
import scipy.cluster.hierarchy as sch
import numpy as np
D = np.genfromtxt('LtoR.txt', dtype=None)
def llf(id):
return str(id)
fig = pylab.figure(figsize=(10,10))
Y = sch.linkage(D, method='single')
Z1 = sch.dendrogram(Y,leaf_label_func=llf,leaf_rotation=90)
fig.show()
fig.savefig('dendrogram.png')
Dendrogram:
thank you.
1.fig.gca().set_ylim(-0.4,1.2) Here gca() returns the current axes object, so you can give it a name
ax=fig.gca()
ax.set_ylim(-0.4,ax.get_ylim()[1])
You can prune the dendrogram and obtain your clusters using fcluster.
To prune at a distance of 0.4:
clusters = sch.fcluster(Y,t = 0.4,criterion = 'distance')
The resulting array (clusters) contains the cluster label for every observation in your data. You can write the array using numpy.savetxt:
np.savetxt('clusters.txt', clusters, delimiter=',')
The border is shown because of the axis. So you can remove the border using the following command:
fig = plt.figure(figsize=(10, 8))
ax2 = fig.add_axes([0.3, 0.71, 0.6, 0.2])
Y = sch.linkage(D, method='ward')
Z2 = sch.dendrogram(Y)
ax2.set_xticks([])
ax2.set_yticks([])
ax2.axis('off')
ax.axis('off') hides the border.

Categories

Resources