How can I count number of points between 2 contours in Python - python

I am using matplotlib.pyplot to interpolate my data and create contours.
Following this answer/example (about how to calculate area within a contour), I am able to get the vertices of a contour line.
Is there a way to use that information, i.e., the vertices of a line, to count how many points fall between two given contours? These points will be different from the data used for deriving the contours.

Usually, you do not want to reverse engineer your plot to obtain some data. Instead you can interpolate the array that is later used for plotting the contours and find out which of the points lie in regions of certain values.
The following would find all points between the levels of -0.8 and -0.4, print them and show them in red on the plot.
import numpy as np; np.random.seed(1)
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
from scipy.interpolate import Rbf
X, Y = np.meshgrid(np.arange(-3.0, 3.0, 0.1), np.arange(-2.4, 1.0, 0.1))
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = 10.0 * (Z2 - Z1)
points = np.random.randn(15,2)/1.2
levels = [-1.2, -0.8,-0.4,-0.2]
# interpolate points
f = Rbf(X.flatten(), Y.flatten(), Z.flatten())
zi = f(points[:,0], points[:,1])
# add interpolated points to array with columns x,y,z
points3d = np.zeros((points.shape[0],3))
points3d[:,:2] = points
points3d[:,2] = zi
# masking condition for points between levels
filt = (zi>levels[1]) & (zi <levels[2])
# print points between the second and third level
print(points3d[filt,:])
### plotting
fig, ax = plt.subplots()
CS = ax.contour(X, Y, Z, levels=levels)
ax.clabel(CS, inline=1, fontsize=10)
#plot points between the second and third level in red:
ax.scatter(points[:,0], points[:,1], c=filt.astype(float), cmap="bwr" )
plt.show()

I am not sure if I understand what points do you want to check, but, if you have the line vertices (two points) and want to check if the third point falls in between the two, you can take a simple (not efficient) approach and calculate the area of the triangle formed by the three. If the area is 0 then the point falls on the same line. Also, you can calculate the distance between the points and see if the point is in between on the line or outside (on the extended) line.
Hope this helps!

Ampere's law of magnetic fields can help here - although it can be computationally expensive. This law says that the path integral of a magnetic field along a closed loop is proportional to the current inside the loop.
Suppose you have a contour C and a point P (x0,y0). Imagine an infinite wire located at P perpendicular to the page (current going into the page) carrying some current. Using Ampere's law we can prove that the magnetic field produced by the wire at a point P (x,y) is inversely proportional to the distance from (x0,y0) to (x,y) and tangential to the circle centered at point P passing through point (x,y). Therefore if the wire is located outside the contour the path integral is zero.
import numpy as np
import pylab as plt
# generating a mesh and values on it
delta = 0.1
x = np.arange(-3.1*2, 3.1*2, delta)
y = np.arange(-3.1*2, 3.1*2, delta)
X, Y = np.meshgrid(x, y)
Z = np.sqrt(X**2 + Y**2)
# generating the contours with some levels
levels = [1.0]
plt.figure(figsize=(10,10))
cs = plt.contour(X,Y,Z,levels=levels)
# finding vertices on a particular level
contours = cs.collections[0]
vertices_level = contours.get_paths()[0].vertices # In this example the
shape of vertices_level is (161,2)
# converting points into two lists; one per dimension. This step can be optimized
lX, lY = list(zip(*vertices_level))
# computing Ampere's Law rhs
def AmpereLaw(x0,y0,lX,lY):
S = 0
for ii in range(len(lX)-1):
dx = lX[ii+1] - lX[ii]
dy = lY[ii+1] - lY[ii]
ds = (1/((lX[ii]-x0)**2+(lY[ii]-y0)**2))*(-(lY[ii]-y0)*dx+(lX[ii]-x0)*dy)
if -1000 < ds < 1000: #to avoid very lare numbers when denominator is small
S = S + ds
return(S)
# we know point (0,0) is inside the contour
AmpereLaw(0,0,lX,lY)
# result: -6.271376740062852
# we know point (0,0) is inside the contour
AmpereLaw(-2,0,lX,lY)
# result: 0.00013279920934375876
You can use this result to find points inside one contour but outside of the other.

Related

Find new position for overlapping circles

I am trying to write a code that, for a given list of circles (list1), it is able to find the positions for new circles (list2). list1 and list2 have the same length, because for each circle in list1 there must be a circle from list2.
Each pair of circles (let's say circle1 from list1 and circle2 from list2), must be as close together as possible,
circles from list2 must not overlap with circles from list1, while circles of the single lists can overlap each other.
list1 is fixed, so now I have to find the right position for circles from list2.
I wrote this simple function to recognize if 2 circles overlap:
def overlap(x1, y1, x2, y2, r1, r2):
distSq = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)
radSumSq = (r1 + r2) * (r1 + r2)
if (distSq >= radSumSq):
return False # no overlap
else:
return True #overlap
and this is the list1:
with:
x=[14.11450195 14.14184093 14.15435028 14.16206741 14.16951752 14.17171097
14.18569565 14.19700241 14.23129082 14.24083233 14.24290752 14.24968338
14.2518959 14.26536751 14.27209759 14.27612877 14.2904377 14.29187012
14.29409599 14.29618549 14.30615044 14.31624985 14.3206892 14.3228569
14.36143875 14.36351967 14.36470699 14.36697292 14.37235737 14.41422081
14.42583466 14.43226814 14.43319225 14.4437027 14.4557848 14.46592999
14.47036076 14.47452068 14.47815609 14.52229309 14.53059006 14.53404236
14.5411644 ]
y=[-0.35319126 -0.44222349 -0.44763246 -0.35669261 -0.24366629 -0.3998799
-0.38940558 -0.57744932 -0.45223859 -0.21021004 -0.44250247 -0.45866323
-0.47203487 -0.51684451 -0.44884869 -0.2018993 -0.40296811 -0.23641759
-0.18019417 -0.33391538 -0.53565156 -0.45215255 -0.40939832 -0.26936951
-0.30894437 -0.55504167 -0.47177047 -0.45573688 -0.43100587 -0.5805912
-0.21770373 -0.199422 -0.17372169 -0.38522363 -0.56950212 -0.56947368
-0.48770753 -0.24940367 -0.31492445 -0.54263926 -0.53460872 -0.4053807
-0.43733299]
radius = 0.014
Copy and pasteable...
x = [14.11450195,14.14184093,14.15435028,14.16206741,14.16951752,
14.17171097,14.18569565,14.19700241,14.23129082,14.24083233,
14.24290752,14.24968338,14.2518959,14.26536751,14.27209759,
14.27612877,14.2904377,14.29187012,14.29409599,14.29618549,
14.30615044,14.31624985,14.3206892,14.3228569,14.36143875,
14.36351967,14.36470699,14.36697292,14.37235737,14.41422081,
14.42583466,14.43226814,14.43319225,14.4437027,14.4557848,
14.46592999,14.47036076,14.47452068,14.47815609,14.52229309,
14.53059006,14.53404236,14.5411644]
y = [-0.35319126,-0.44222349,-0.44763246,-0.35669261,-0.24366629,
-0.3998799,-0.38940558,-0.57744932,-0.45223859,-0.21021004,
-0.44250247,-0.45866323,-0.47203487,-0.51684451,-0.44884869,
-0.2018993,-0.40296811,-0.23641759,-0.18019417,-0.33391538,
-0.53565156,-0.45215255,-0.40939832,-0.26936951,-0.30894437,
-0.55504167,-0.47177047,-0.45573688,-0.43100587,-0.5805912,
-0.21770373,-0.199422,-0.17372169,-0.38522363,-0.56950212,
-0.56947368,-0.48770753,-0.24940367,-0.31492445,-0.54263926,
-0.53460872,-0.4053807,-0.43733299]
Now I am not sure about what I have to do, my first idea is to draw circles of list2 taking x and y from list one and do something like x+c and y+c, where c is a fixed value. Then I can call my overlapping function and, if there is overlap I can increase the c value.
In this way I have 2 for loops. Now, my questions are:
There is a way to avoid for loops?
Is there a smart solution to find a neighbor (circle from list2) for each circle from list1 (without overlaps with other circles from list2)?
Using numpy arrays, you can avoid for loops.
Setup from your example.
import numpy as np
#Using your x and y
c1 = np.array([x,y]).T
# random set of other centers within the same range as c1
c2 = np.random.random((10,2))
np.multiply(c2, c1.max(0)-c1.min(0),out = c2)
np.add(c2, c1.min(0), out=c2)
radius = 0.014
r = radius
min_d = (2*r)*(2*r)
plot_circles(c1,c2) # see function at end
An array of distances from each center in c1 to each center in c2
def dist(c1,c2):
dx = c1[:,0,None] - c2[:,0]
dy = c1[:,1,None] - c2[:,1]
return dx*dx + dy*dy
d = dist(c1,c2)
Or you could use scipy.spatial
from scipy.spatial import distance
d = distance.cdist(c1,c2,'sqeuclidean')
Create a 2d Boolean array for circles that intersect.
intersect = d <= min_d
Find the indices of overlapping circles from the two sets.
a,b = np.where(intersect)
plot_circles(c1[a],c2[b])
Using intersect or a and b to index c1,c2, and d you should be able to get groups of intersecting circles then figure out how to move the c2 centers - but I'll leave that for another question/answer. If a list2 circle intersects one list1 circle - find the line between the two and move along that line. If a list2 circle intersects more than one list1 circle - find the line between the two closestlist1circles and move thelitst2` circle along a line perpendicular to that. You didn't mention any constraints on moving the circles so maybe random movement then find the intersects again but that might be problematic. In the following image, it may be trivial to figure out how to move most of the red circles but the group circled in blue might require a different strategy.
Here are some examples for getting groups:
>>> for f,g,h in zip(c1[a],c2[b],d[a,b]):
print(f,g,h)
>>> c1[intersect.any(1)],c2[intersect.any(0)]
>>> for (f,g) in zip(c2,intersect.T):
if g.any():
print(f.tolist(),c1[g].tolist())
import matplotlib as mpl
from matplotlib import pyplot as plt
def plot_circles(c1,c2):
bounds = np.array([c1.min(0),c2.min(0),c1.max(0),c2.max(0)])
xmin, ymin = bounds.min(0)
xmax, ymax = bounds.max(0)
circles1 = [mpl.patches.Circle(xy,radius=r,fill=False,edgecolor='g') for xy in c1]
circles2 = [mpl.patches.Circle(xy,radius=r,fill=False,edgecolor='r') for xy in c2]
fig = plt.figure()
ax = fig.add_subplot(111)
for c in circles2:
ax.add_artist(c)
for c in circles1:
ax.add_artist(c)
ax.set_xlim(xmin-r,xmax+r)
ax.set_ylim(ymin-r,ymax+r)
plt.show()
plt.close()
This problem can very well be seen as an optimization problem. To be more precise, a nonlinear optimization problem with constraints.
Since optimization strategies are not always so easy to understand, I will define the problem as simply as possible and also choose an approach that is as general as possible (but less efficient) and does not involve a lot of mathematics. As a spoiler: We are going to formulate the problem and the minimization process in less than 10 lines of code using the scipy library.
However, I will still provide hints on where you can get your hands even dirtier.
Formulating the problem
As a guide for a formulation of an NLP-class problem (Nonlinear Programming), you can go directly to the two requirements in the original post.
Each pair of circles must be as close together as possible -> Hint for a cost-function
Circles must not overlap with other (moved) circles -> Hint for a constraint
Cost function
Let's start with the formulation of the cost function to be minimized.
Since the circles should be moved as little as possible (resulting in the closest possible neighborhood), a quadratic penalty term for the distances between the circles of the two lists can be chosen for the cost function:
import scipy.spatial.distance as sd
def cost_function(new_positions, old_positions):
new_positions = np.reshape(new_positions, (-1, 2))
return np.trace(sd.cdist(new_positions, old_positions, metric='sqeuclidean'))
Why quadratic? Partly because of differentiability and for stochastic reasons (think of the circles as normally distributed measurement errors -> least squares is then a maximum likelihood estimator). By exploiting the structure of the cost function, the efficiency of the optimization can be increased (elimination of sqrt). By the way, this problem is related to nonlinear regression, where (nonlinear) least squares are also used.
Now that we have a cost function at hand, we also have a good way to evaluate our optimization. To be able to compare solutions of different optimization strategies, we simply pass the newly calculated positions to the cost function.
Let's give it a try: For example, let us use the calculated positions from the Voronoi approach (by Paul Brodersen).
print(cost_function(new_positions, old_positions))
# prints 0.007999244511697411
That's a pretty good value if you ask me. Considering that the cost function spits out zero when there is no displacement at all, this cost is pretty close. We can now try to outperform this value by using classical optimization!
Non-linear constraint
We know that circles must not overlap with other circles in the new set. If we translate this into a constraint, we find that the lower bound for the distance is 2 times the radius and the upper bound is simply infinity.
import scipy.spatial.distance as sd
from scipy.optimize import NonlinearConstraint
def cons_f(x):
x = np.reshape(x, (-1, 2))
return sd.pdist(x)
nonlinear_constraint = NonlinearConstraint(cons_f, 2*radius, np.inf, jac='2-point')
Here we make life easy by approximating the Jacobi matrix via finite differences (see parameter jac='2-point'). At this point it should be said that we can increase the efficiency here, by formulating the derivatives of the first and second order ourselves instead of using approximations. But this is left to the interested reader. (It is not that hard, because we use quite simple mathematical expressions for distance calculation here.)
One additional note: You can also set a boundary constraint for the positions themselves not to exceed a specified region. This can then be used as another parameter. (See scipy.optimize.Bounds)
Minimizing the cost function under constraints
Now we have both ingredients, the cost function and the constraint, in place. Now let's minimize the whole thing!
from scipy.optimize import minimize
res = minimize(lambda x: cost_function(x, positions), positions.flatten(), method='SLSQP',
jac="2-point", constraints=[nonlinear_constraint])
As you can see, we approximate the first derivatives here as well. You can also go deeper here and set up the derivatives yourself (analytically).
Also note that we must always pass the parameters (an nx2 vector specifying the positions of the new layout for n circles) as a flat vector. For this reason, reshaping can be found several times in the code.
Evaluation, summary and visualization
Let's see how the optimization result performs in our cost function:
new_positions = np.reshape(res.x, (-1,2))
print(cost_function(new_positions, old_positions))
# prints 0.0010314079483565686
Starting from the Voronoi approach, we actually reduced the cost by another 87%! Thanks to the power of modern optimization strategies, we can solve a lot of problems in no time.
Of course, it would be interesting to see how the shifted circles look now:
Circles after Optimization
Performance: 77.1 ms ± 1.17 ms
The entire code:
from scipy.optimize import minimize
import scipy.spatial.distance as sd
from scipy.optimize import NonlinearConstraint
# Given by original post
positions = np.array([x, y]).T
def cost_function(new_positions, old_positions):
new_positions = np.reshape(new_positions, (-1, 2))
return np.trace(sd.cdist(new_positions, old_positions, metric='sqeuclidean'))
def cons_f(x):
x = np.reshape(x, (-1, 2))
return sd.pdist(x)
nonlinear_constraint = NonlinearConstraint(cons_f, 2*radius, np.inf, jac='2-point')
res = minimize(lambda x: cost_function(x, positions), positions.flatten(), method='SLSQP',
jac="2-point", constraints=[nonlinear_constraint])
One solution could be to follow the gradient of the unwanted spacing between each circle, though maybe there is a better way. This approach has a few parameters to tune and takes some time to run.
import matplotlib.pyplot as plt
from scipy.optimize import minimize as mini
import numpy as np
from scipy.optimize import approx_fprime
x = np.array([14.11450195,14.14184093,14.15435028,14.16206741,14.16951752,
14.17171097,14.18569565,14.19700241,14.23129082,14.24083233,
14.24290752,14.24968338,14.2518959,14.26536751,14.27209759,
14.27612877,14.2904377,14.29187012,14.29409599,14.29618549,
14.30615044,14.31624985,14.3206892,14.3228569,14.36143875,
14.36351967,14.36470699,14.36697292,14.37235737,14.41422081,
14.42583466,14.43226814,14.43319225,14.4437027,14.4557848,
14.46592999,14.47036076,14.47452068,14.47815609,14.52229309,
14.53059006,14.53404236,14.5411644])
y = np.array([-0.35319126,-0.44222349,-0.44763246,-0.35669261,-0.24366629,
-0.3998799,-0.38940558,-0.57744932,-0.45223859,-0.21021004,
-0.44250247,-0.45866323,-0.47203487,-0.51684451,-0.44884869,
-0.2018993,-0.40296811,-0.23641759,-0.18019417,-0.33391538,
-0.53565156,-0.45215255,-0.40939832,-0.26936951,-0.30894437,
-0.55504167,-0.47177047,-0.45573688,-0.43100587,-0.5805912,
-0.21770373,-0.199422,-0.17372169,-0.38522363,-0.56950212,
-0.56947368,-0.48770753,-0.24940367,-0.31492445,-0.54263926,
-0.53460872,-0.4053807,-0.43733299])
radius = 0.014
x0, y0 = (x, y)
def plot_circles(x, y, name='initial'):
fig, ax = plt.subplots()
for ii in range(x.size):
ax.add_patch(plt.Circle((x[ii], y[ii]), radius, color='b', fill=False))
ax.set_xlim(x.min() - radius, x.max() + radius)
ax.set_ylim(y.min() - radius, y.max() + radius)
fig.savefig(name)
plt.clf()
def spacing(s):
x, y = np.split(s, 2)
dX, dY = [np.subtract(*np.meshgrid(xy, xy, indexing='ij')).T
for xy in [x, y]]
dXY2 = dX**2 + dY**2
return np.minimum(dXY2[np.triu_indices(x.size, 1)] - (2 * radius) ** 2, 0).sum()
plot_circles(x, y)
def spacingJ(s):
return approx_fprime(s, spacing, 1e-8)
s = np.append(x, y)
for ii in range(50):
j = spacingJ(s)
if j.sum() == 0: break
s += .01 * j
x_new, y_new = np.split(s, 2)
plot_circles(x_new, y_new, 'new%i' % ii)
plot_circles(x_new, y_new, 'new%i' % ii)
https://giphy.com/gifs/x0lWDLZBz5O3gWTbLa
This answer implements a variation of the Lloyds algorithm. The basic idea is to compute the Voronoi diagram for your points / circles. This assigns each point a cell, which is a region that includes the point and which has a center that is maximally far away from all other points.
In the original algorithm, we would move each point towards the center of its Voronoi cell. Over time, this results in an even spread of points, as illustrated here.
In this variant, we only move points that overlap another point.
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi
from scipy.spatial.distance import cdist
def remove_overlaps(positions, radii, tolerance=1e-6):
"""Use a variation of Lloyds algorithm to move circles apart from each other until none overlap.
Parameters
----------
positions : array
The (x, y) coordinates of the circle origins.
radii : array
The radii for each circle.
tolerance : float
If all circles overlap less than this threshold, the computation stops.
Higher values leads to faster convergence.
Returns
-------
new_positions : array
The (x, y) coordinates of the circle origins.
See also
--------
https://en.wikipedia.org/wiki/Lloyd%27s_algorithm
"""
positions = np.array(positions)
radii = np.array(radii)
minimum_distances = radii[np.newaxis, :] + radii[:, np.newaxis]
minimum_distances[np.diag_indices_from(minimum_distances)] = 0 # ignore distances to self
# Initialize the first loop.
distances = cdist(positions, positions)
displacements = np.max(np.clip(minimum_distances - distances, 0, None), axis=-1)
while np.any(displacements > tolerance):
centroids = _get_voronoi_centroids(positions)
# Compute the direction from each point towards its corresponding Voronoi centroid.
deltas = centroids - positions
magnitudes = np.linalg.norm(deltas, axis=-1)
directions = deltas / magnitudes[:, np.newaxis]
# Mask NaNs that arise if the magnitude is zero, i.e. the point is already center of the Voronoi cell.
directions[np.isnan(directions)] = 0
# Step into the direction of the centroid.
# Clipping prevents overshooting of the centroid when stepping into the direction of the centroid.
# We step by half the displacement as the other overlapping point will be moved in approximately the opposite direction.
positions = positions + np.clip(0.5 * displacements, None, magnitudes)[:, np.newaxis] * directions
# Initialize next loop.
distances = cdist(positions, positions)
displacements = np.max(np.clip(minimum_distances - distances, 0, None), axis=-1)
return positions
def _get_voronoi_centroids(positions):
"""Construct a Voronoi diagram from the given positions and determine the center of each cell."""
voronoi = Voronoi(positions)
centroids = np.zeros_like(positions)
for ii, idx in enumerate(voronoi.point_region):
region = [jj for jj in voronoi.regions[idx] if jj != -1] # i.e. ignore points at infinity; TODO: compute correctly clipped regions
centroids[ii] = np.mean(voronoi.vertices[region], axis=0)
return centroids
if __name__ == '__main__':
x = np.array([14.11450195,14.14184093,14.15435028,14.16206741,14.16951752,
14.17171097,14.18569565,14.19700241,14.23129082,14.24083233,
14.24290752,14.24968338,14.2518959,14.26536751,14.27209759,
14.27612877,14.2904377,14.29187012,14.29409599,14.29618549,
14.30615044,14.31624985,14.3206892,14.3228569,14.36143875,
14.36351967,14.36470699,14.36697292,14.37235737,14.41422081,
14.42583466,14.43226814,14.43319225,14.4437027,14.4557848,
14.46592999,14.47036076,14.47452068,14.47815609,14.52229309,
14.53059006,14.53404236,14.5411644])
y = np.array([-0.35319126,-0.44222349,-0.44763246,-0.35669261,-0.24366629,
-0.3998799,-0.38940558,-0.57744932,-0.45223859,-0.21021004,
-0.44250247,-0.45866323,-0.47203487,-0.51684451,-0.44884869,
-0.2018993,-0.40296811,-0.23641759,-0.18019417,-0.33391538,
-0.53565156,-0.45215255,-0.40939832,-0.26936951,-0.30894437,
-0.55504167,-0.47177047,-0.45573688,-0.43100587,-0.5805912,
-0.21770373,-0.199422,-0.17372169,-0.38522363,-0.56950212,
-0.56947368,-0.48770753,-0.24940367,-0.31492445,-0.54263926,
-0.53460872,-0.4053807,-0.43733299])
radius = 0.014
positions = np.c_[x, y]
radii = np.full(len(positions), radius)
fig, axes = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(14, 7))
for position, radius in zip(positions, radii):
axes[0].add_patch(plt.Circle(position, radius, fill=False))
axes[0].set_xlim(x.min() - radius, x.max() + radius)
axes[0].set_ylim(y.min() - radius, y.max() + radius)
axes[0].set_aspect('equal')
new_positions = remove_overlaps(positions, radii)
for position, radius in zip(new_positions, radii):
axes[1].add_patch(plt.Circle(position, radius, fill=False))
for ax in axes.ravel():
ax.set_aspect('equal')
plt.show()

How to count how many particles in a 2D Gaussian distribution

I've just started using Python, so I'm sorry if I ask for trivial things.
I generated two random Gaussian distributions, and I used them to generate a 2D Gaussian distribution. What I'd like to do now is to plot a graph that represents the number of elements within a circumference of the 2D Gaussian distribution, varying the radius of the circumference (reducing it at each step).
You would be so kind to help me solve the problem. Thank you for taking my post into consideration.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
mu1, sigma1 = 0, 0.1 # mean and standard deviation
s1 = np.random.normal(mu1, sigma1, 10000) # generate N randoomly Gaussian points
mu2, sigma2 = 0.8, 0.3
s2 = np.random.normal(mu2, sigma2, 10000)
#ISTOGRAMMA DI DUE DISTRIBUZIONI GAUSSIANE CON DIFFERENTI SET
plt.figure(1)
plt.title('Histogram of a 2D-Gaussian Distribution')
bins1 = plt.hist(s1, 100)
bins2 = plt.hist(s2, 100)
plt.show()
#DISTRIBUZIONE GAUSSIANA 2D
plt.figure(2)
plt.title('2D-Gaussian Distribution')
bins = plt.hist2d(s1, s2, 100)
cb = plt.colorbar()
cb.set_label('counts in bin')
plt.show()
This should do the trick, assuming that s1 and s2 are the coordinates of some 2D points. If not, the code can be easily changed to match your problem.
First you center the two distributions by subtracting their mean, then you check which of their elements (absolute value) is inside the radius of your circle. You then take a logical and to make sure only to take the points that have both coordinates inside the circle.
radius = 0.1
valid_indexes = np.logical_and(abs(s1 -mu1)<= radius, abs(s2 - mu2) <= radius)
s1_valid = s1[valid_indexes]
s2_valid = s2[valid_indexes]
You have now obtained the points in the distributions that are inside a circle with given radius centered in (mu1, mu2).
[Edit]
As you want to count the number of elements, and not extract them, you can easily do
radius = 0.1
sum(np.logical_and(abs(s1 -mu1)< radius, abs(s2 - mu2) < radius))
[Edit 2]
This plots the number of points for every radius of the circle, starting from limit and reducing it by step until 0
step = 0.025
limit = 1
s1_ca = abs(s1-mu1)
s2_ca = abs(s2-mu2)
points_in_radius = []
radius_values = np.round(np.arange(0, limit, step), 3)[::-1]
for radius in radius_values:
points_in_radius.append(sum(np.logical_and(s1_ca < radius, s2_ca < radius)))
plt.plot(points_in_radius)
plt.xticks(range(len(points_in_radius)), radius_values, rotation=90)
plt.show()
first i center the distributions and take their abs value. then I create the range of radiuses to use and finally I cycle through them and add the result using my above formula.
This is not the most efficient way to do it, but it works.

Take data from a circle in python

I am looking into how the intensity of a ring changes depending on angle. Here is an example of an image:
What I would like to do is take a circle of values from within the center of that doughnut and plot them vs angle. What I'm currently doing is using scipy.ndimage.interpolation.rotate and taking slices radially through the ring, and extracting the maximum of the two peaks and plotting those vs angle.
crop = np.ones((width,width)) #this is my image
slices = np.arange(0,width,1)
stack = np.zeros((2*width,len(slices)))
angles = np.linspace(0,2*np.pi,len(crop2))
for j in range(len(slices2)): # take slices
stack[:,j] = rotate(crop,slices[j],reshape=False)[:,width]
However I don't think this is doing what I'm actually looking for. I'm mostly struggling with how to extract the data I want. I have also tried applying a mask which looks like this;
to the image, but then I don't know how to get the values within that mask in the correct order (ie. in order of increasing angle 0 - 2pi)
Any other ideas would be of great help!
I made a different input image to help verifying correctness:
import numpy as np
import scipy as sp
import scipy.interpolate
import matplotlib.pyplot as plt
# Mock up an image.
W = 100
x = np.arange(W)
y = np.arange(W)
xx,yy = np.meshgrid(x,y)
image = xx//5*5 + yy//5*5
image = image / np.max(image) # scale into [0,1]
plt.imshow(image, interpolation='nearest', cmap='gray')
plt.show()
To sample values from circular paths in the image, we first build an interpolator because we want to access arbitrary locations. We also vectorize it to be faster.
Then, we generate the coordinates of N points on the circle's circumference using the parametric definition of the circle x(t) = sin(t), y(t) = cos(t).
N should be at least twice the circumference (Nyquist–Shannon sampling theorem).
interp = sp.interpolate.interp2d(x, y, image)
vinterp = np.vectorize(interp)
for r in (15, 30, 45): # radii for circles around image's center
xcenter = len(x)/2
ycenter = len(y)/2
arclen = 2*np.pi*r
angle = np.linspace(0, 2*np.pi, arclen*2, endpoint=False)
value = vinterp(xcenter + r*np.sin(angle),
ycenter + r*np.cos(angle))
plt.plot(angle, value, label='r={}'.format(r))
plt.legend()
plt.show()

How can I calculate the area within a contour in Python using the Matplotlib?

I am trying to figure out a way to get the area inside a specific contour line? I use matplotlib.pyplot to create my contours. Does anyone have experience for this?
Thanks a lot.
From the collections attribute of the contour collection, which is returned by the contour function, you can get the paths describing each contour. The paths' vertices attributes then contain the ordered vertices of the contour.
Using the vertices you can approximate the contour integral 0.5*(x*dy-y*dx), which by application of Green's theorem gives you the area of the enclosed region.
However, the contours must be fully contained in the plot, because otherwise the contours are broken up into multiple, not necessarily connected paths and the method breaks down.
Here's the method used to compute the area enclosed of the radius function, i.e. r = (x^2 + y^2)^0.5, for r=1.0, r=2.0, r=3.0.
import numpy as np
import matplotlib.pylab as plt
# Use Green's theorem to compute the area
# enclosed by the given contour.
def area(vs):
a = 0
x0,y0 = vs[0]
for [x1,y1] in vs[1:]:
dx = x1-x0
dy = y1-y0
a += 0.5*(y0*dx - x0*dy)
x0 = x1
y0 = y1
return a
# Generate some test data.
delta = 0.01
x = np.arange(-3.1, 3.1, delta)
y = np.arange(-3.1, 3.1, delta)
X, Y = np.meshgrid(x, y)
r = np.sqrt(X**2 + Y**2)
# Plot the data
levels = [1.0,2.0,3.0]
cs = plt.contour(X,Y,r,levels=levels)
plt.clabel(cs, inline=1, fontsize=10)
# Get one of the contours from the plot.
for i in range(len(levels)):
contour = cs.collections[i]
vs = contour.get_paths()[0].vertices
# Compute area enclosed by vertices.
a = area(vs)
print "r = " + str(levels[i]) + ": a =" + str(a)
plt.show()
Output:
r = 1.0: a = 2.83566351207
r = 2.0: a = 11.9922190971
r = 3.0: a = 27.3977413253
A vectorized version of #spfrnd's answer to compute the area:
x=contour.vertices[:,0]
y=contour.vertices[:,1]
area=0.5*np.sum(y[:-1]*np.diff(x) - x[:-1]*np.diff(y))
area=np.abs(area)
Note that you may need to take the abs of the area because if the points along the contour are oriented in the opposite direction the result will be negative.
Obviously, the results for r=1,2,3 in #spfrnd's answer are far off the exact areas for circles according to A(r) = pi r^2, even for a rather dense grid. The reason the above code doesn't work properly is that the returned vertices are incomplete due to the clabels generated by
plt.clabel(cs, inline=1, fontsize=10)
and consequently, the shoelace algorithm calculates the wrong area.
You can verify this easily by plotting the returned vertices next to the contour plot (best use a larger delta or keep only every Nth point via the :: operator)
N = 10
vs = cs.collections[0].get_paths()[0].vertices
plt.plot(vs[::N, 0], vs[::N, 1], marker="x", alpha=0.5)
For a visualization, see this picture.
Removing the clabels leads to quite accurate results, even for rather coarse grids. Setting inline=False in the clabel command does the job as well.
The question regards a "specific contour line" and spfrnd toghether with PandaScience's response is a good solution.
However, the method will not give a correct value if there are multiple islands for the same contour level. The code below handles multiple islands:
for i in range(len(levels)):
contour = cs.collections[i]
vs = contour.get_paths()
area=0
for island in vs:
ai=np.abs(area(island.vertices))
area+=ai

How to generate equispaced interpolating values

I have a list of (x,y) values that are not uniformly spaced. Here is the archive used in this question.
I am able to interpolate between the values but what I get are not equispaced interpolating points. Here's what I do:
x_data = [0.613,0.615,0.615,...]
y_data = [5.919,5.349,5.413,...]
# Interpolate values for x and y.
t = np.linspace(0, 1, len(x_data))
t2 = np.linspace(0, 1, 100)
# One-dimensional linear interpolation.
x2 = np.interp(t2, t, x_data)
y2 = np.interp(t2, t, y_data)
# Plot x,y data.
plt.scatter(x_data, y_data, marker='o', color='k', s=40, lw=0.)
# Plot interpolated points.
plt.scatter(x2, y2, marker='o', color='r', s=10, lw=0.5)
Which results in:
As can be seen, the red dots are closer together in sections of the graph where the original points distribution is denser.
I need a way to generate the interpolated points equispaced in x, y according to a given step value (say 0.1)
As askewchan correctly points out, when I mean "equispaced in x, y" I mean that two consecutive interpolated points in the curve should be distanced from each other (euclidean straight line distance) by the same value.
I tried unubtu's answer and it works well for smooth curves but seems to break for not so smooth ones:
This happens because the code calculates the point distance in an euclidean way instead of directly over the curve and I need the distance over the curve to be the same between points. Can this issue be worked around somehow?
Convert your xy-data to a parametrized curve, i.e. calculate all all distances between the points and generate the coordinates on the curve by cumulative summing. Then interpolate the x- and y-coordinates independently with respect to the new coordinates.
import numpy as np
from matplotlib import pyplot as plt
data = '''0.615 5.349
0.615 5.413
0.617 6.674
0.617 6.616
0.63 7.418
0.642 7.809
0.648 8.04
0.673 8.789
0.695 9.45
0.712 9.825
0.734 10.265
0.748 10.516
0.764 10.782
0.775 10.979
0.783 11.1
0.808 11.479
0.849 11.951
0.899 12.295
0.951 12.537
0.972 12.675
1.038 12.937
1.098 13.173
1.162 13.464
1.228 13.789
1.294 14.126
1.363 14.518
1.441 14.969
1.545 15.538
1.64 16.071
1.765 16.7
1.904 17.484
2.027 18.36
2.123 19.235
2.149 19.655
2.172 20.096
2.198 20.528
2.221 20.945
2.265 21.352
2.312 21.76
2.365 22.228
2.401 22.836
2.477 23.804'''
data = np.array([line.split() for line in data.split('\n')],dtype=float)
x,y = data.T
xd = np.diff(x)
yd = np.diff(y)
dist = np.sqrt(xd**2+yd**2)
u = np.cumsum(dist)
u = np.hstack([[0],u])
t = np.linspace(0,u.max(),10)
xn = np.interp(t, u, x)
yn = np.interp(t, u, y)
f = plt.figure()
ax = f.add_subplot(111)
ax.set_aspect('equal')
ax.plot(x,y,'o', alpha=0.3)
ax.plot(xn,yn,'ro', markersize=8)
ax.set_xlim(0,5)
Let's first consider a simple case. Suppose your data looked like the blue line,
below.
If you wanted to select equidistant points that were r distance apart,
then there would be some critical value for r where the cusp at (1,2) is the first equidistant point.
If you wanted points that were greater than this critical distance apart, then
the first equidistant point would jump from (1,2) to some place very different --
depicted by the intersection of the green arc with the blue line. The change is not gradual.
This toy case suggests that a tiny change in the parameter r can have a radical, discontinuous affect on the solution.
It also suggests that you must know the location of the ith equidistant point
before you can determine the location of the (i+1)-th equidistant point.
So it appears an iterative solution is required:
import numpy as np
import matplotlib.pyplot as plt
import math
x, y = np.genfromtxt('data', unpack=True, skip_header=1)
# find lots of points on the piecewise linear curve defined by x and y
M = 1000
t = np.linspace(0, len(x), M)
x = np.interp(t, np.arange(len(x)), x)
y = np.interp(t, np.arange(len(y)), y)
tol = 1.5
i, idx = 0, [0]
while i < len(x):
total_dist = 0
for j in range(i+1, len(x)):
total_dist += math.sqrt((x[j]-x[j-1])**2 + (y[j]-y[j-1])**2)
if total_dist > tol:
idx.append(j)
break
i = j+1
xn = x[idx]
yn = y[idx]
fig, ax = plt.subplots()
ax.plot(x, y, '-')
ax.scatter(xn, yn, s=50)
ax.set_aspect('equal')
plt.show()
Note: I set the aspect ratio to 'equal' to make it more apparent that the points are equidistant.
The following script will interpolate points with a equal step of x_max - x_min / len(x) = 0.04438
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
data = np.loadtxt('data.txt')
x = data[:,0]
y = data[:,1]
f = interp1d(x, y)
x_new = np.linspace(np.min(x), np.max(x), x.shape[0])
y_new = f(x_new)
plt.plot(x,y,'o', x_new, y_new, '*r')
plt.show()
Expanding on the answer by #Christian K., here's how to do this for higher dimensional data with scipy.interpolate.interpn. Let's say we want to resample to 10 equally-spaced points:
import numpy as np
import scipy
# Assuming that 'data' is rows x dims (where dims is the dimensionality)
diffs = data[1:, :] - data[:-1, :]
dist = np.linalg.norm(diffs, axis=1)
u = np.cumsum(dist)
u = np.hstack([[0], u])
t = np.linspace(0, u[-1], 10)
resampled = scipy.interpolate.interpn((u,), pts, t)
It IS possible to generate equidistant points along the curve. But there must be more definition of what you want for a real answer. Sorry, but the code I've written for this task is in MATLAB, but I can describe the general ideas. There are three possibilities.
First, are the points to be truly equidistant from the neighbors in terms of a simple Euclidean distance? To do so would involve finding the intersection at any point on the curve with a circle of a fixed radius. Then just step along the curve.
Next, if you intend distance to mean distance along the curve itself, if the curve is a piecewise linear one, the problem is again easy to do. Just step along the curve, since distance on a line segment is easy to measure.
Finally, if you intend for the curve to be a cubic spline, again this is not incredibly difficult, but is a bit more work. Here the trick is to:
Compute the piecewise linear arclength from point to point along the curve. Call it t.
Generate a pair of cubic splines, x(t), y(t).
Differentiate x and y as functions of t. Since these are cubic segments, this is easy. The derivative functions will be piecewise quadratic.
Use an ode solver to move along the curve, integrating the differential arclength function. In MATLAB, ODE45 worked nicely.
Thus, one integrates
sqrt((x')^2 + (y')^2)
Again, in MATLAB, ODE45 can be set to identify those locations where the function crosses certain specified points.
If your MATLAB skills are up to the task, you can look at the code in interparc for more explanation. It is reasonably well commented code.

Categories