I'm generating a list of (x,y) coordinates from detecting a ball's flight in a video. The problem I have is for a few frames in the middle of the video the ball can't be detected, for these frames the list appends (-1,-1).
Is there a way to estimate the true (x,y) coordinates of the ball for these points?
Eg tracked points list being:
pointList = [(60, 40), (55, 42), (53, 43), (-1, -1), (-1, -1), (-1, -1), (35, 55), (30, 60)]
Then returning an estimate of what the 3 (-1,-1) missing coordinates would be with context to the sourounding points (preserving the curve).
If it's a ball then theoretically it should have a parabolic path, you could try and fit a curve ignoring the (-1, -1) and then replace the missing values.
Something like...
import numpy as np
pointList = [(60, 40), (55, 42), (53, 43), (-1, -1), (-1, -1), (-1, -1), (35, 55), (30, 60)]
x, y = list(zip(*[(x, y) for (x, y) in pointList if x>0]))
fit = np.polyfit(x, y, 2)
polynome = np.poly1d(fit)
# call your polynome for missing data, e.g.
missing = (55 - i*(55-35)/4 for i in range(3))
print([(m, polynome(m)) for m in missing])
giving ...
[(55.0, 41.971982486554325), (50.0, 44.426515896714186), (45.0, 47.44514924300471)]
You could use scipys spline to interpolate the missing values:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import splprep, splev
pointList = [(60, 40), (55, 42), (53, 43),
(-1, -1), (-1, -1), (-1, -1),
(35, 55), (30, 60)]
# Remove the missing values
pointList = np.array(pointList)
pointList = pointList[pointList[:, 0] != -1, :]
def spline(x, n, k=2):
tck = splprep(x.T, s=0, k=k)[0]
u = np.linspace(0.0, 1.0, n)
return np.column_stack(splev(x=u, tck=tck))
# Interpolate the points with a quadratic spline at 100 points
pointList_interpolated = spline(pointList, n=100, k=2)
plt.plot(*pointList.T, c='r', ls='', marker='o', zorder=10)
plt.plot(*pointList_interpolated.T, c='b')
If camera is not moving - just the ball and you ignore the wind, then trajectory is parabolic. See: https://en.wikipedia.org/wiki/Trajectory#Uniform_gravity,_neither_drag_nor_wind
In this case fit quadratic function to points which you know and you will get missing ones. Set also error of boundary points in the vicinity of unknown area (point 53,43 and 35, 55) to be 0 or close to 0 (no-error, big weight in interpolation) when fitting so your interpolation will go through these points.
There are some libraries for polynomial fit. E.g. numpy.polyfit:
https://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.polynomial.polynomial.polyfit.html
Related
I have 9 points (longitudes, latitudes in degrees) on the surface of Earth follows.
XY = [(100, 10), (100, 11), (100, 13), (101, 10), (101, 11), (101, 13), (103, 10), (103, 11), (103, 13)]
print (len(XY))
# 9
I wanted to extract those points which are at least 3 degrees far from each other.
I tried it as follows.
results = []
for point in XY:
x1,y1 = point
for result in results:
x2,y2 = result
distance = math.hypot(x2 - x1, y2 - y1)
if distance >= 3:
results.append(point)
print (results)
But output is empty.
edit 2
from sklearn.metrics.pairwise import haversine_distances
from math import radians
results = []
for point in XY:
x1,y1 = [radians(_) for _ in point]
for result in results:
distance = haversine_distances((x1,y1), (x2,y2))
print (distance)
if distance >= 3:
results.append(point)
print (results)
Still the result is empty
edit 3
results = []
for point in XY:
x1,y1 = point
for point in XY:
x2,y2 = point
distance = math.hypot(x2 - x1, y2 - y1)
print (distance)
if distance >= 3:
results.append(point)
print (results)
print (len(results))
# 32 # unexpected len
Important: You've said you want to "Extract those points which are at least 3 degrees far from each other" but then you've used the Euclidean distance with math.hypot(). As mentioned by #martineau, this should use the Haversine angular distance.
Since your points are "(longitudes, latitudes in degrees)", they first need to be converted to radians. And the pairs should be flipped so that latitude comes first, as required by the haversine_distances() function. That can be done with:
XY_r = [(math.radians(lat), math.radians(lon)) for lon, lat in XY]
Here's the kicker - none of the combnation-making or looping is necesssary. If haversine_distances() is passed in a list of points, it will calculate the distances between all of them and provide a result as an array of arrays. These can then be converted back to degrees and checked; or convert 3 degrees to radians and then check against h-dists.
import math
import numpy as np
from sklearn.metrics.pairwise import haversine_distances
XY = [(100, 10), (100, 11), (100, 13), (101, 10), (101, 11), (101, 13), (103, 10), (103, 11), (103, 13)]
# convert to radians and flip so that latitude is first
XY_r = [(math.radians(lat), math.radians(lon)) for lon, lat in XY]
distances = haversine_distances(XY_r) # distances array-of-arrays in RADIANS
dist_criteria = distances >= math.radians(3) # at least 3 degrees (in radians) away
results = [point for point, result in zip(XY, dist_criteria) if np.any(result)]
print(results)
print(len(results))
print('<3 away from all:', set(XY) - set(results))
Output:
[(100, 10), (100, 11), (100, 13), (101, 10), (101, 13), (103, 10), (103, 11), (103, 13)]
8
<3 away from all: {(101, 11)}
Wrt the previous edit and your original code:
Your first two attempts are giving empty results because of this:
results = []
for point in XY:
...
for result in results:
results is initialised as an empty list. So the for result in results loop will directly exit. Nothing inside the loop executes.
The 3rd attempt is getting you 32 results because of repetitions. You've got:
for point in XY:
...
for point in XY:
so some points you get will be the same point.
To avoid duplicates in the loops:
Add a check for it and go to the next iteration:
if (x1, y1) == (x2, y2):
continue
Btw, you're mangling the point variable because it's reused in both loops. It doesn't cause a problem but makes your code harder to debug. Either make them point1 and point2, or even better, instead of for point in XY: x1, y1 = point, you can directly do for x1, y1 in XY - that's called tuple unpacking.
for x1, y1 in XY:
for x2, y2 in XY:
if (x1, y1) == (x2, y2):
continue
...
You also need to change result to be a set instead of a list so that the same point is not re-added to the results when it's more than 3 away from another point. Sets don't allow duplicates, that way points don't get repeated in results.
Use itertools.combinations() to get unique pairs of points without repetitions. This allows you to skip the duplicate check (unless XY actually has duplicate points) and brings the previous block down to one for-loop:
import itertools
import math
results = set() # unique results
for (x1, y1), (x2, y2) in itertools.combinations(XY, r=2):
distance = math.hypot(x2 - x1, y2 - y1) # WRONG! see above
if distance >= 3:
# add both points
results.update({(x1, y1), (x2, y2)})
print(results)
print(len(results))
print('<3 away from all:', set(XY) - results)
The (wrong) output:
{(103, 11), (100, 13), (101, 13), (100, 10), (103, 10), (101, 10), (103, 13), (100, 11)}
8
<3 away from all: {(101, 11)}
(The result is the same but merely by coincidence of the input data.)
I have several points that I need to covert them from Cartesian to Polar Coordinates. But for some points, the results I got were negative values.
For example, the origin or the center of the system is (50,50), and the point I want to covert is (10, 43). The angle I got from my code is -170.07375449, but I wish the angle is 189.92624551. (I hope all of the angles after conversion are between 0~360 degree)
How I fix this?
Thanks!!!
import numpy as np
points = np.array([(10, 43), (10, 44), (10, 45), (10, 46), (10, 47)])
#Set the center (origin) at (50, 50). Not (0, 0)
def cart_to_pol(coords, center = [50,50], deg = True):
complex_format = np.array(coords, dtype = float).view(dtype = np.complex) -\
np.array(center, dtype = float).view(dtype = np.complex)
# return np.abs(complex_format).squeeze(), np.angle(complex_format, deg = deg).squeeze()
return np.angle(complex_format, deg=deg).squeeze()
print(cart_to_pol(points))
You could use np.where to add 360 to the negative angles:
import numpy as np
points = np.array([(10, 43), (10, 44), (10, 45), (10, 46), (10, 47)])
#Set the center (origin) at (50, 50). Not (0, 0)
def cart_to_pol(coords, center = [50,50], deg = True):
complex_format = np.array(coords, dtype = float).view(dtype = np.complex) -\
np.array(center, dtype = float).view(dtype = np.complex)
angles = np.angle(complex_format, deg=deg).squeeze()
summand = 360 if deg else 2*np.pi
return np.where(angles < 0, angles+summand, angles)
print(cart_to_pol(points))
Output:
[189.92624551 188.53076561 187.12501635 185.71059314 184.28915333]
Note that complex numbers are not needed here. arctan2(y, x) calculates the desired angle. To get the distances: np.linalg.norm(diff_with_center, axis=-1).
def cart_to_pol(coords, center=[50, 50], deg=True):
conversion = 180 / np.pi if deg else 1
diff_with_center = points - center
angles = np.arctan2(diff_with_center[:, 1], diff_with_center[:, 0])
return conversion * np.where(angles < 0, angles + 2*np.pi, angles)
If you need to convert [-180; 180] angle to [0; 360] you can use this code:
def convert_angle(angle):
return (angle + 360) % 360
Combining some of the other solutions here...
If you specify the origin and/or points as float, you can us a view to operate on them as complex numbers, and then simply return the angle modulo 360 degrees:
points = points - origin
np.angle(points.view(np.complex), deg=True) % 360
>>> array([[189.92624551],
[188.53076561],
[187.12501635],
[185.71059314],
[184.28915333]])
Or, with in-place operations, assuming that points is already floating point:
np.subtract(points, origin, out=points)
v = points.view(np.complex)
np.arctan2(v.imag, v.real, out=v.real)
np.degrees(v.real, out=v.real)
np.mod(v.real, 360, out=v.real)
print(points[0]) # or print(v.real)
>>> array([[189.92624551],
[188.53076561],
[187.12501635],
[185.71059314],
[184.28915333]])
In this case we can't use angle because there is no out option, but we can compute arctan2 in-place in points[0], via our view. This uses no more memory than the original (floating point) points array, and while I didn't time it, should not take more operations to compute.
Have a set of data points (x,y,z), and trying to fit a generic quadratic to them using scipy optimize.curve_fit.
I have tried a couple different methods, but can't seem to make it work. Any guidance on why I would be getting this error, or a recommended different method?
Error is "ValueError: operands could not be broadcast together with shapes (2,) (12,)"
import scipy.optimize as optimize
XY = [(11, 70), (11, 75), (11, 80), (11, 85), (12, 70), (12, 75), (12, 80), (12, 85), (13, 70), (13, 75), (13, 80), (13, 85)]
Z = [203.84, 208, 218.4, 235.872, 228.30080000000004, 232.96000000000004, 244.60800000000006, 264.1766400000001, 254.8, 260, 273, 294.84000000000003]
guess = (1,1,1,1,1,1)
def fit(X, a, b, c, d, f, g):
return a + (b*X[0])+(c*X[1])+(d*X[0]**2)+(f*X[1]**2)+(g*X[0]*X[1])
params, cov = optimize.curve_fit(fit, XY, Z, guess)
According to the docs you need XY to be size (k,M) where k is the dimension and M is the number of samples. In your case you've defined XY to be size (M,k).
Try the following
import numpy as np
...
params, cov = optimize.curve_fit(fit, np.transpose(XY), Z, guess)
I’m working with tetris pieces.
The pieces are defined with coordinates, where each piece has an origin block (0,0)
So an L piece could be defined as [(0,0), (0,1), (0,2), (1,2)] as well as [(0,-1), (0,0), (0,1), (1,1)] depending on where you place the origin block.
I want to check whether a set of coordinates A e.g. [(50,50), (50,51), (50,52), (51,52)] matches the shape of a given tetris piece B.
I’m currently using numpy to take away one of the A values from every value in A to reach relative coordinates, then compare with B. The ordering of A will always been in increasing order, but is not guarenteed to match the ordering of B. B is stored in a list with other tetris pieces, and throughout the program, it's origin block will remain the same. This method below seems inefficient and doesn’t account for rotations / reflections of B.
def isAinB(A,B): # A and B are numpy arrays
for i in range(len(A)):
matchCoords = A - A[i]
setM = set([tuple(x) for x in matchCoords])
setB = set([tuple(x) for x in B])
if setM == setB: # Sets are used here because the ordering of M and B are not guarenteed to match
return True
return False
Is there an efficient method / function to implement this? (Accounting for rotations and reflections aswell if possible)
This is one way to approach it. The idea is to first build all the set of variations of a piece in some canonical coordinates (you can do this once per piece kind and reuse it), then put the given piece in the same canonical coordinates and compare.
# Rotates a piece by 90 degrees
def rotate_coords(coords):
return [(y, -x) for x, y in coords]
# Returns a canonical coordinates representation of a piece as a frozen set
def canonical_coords(coords):
x_min = min(x for x, _ in coords)
y_min = min(y for _, y in coords)
return frozenset((y - y_min, x - x_min) for x, y in coords)
# Makes all possible variations of a piece (optionally including reflections)
# as a set of canonical representations
def make_piece_variations(piece, reflections=True):
variations = {canonical_coords(piece)}
for i in range(3):
piece = rotate_coords(piece)
variations.add(canonical_coords(piece))
if reflections:
piece_reflected = [(y, x) for x, y in piece]
variations.update(make_piece_variations(piece_reflected, False))
return variations
# Checks if a given piece is in a set of variations
def matches_piece(piece, variations):
return canonical_coords(piece) in variations
These are some tests:
# L-shaped piece
l_piece = [(0, 0), (0, 1), (0, 2), (1, 2)]
l_piece_variations = make_piece_variations(l_piece, reflections=True)
# Same orientation
print(matches_piece([(50, 50), (50, 51), (50, 52), (51, 52)], l_piece_variations))
# True
# Rotated
print(matches_piece([(50, 50), (51, 50), (52, 50), (52, 49)], l_piece_variations))
# True
# Reflected and rotated
print(matches_piece([(50, 50), (49, 50), (48, 50), (48, 49)], l_piece_variations))
# True
# Rotated and different order of coordinates
print(matches_piece([(50, 48), (50, 50), (49, 48), (50, 49)], l_piece_variations))
# True
# Different piece
print(matches_piece([(50, 50), (50, 51), (50, 52), (50, 53)], l_piece_variations))
# False
This is not a particularly smart algorithm, but it works with minimal constraints.
EDIT: Since in your case you say that the first block and the relative order will always be the same, you can redefine the canonical coordinates as follows to make it just a bit more optimal (although the performance difference will probably be negligible and its use will be more restricted):
def canonical_coords(coords):
return tuple((y - coords[0][0], x - coords[0][1]) for x, y in coords[1:])
The first coordinate will always be (0, 0), so you can skip that and use it as reference point for the rest, and instead of a frozenset you can use a tuple for the sequence of coordinates.
Having a 2d data array data and two co-ordinate arrays x, y I can plot a contour plot with matplotlib at any given level
import numpy as np
import matplotlib.pyplot as plt
x, y = np.linspace(0, 2*np.pi), np.linspace(0, 2*np.pi)
xx, yy = np.meshgrid(x, y)
data = np.sin(xx) * np.sin(yy)
level = 0.5
contour_ = plt.contour(xx, yy, data, levels=[level])
plt.show()
Now, I am not really interested in plotting, but rather in position of the contour. For example, I want to see if the contour lies within the x, y domain or 'leaks' outside.
I can get a path object containing (x, y) points of the contour by calling
contour_path = contour_.collections[0].get_paths()
My question is whether there are standard tools to get the same (or analogous) information using only numpy, without matplotlib module. Since no plotting is involved, that would be reasonable.
If you read the source code of contour you can find Cntr:
from matplotlib._cntr import Cntr
x, y = np.linspace(0, 2*np.pi), np.linspace(0, 2*np.pi)
xx, yy = np.meshgrid(x, y)
data = np.sin(xx) * np.sin(yy)
level = 0.5
cntr = Cntr(xx, yy, data)
res = cntr.trace(level)
res is a list that contains the pathes and the codes.
If you just have the data field, you can find approximately where the boundary lies;
In [1]: import numpy as np
In [2]: x, y = np.linspace(0, 2*np.pi), np.linspace(0, 2*np.pi)
In [3]: xx, yy = np.meshgrid(x, y)
In [4]: data = np.sin(xx) * np.sin(yy)
In [5]: scan = np.logical_and(data>0.45, data<0.55)
In [6]: a, b = scan.shape
In [7]: for x in range(a):
for y in range(b):
if scan[x,y]:
print('({}, {}),'.format(x,y), end='')
...:
(4, 10),(4, 11),(4, 12),(4, 13),(4, 14),(4, 15),(5, 7),(5, 8),(5, 9),
(5, 16),(5, 17),(6, 6),(6, 7),(6, 18),(6, 19),(7, 5),(7, 6),(7, 19),
(8, 5),(8, 20),(9, 5),(9, 20),(10, 4),(10, 20),(11, 4),(11, 20),
(12, 4),(12, 20),(13, 4),(13, 20),(14, 4),(14, 20),(15, 4),(15, 20),
(16, 5),(16, 20),(17, 5),(17, 19),(18, 6),(18, 18),(18, 19),(19, 6),
(19, 7),(19, 17),(19, 18),(20, 8),(20, 9),(20, 10),(20, 11),(20, 12),
(20, 13),(20, 14),(20, 15),(20, 16),(29, 33),(29, 34),(29, 35),
(29, 36),(29, 37),(29, 38),(29, 39),(29, 40),(29, 41),(30, 31),
(30, 32),(30, 42),(30, 43),(31, 30),(31, 31),(31, 43),(32, 30),
(32, 44),(33, 29),(33, 44),(34, 29),(34, 45),(35, 29),(35, 45),
(36, 29),(36, 45),(37, 29),(37, 45),(38, 29),(38, 45),(39, 29),
(39, 45),(40, 29),(40, 44),(41, 29),(41, 44),(42, 30),(42, 43),
(42, 44),(43, 30),(43, 31),(43, 42),(43, 43),(44, 32),(44, 33),
(44, 40),(44, 41),(44, 42),(45, 34),(45, 35),(45, 36),(45, 37),
(45, 38),(45, 39),
Of course of make the scan range too small, you won't find many points.
In [9]: scan2 = np.logical_and(data>0.49, data<0.51)
In [10]: for x in range(a):
for y in range(b):
if scan2[x,y]:
print('({}, {}),'.format(x,y), end='')
....:
(4, 12),(5, 17),(7, 19),(9, 20),(12, 4),(17, 5),(19, 7),(20, 9),
(29, 40),(30, 42),(32, 44),(37, 45),(40, 29),(42, 30),(44, 32),
(45, 37),