bilinear interpolation for angles - python-3.x

I have a 2d array of directional data. I need to interpolate over a higher resolution grid however the ready made functions like scipy interp2d, etc don't account for the discontinuity between 0 and 360.
I have code for doing this for a single grid of 4 points (thanks How to perform bilinear interpolation in Python and Rotation Interpolation) however I would like it to accept big data sets at once - just like the interp2d function. How can I encorporate this into the code below in a way which doesn't just loop over all of the data?
Thanks!
def shortest_angle(beg,end,amount):
shortest_angle=((((end - beg) % 360) + 540) % 360) - 180
return shortest_angle*amount
def bilinear_interpolation_rotation(x, y, points):
'''Interpolate (x,y) from values associated with four points.
The four points are a list of four triplets: (x, y, value).
The four points can be in any order. They should form a rectangle.
'''
points = sorted(points) # order points by x, then by y
(x1, y1, q11), (_x1, y2, q12), (x2, _y1, q21), (_x2, _y2, q22) = points
if x1 != _x1 or x2 != _x2 or y1 != _y1 or y2 != _y2:
raise ValueError('points do not form a rectangle')
if not x1 <= x <= x2 or not y1 <= y <= y2:
raise ValueError('(x, y) not within the rectangle')
# interpolate over the x value at each y point
fxy1 = q11 + shortest_angle(q11,q21,((x-x1)/(x2-x1)))
fxy2 = q12 + shortest_angle(q12,q22,((x-x1)/(x2-x1)))
# interpolate over the y values
fxy = fxy1 + shortest_angle(fxy1,fxy2,((y-y1)/(y2-y1)))
return fxy

I'm going to reuse some personal Point and Point3D simplified classes for this example:
Point
class Point:
#Constructors
def __init__(self, x, y):
self.x = x
self.y = y
# Properties
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = float(value)
#property
def y(self):
return self._y
#y.setter
def y(self, value):
self._y = float(value)
# Printing magic methods
def __repr__(self):
return "({p.x},{p.y})".format(p=self)
# Comparison magic methods
def __is_compatible(self, other):
return hasattr(other, 'x') and hasattr(other, 'y')
def __eq__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x == other.x) and (self.y == other.y)
def __ne__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x != other.x) or (self.y != other.y)
def __lt__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y) < (other.x, other.y)
def __le__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y) <= (other.x, other.y)
def __gt__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y) > (other.x, other.y)
def __ge__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y) >= (other.x, other.y)
It represents a 2D point. It has a simple constructor, x and y properties that ensure they always store floats, magic methods for string representation as (x,y) and comparison to make them sortable (sorts by x, then by y). My original class has additional features such as addition and substraction (vector behaviour) magic methods both but they are not needed for this example.
Point3D
class Point3D(Point):
# Constructors
def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
#classmethod
def from2D(cls, p, z):
return cls(p.x, p.y, z)
# Properties
#property
def z(self):
return self._z
#z.setter
def z(self, value):
self._z = (value + 180.0) % 360 - 180
# Printing magic methods
def __repr__(self):
return "({p.x},{p.y},{p.z})".format(p=self)
# Comparison magic methods
def __is_compatible(self, other):
return hasattr(other, 'x') and hasattr(other, 'y') and hasattr(other, 'z')
def __eq__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x == other.x) and (self.y == other.y) and (self.z == other.z)
def __ne__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x != other.x) or (self.y != other.y) or (self.z != other.z)
def __lt__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y, self.z) < (other.x, other.y, other.z)
def __le__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y, self.z) <= (other.x, other.y, other.z)
def __gt__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y, self.z) > (other.x, other.y, other.z)
def __ge__(self, other):
if not self.__is_compatible(other):
return NotImplemented
return (self.x, self.y, self.z) >= (other.x, other.y, other.z)
Same as Point but for 3D points. It also includes an additional constructor classmethod that takes a Point and its z value as arguments.
Linear interpolation
def linear_interpolation(x, *points, extrapolate=False):
# Check there are a minimum of two points
if len(points) < 2:
raise ValueError("Not enought points given for interpolation.")
# Sort the points
points = sorted(points)
# Check that x is the valid interpolation interval
if not extrapolate and (x < points[0].x or x > points[-1].x):
raise ValueError("{} is not in the interpolation interval.".format(x))
# Determine which are the two surrounding interpolation points
if x < points[0].x:
i = 0
elif x > points[-1].x:
i = len(points)-2
else:
i = 0
while points[i+1].x < x:
i += 1
p1, p2 = points[i:i+2]
# Interpolate
return Point(x, p1.y + (p2.y-p1.y) * (x-p1.x) / (p2.x-p1.x))
It takes a first position argument that will determine the x whose y value we want to calculate, and an infinite amount of Point instances from where we want to interpolate. A keyword argument (extrapolate) allows to turn on extrapolation. A Point instance is returned with the requested x and the calculated y values.
Bilinear interpolation
I offer two alternatives, both of them have a similar signature to the previous interpolation function. A Point whose z value we want to calculate, a keyword argument (extrapolate) that turns on extrapolation and return a Point3D instance with the requested and calculated data. The difference between these two approaches are how the values that will be used to interpolate are provided:
Approach 1
The first approach takes a two-levels-deep nested dict. The first level keys represent the x values, the second level keys the y values and the second level values the z values.
def bilinear_interpolation(p, points, extrapolate=False):
x_values = sorted(points.keys())
# Check there are a minimum of two x values
if len(x_values) < 2:
raise ValueError("Not enought points given for interpolation.")
y_values = set()
for value in points.values():
y_values.update(value.keys())
y_values = sorted(y_values)
# Check there are a minimum of two y values
if len(y_values) < 2:
raise ValueError("Not enought points given for interpolation.")
# Check that p is in the valid interval
if not extrapolate and (p.x < x_values[0] or p.x > x_values[-1] or p.y < y_values[0] or p.y > y_values[-1]):
raise ValueError("{} is not in the interpolation interval.".format(p))
# Determine which are the four surrounding interpolation points
if p.x < x_values[0]:
i = 0
elif p.x > x_values[-1]:
i = len(x_values) - 2
else:
i = 0
while x_values[i+1] < p.x:
i += 1
if p.y < y_values[0]:
j = 0
elif p.y > y_values[-1]:
j = len(y_values) - 2
else:
j = 0
while y_values[j+1] < p.y:
j += 1
surroundings = [
Point(x_values[i ], y_values[j ]),
Point(x_values[i ], y_values[j+1]),
Point(x_values[i+1], y_values[j ]),
Point(x_values[i+1], y_values[j+1]),
]
for i, surrounding in enumerate(surroundings):
try:
surroundings[i] = Point3D.from2D(surrounding, points[surrounding.x][surrounding.y])
except KeyError:
raise ValueError("{} is missing in the interpolation grid.".format(surrounding))
p1, p2, p3, p4 = surroundings
# Interpolate
p12 = Point3D(p1.x, p.y, linear_interpolation(p.y, Point(p1.y,p1.z), Point(p2.y,p2.z), extrapolate=True).y)
p34 = Point3D(p3.x, p.y, linear_interpolation(p.y, Point(p3.y,p3.z), Point(p4.y,p4.z), extrapolate=True).y)
return Point3D(p.x, p12.y, linear_interpolation(p.x, Point(p12.x,p12.z), Point(p34.x,p34.z), extrapolate=True).y)
print(bilinear_interpolation(Point(2,3), {1: {2: 5, 4: 6}, 3: {2: 3, 4: 9}}))
Approach 2
The second approach takes an infinite number of Point3D instances.
def bilinear_interpolation(p, *points, extrapolate=False):
# Check there are a minimum of four points
if len(points) < 4:
raise ValueError("Not enought points given for interpolation.")
# Sort the points into a grid
x_values = set()
y_values = set()
for point in sorted(points):
x_values.add(point.x)
y_values.add(point.y)
x_values = sorted(x_values)
y_values = sorted(y_values)
# Check that p is in the valid interval
if not extrapolate and (p.x < x_values[0] or p.x > x_values[-1] or p.y < y_values[0] or p.y > y_values[-1]):
raise ValueError("{} is not in the interpolation interval.".format(p))
# Determine which are the four surrounding interpolation points
if p.x < x_values[0]:
i = 0
elif p.x > x_values[-1]:
i = len(x_values) - 2
else:
i = 0
while x_values[i+1] < p.x:
i += 1
if p.y < y_values[0]:
j = 0
elif p.y > y_values[-1]:
j = len(y_values) - 2
else:
j = 0
while y_values[j+1] < p.y:
j += 1
surroundings = [
Point(x_values[i ], y_values[j ]),
Point(x_values[i ], y_values[j+1]),
Point(x_values[i+1], y_values[j ]),
Point(x_values[i+1], y_values[j+1]),
]
for point in points:
for i, surrounding in enumerate(surroundings):
if point.x == surrounding.x and point.y == surrounding.y:
surroundings[i] = point
for surrounding in surroundings:
if not isinstance(surrounding, Point3D):
raise ValueError("{} is missing in the interpolation grid.".format(surrounding))
p1, p2, p3, p4 = surroundings
# Interpolate
p12 = Point3D(p1.x, p.y, linear_interpolation(p.y, Point(p1.y,p1.z), Point(p2.y,p2.z), extrapolate=True).y)
p34 = Point3D(p3.x, p.y, linear_interpolation(p.y, Point(p3.y,p3.z), Point(p4.y,p4.z), extrapolate=True).y)
return Point3D(p.x, p12.y, linear_interpolation(p.x, Point(p12.x,p12.z), Point(p34.x,p34.z), extrapolate=True).y)
print(bilinear_interpolation(Point(2,3), Point3D(3,2,3), Point3D(1,4,6), Point3D(3,4,9), Point3D(1,2,5)))
You can see from both approaches that they use the previously defined linear_interpoaltion function, and that they always set extrapolation to True as they already raised an exception if it was False and the requested point was outside the provided interval.

Related

Tetris - Right side collision flickering issue

I am a student in high school doing a summative project in tetris. Lately I've been having this problem where for certain positions, when my block collides with the RIGHT side of the screen, it sort of 'flickers'. I can't seem to find the reason why it is doing this, and I don't know if the shape is going out of the grid and coming in and being drawn again, I'm not sure and very confused.
So far I've been able to make random blocks spawn once the first one hits the ground, I've been able to do collision with the left side correctly for all my blocks, some stacking and rotation works as well. I've included the main ideas of how I did my code as a lot of it is repeated, so I included 2 examples of each. (2 different rotations, 2 ways shown of how I drew the block, etc.)
I'm really stuck so if someone could help that would be amazing, Thank you.
import pygame
import colors
import random
class Shape:
def __init__ (self, x, y, shape1, shape2, shape3, shape4, shapeType):
self.shape1 = shape1
self.shape2 = shape2
self.shape3 = shape3
self.shape4 = shape4
self.x = x
self.y = y
self.lasty = 0
self.direction = 0
self.collided = False
self.fc = 0
self.shapeType = shapeType
def draw (self, screen):
self.lasty = self.y
self.fc += 1
if pygame.key.get_pressed()[pygame.K_DOWN] and self.fc >= 5:
self.fc = 0
self.y += 39
elif self.fc >= 90:
self.fc = 0
self.y += 39
if pygame.key.get_pressed()[pygame.K_RIGHT] and self.fc >= 5:
self.fc = 0
self.x += 39
elif self.fc >= 90:
self.fc = 0
self.x += 39
## When the block collides with the bottom of the grid
if self.y >= 778:
self.y = 0
## I USE 'DIRECTION' TO REPRESENT THE DIFFERENT ROTATED POSITIONS -- ONLY 1 IS SHOWN BELOW BUT I HAVE (0-3 DIRECTIONS) = 4 DIFFERENT ROTATIONS
if self.direction == 0: # if the shape is in the first position
for y in range(len(self.shape1)): # RUNS A LOOP THAT GETS THE LENGTH OF THE SHAPE IN THE FIRST POSITION
for x in range(len(self.shape1[y])):
if self.shape1[y][x] == 1 and not self.collided:
if (self.y + 39*y) + 39 > 780: # CHECKS THAT THE SHAPE IS NOT GOING PASSED THE RIGHT SIDE OF THE GRID
self.collided = True
if (self.x + (39*x)) >= 739:
self.x = 740-(39*x)-39
for blok in blocks: ## stacking the blocks and checking if the block collides with the other blocks
if self.x + (39*x) == blok.x and self.y + (39*y) == blok.y:
self.collided = True
pygame.draw.rect(screen, colors.lightBlue,(self.x + (39*x), self.y + (39*y), 39,39))
elif self.direction == 1: # WHEN THE SHAPE IS IN THE SECOND ROTATION
for y in range(len(self.shape2)):
for x in range(len(self.shape2[y])):
if self.shape2[y][x] == 1 and not self.collided:
if(self.y + 39*y) + 39 > 780:
self.collided = True
if (self.x + (39*x)) >= 739:
self.x = 740-(39*x)-39
for blok in blocks:
if self.x + (39*x) == blok.x and self.y + (39*y) == blok.y:
self.collided = True
pygame.draw.rect(screen, colors.lightBlue,(self.x + (39*x), self.y + (39*y), 39,39))
if self.collided == True:
self.y = self.lasty
if self.direction == 0:
for y in range(len(self.shape1)):
for x in range(len(self.shape1[y])):
if self.shape1[y][x] == 1:
blocks.append(Block(self.x + (39 * x), self.y + (39 * y)))
elif self.direction == 1:
for y in range(len(self.shape2)):
for x in range(len(self.shape2[y])):
if self.shape2[y][x] == 1:
blocks.append(Block(self.x + (39 * x), self.y + (39 * y)))
class Block:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self, screen):
pygame.draw.rect(screen, colors.lightBlue, (self.x, self.y, 39, 39))
blocks = []
## EXAMPLE OF HOW EACH SHAPE IS DRAWN
## L SHAPE
b = Shape(350,0, [[1,1],[0,1],[0,1]], [[0,0,1],[1,1,1]], [[1,0,],[1,0], [1,1]], [[1,1,1],[1,0,0]],"L SHAPE")
## Z SHAPE
##b = Shape(300,300, [[0,1],[1,1],[1,0]], [[1,1],[0,1,1]], [[0,1],[1,1],[1,0]], [[1,1],[0,1,1]])
# FUNCTION FOR THE GRID
def drawGrid(_x, _y, screen,width,height, columns,rows ):
for y in range(0, rows+1):
for x in range(0, columns+1):
screen.fill(colors.grey, [(x*(width/columns))+_x, _y, 1, height])
screen.fill(colors.grey, [_x, (y*(height/rows))+_y, width, 1])
def drawGame(screen, scene): # UPDATING DRAW FUNCTION
global b
for evn in pygame.event.get():
if evn.type == pygame.QUIT:
quit()
return scene, True
elif evn.type == pygame.KEYDOWN:
if evn.key == pygame.K_UP: # IF THE UP ARROW KEY IS PRESSED, CHANGE THE ROTATION OF THE SHAPE
b.direction += 1
if b.direction == 4: # IF THE DIRECTION EQUALS 4 RESET TO THE FIRST POSITION
b.direction = 0
screen.fill(colors.black)
## ACCELERATES THE BLOCK DOWNWARDS
drawGrid(350, 0, screen, 390, 780, 10, 20)
if not b.collided:
b.draw(screen)
else: # CHECKS WHICH VALUE OUT OF (0-6) IS RECEIVED TO CHOOSE THE RANDOM SHAPE
i = random.randint(0,6)
if i == 0:
## L shape
b = Shape(350,0, [[1,1],[0,1],[0,1]], [[0,0,1],[1,1,1]], [[1,0,],[1,0], [1,1]], [[1,1,1],[1,0,0]],"L SHAPE")
elif i == 5:
## Z Shape
b = Shape(350, 0, [[0, 1], [1, 1], [1, 0]], [[1, 1], [0, 1, 1]], [[0, 1], [1, 1], [1, 0]],
[[1, 1], [0, 1, 1]],"Z SHAPE")
for i in blocks: # RUNS THE LOOP TO DRAW THE SHAPE
i.draw(screen) # DRAWS THE VALUE OF 'i' ONTO THE SCREEN
pygame.display.flip()
return scene, False
As it turns out, I was drawing some of the rectangles in the draw loop before some of them were checked for collision with the wall.

python3 AttributeError: 'list' object has no attribute 'dot'

#unit vector
def normalized(self):
try:
unit = [ x/self.magnitude() for x in self.coordinates ]
return unit
except ZeroDivisionError:
raise Exception("Can not normalize the zero vector")
#dot product of vector
def dot(self, v):
x = [ x*y for x,y in zip(self.coordinates, v.coordinates)]
return sum(x)
#radians and angle of vector
def angle_with(self, v , degrees = False):
try:
u1 = self.normalized()
u2 = v.normalized()
print(u1, u2)
angle_in_radians = math.acos(u1.dot(u2))
if degrees:
degrees_per_radian = 180. / math.pi
return angle_in_radians * degrees_per_radian
else:
return angle_in_radians
How can I use "dot" function in here?
Try this
class Vector(object):
def __init__(self, coordinates):
self.coordinates = tuple(coordinates)
self.dimension = len(coordinates)
def normalized(self):
try:
a = 1.0/self.magnitude()
res = [a*x for x in self.coordinates]
return res
except ZeroDivisionError:
raise Exception('your error messsage')
def dot(self, v):
return sum([x * y for x,y in zip(self.coordinates,v.coordinates)])
def angle_with(self, v, in_degree=False):
try:
u1 = Vector(self.normalized())
u2 = Vector(v.normalized())
angle_in_redians = math.acos(u1.dot(u2))
if in_degree:
degrees_per_redian = 180. /pi
return angle_in_redians * degrees_per_redian
else:
return angle_in_redians
OR
#same Vector class and init method
def magnitude(self):
squared_coordinates = [x**2 for x in self.coordinates]
return sqrt(sum(squared_coordinates))
def dot(self, v):
return sum([x * y for x,y in zip(self.coordinates,v.coordinates)])
def angle_with(self, v, in_degree=False):
try:
dot = self.dot(v)
magnitude_v1 = self.magnitude()
magnitude_v2 = v.magnitude()
res = dot/(magnitude_v1*magnitude_v2)
angle_in_redians = math.acos(res)
if in_degree:
degrees_per_redian = 180. /pi
return angle_in_redians * degrees_per_redian
else:
return angle_in_redians

AttributeError: 'float' object has no attribute 'get_coords'

I'm learning Python from this lecture: Lec 19 | MIT 6.00 Introduction to Computer Science and Programming. I'm using Python 3.6.2, lecture example runs on Python 2.x. Whats the proper way to set values of x and y in function ans_quest?
x, y = loc_list[-1].get_coords()
Can this method be called like this? This was the example in the lecture.
Full code:
import math, random, pylab, copy
class Location(object):
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def move(self, xc, yc):
return Location(self.x+float(xc), self.y+float(yc))
def get_coords(self):
return self.x, self.y
def get_dist(self, other):
ox, oy = other.get_coords()
x_dist = self.x - ox
y_dist = self.y - oy
return math.sqrt(x_dist**2 + y_dist**2)
class Compass_Pt(object):
possibles = ('N', 'S', 'E', 'W')
def __init__(self, pt):
if pt in self.possibles: self.pt = pt
else: raise ValueError('in Compass_Pt.__init__')
def move(self, dist):
if self.pt == 'N': return (0, dist)
elif self.pt == 'S': return (0, -dist)
elif self.pt == 'E': return (dist, 0)
elif self.pt == 'W': return (-dist, 0)
else: raise ValueError('in Compass_Pt.move')
class Field(object):
''' Cartesian plane where object will be located '''
def __init__(self, drunk, loc):
self.drunk = drunk
self.loc = loc
def move(self, cp, dist):
old_loc = self.loc
xc, yc = cp.move(dist)
self.loc = old_loc.move(xc, yc)
def get_loc(self):
return self.loc
def get_drunk(self):
return self.drunk
class Drunk(object):
''' Point itself '''
def __init__(self, name):
self.name = name
def move(self, field, cp, dist = 1):
if field.get_drunk().name != self.name:
raise ValueError('Drunk.move called with drunk not in the field')
for i in range(dist):
field.move(cp, 1)
class Usual_Drunk(Drunk):
def move(self, field, dist = 1):
''' Drunk.move superclass method override. Sends additional cp attribute.'''
cp = random.choice(Compass_Pt.possibles)
Drunk.move(self, field, Compass_Pt(cp), dist)
class Cold_Drunk(Drunk):
def move(self, field, dist = 1):
cp = random.choice(Compass_Pt.possibles)
if cp == 'S':
Drunk.move(self, field, Compass_Pt(cp), 2*dist)
else:
Drunk.move(self, field, Compass_Pt(cp), dist)
class EW_Drunk(Drunk):
def move(self, field, time = 1):
cp = random.choice(Compass_Pt.possibles)
while cp != 'E' and cp != 'W':
cp = random.choice(Compass_Pt.possibles)
Drunk.move(self, field, Compass_Pt(cp), time)
def perform_trial(time, f):
start = f.get_loc()
distances = [0,0]
for t in range(1, time + 1):
f.get_drunk().move(f)
new_loc = f.get_loc()
distance = new_loc.get_dist(start)
distances.append(distance)
return distances
def perform_sim(time, num_trials, drunk_type):
dist_lists = []
loc_lists = []
for trial in range(num_trials):
d = drunk_type('Drunk' + str(trial))
f = Field(d, Location(0, 0))
distances = perform_trial(time, f)
locs = copy.deepcopy(distances)
dist_lists.append(distances)
loc_lists.append(locs)
return dist_lists, loc_lists
def ans_quest(max_time, num_trials, drunk_type, title):
dist_lists, loc_lists = perform_sim(max_time, num_trials, drunk_type)
means = []
for t in range(max_time + 1):
tot = 0.0
for dist_l in dist_lists:
tot += dist_l[t]
means.append(tot/len(dist_lists))
pylab.figure()
pylab.plot(means)
pylab.ylabel('distance')
pylab.xlabel('time')
pylab.title('{} Ave. Distance'.format(title))
lastX = []
lastY = []
for loc_list in loc_lists:
x, y = loc_list[-1].get_coords()
lastX.append(x)
lastY.append(y)
pylab.figure()
pylab.scatter(lastX, lastY)
pylab.ylabel('NW Distance')
pylab.title('{} Final location'.format(title))
pylab.figure()
pylab.hist(lastX)
pylab.xlabel('EW Value')
pylab.ylabel('Number of Trials')
pylab.title('{} Distribution of Final EW Values'.format(title))
num_steps = 50
num_trials = 10
ans_quest(num_steps, num_trials, Usual_Drunk, 'Usual Drunk ' + str(num_trials) + ' Trials')
ans_quest(num_steps, num_trials, Cold_Drunk, 'Cold Drunk ' + str(num_trials) + ' Trials')
ans_quest(num_steps, num_trials, EW_Drunk, 'EW Drunk ' + str(num_trials) + ' Trials')
pylab.show()
Error:
Traceback (most recent call last):
File "/home/tihe/Documents/CODING/Project Home/Python/biased_random_walks.py", line 194, in <module>
ans_quest(num_steps, num_trials, Usual_Drunk, 'Usual Drunk ' + str(num_trials) + ' Trials')
File "/home/tihe/Documents/CODING/Project Home/Python/biased_random_walks.py", line 175, in ans_quest
x, y = loc_list[-1].get_coords()
AttributeError: 'float' object has no attribute 'get_coords'
This method could be called like this if you had a list of Location objects. The error is because the loc_list is populated with distances and not Location objects. That happens in function perform_sim when instead of geting the location you are making a deep copy of distance.
Perhaps you could try something like this:
def perform_trial(time, f):
start = f.get_loc()
distances = [0,0]
locations = []
for t in range(1, time + 1):
f.get_drunk().move(f)
new_loc = f.get_loc()
locations.append(new_loc)
distance = new_loc.get_dist(start)
distances.append(distance)
return distances, locations
def perform_sim(time, num_trials, drunk_type):
dist_lists = []
loc_lists = []
for trial in range(num_trials):
d = drunk_type('Drunk' + str(trial))
f = Field(d, Location(0, 0))
distances, locations = perform_trial(time, f)
dist_lists.append(distances)
loc_lists.append(locations)
return dist_lists, loc_lists
I hope that helped you out.

Updating quiver 3D points in matplotlib

I am trying to use matplotlib to graph a 3D tree. I want a user to be able to drag each node around so that they can view the tree in any manner they choose. My plan is to eventually subclass in order to add data to the nodes and create the tree.
I am having trouble updating the graph while they are dragging the nodes around. I found a way to update the position of the scatter points using:
self.plot.set_offsets([x,y])
self.plot.set_3d_properties([z], 'z')
But cannot seem to find a way of also updating the position and vector of the quivers.
I am also having difficulties with retrieving the mouse position which currently I always find:
ecoor == {'elevation': 30.0, 'azimuth': -60.0}
EDIT: Thanks to ImportanceOfBeingErnest instead of trying to update the plot I removed it and made a new one.
# PlotNode._plot changes
self.plot.remove()
self.plot = axes.scatter([x], [y], [z])
# PlotInterNode._plot changes (both vector updates)
arlen=length(xc - x, yc - y, zc - z)
edge.remove()
edge = axes.quiver(x, y, z, xc - x, yc - y, zc - z, length=arlen,
arrow_length_ratio=.5/arlen, pivot='tail')
self.edges.update({childname:(childnode, edge)})
Here is the original code:
import math
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
class PlotNode(object):
"""
Class for graph node.
"""
def __init__(self, name, parent):
"""
Initializes PlotNode.
"""
self.__name__ = name
self.parent = parent
self.coordinates = self._make_coordinates()
x, y, z = self.coordinates
self.plot = axes.scatter([x], [y], [z])
self.pressed = False
self.move = False
def name(self):
return self.__name__
COOR = (0, 0, 0)
def _make_coordinates(self):
"""
Finds coordinates from a file or, if not exist, calculate new coordinates.
"""
if PlotNode.COOR[1] == PlotNode.COOR[0]:
PlotNode.COOR = (PlotNode.COOR[0] + 1, 0, 0)
elif PlotNode.COOR[2] == PlotNode.COOR[1]:
PlotNode.COOR = (PlotNode.COOR[0], PlotNode.COOR[1] + 1, 0)
else:
PlotNode.COOR = (PlotNode.COOR[0], PlotNode.COOR[1], PlotNode.COOR[2] + 1)
return (PlotNode.COOR[0], PlotNode.COOR[1], PlotNode.COOR[2])
def _plot(self):
"""
Plots node onto graph.
"""
x, y, z = self.coordinates
#updates the plot coordinates
self.plot.set_offsets([x,y])
self.plot.set_3d_properties([z], 'z')
#updates the parent
if self.parent:
self.parent._plot(self.name())
self.plot.figure.canvas.draw()
def press(self, event):
"""
Mouse press event.
"""
if event.inaxes != self.plot.axes or not self.plot.contains(event)[0]:
return False
self.pressed = True
axes.disable_mouse_rotation() #Make sure node moves instead of plot rotation
return True
def release(self, event):
"""
Mouse release event.
"""
if event.inaxes != self.plot.axes or not self.pressed:
return False
self.pressed = False
if self.move:
self.move = False
x, y, z = self.coordinates
ecoor = to_dict(axes.format_coord(event.xdata, event.ydata))
xe, ye, ze = ecoor.get('x', x), ecoor.get('y', y), ecoor.get('z', z)
self.coordinates = (xe, ye, ze)
self._plot()
else:
self.open()
axes.mouse_init() #Make plot rotation avaliable again
return True
def motion(self, event):
"""
Mouse motion event.
"""
if event.inaxes != self.plot.axes or not self.plot.contains(event)[0]:
return False
if not self.pressed:
return False
self.move = True
x, y, z = self.coordinates
ecoor = to_dict(axes.format_coord(event.xdata, event.ydata))
xe, ye, ze = ecoor.get('x', x), ecoor.get('y', y), ecoor.get('z', z)
self.coordinates = (xe, ye, ze)
self._plot()
return True
def open(self):
print('openned!') #to be changed
class PlotInterNode(PlotNode):
"""
Class for graph folder node.
"""
def __init__(self, name, parent=None):
"""
Initializes PlotDir.
"""
self.edges = {}
PlotNode.__init__(self, name, parent)
def _plot(self, childname=None):
"""
Plots node onto graph.
"""
if childname:
x, y, z = self.coordinates
childnode, edge = self.edges.get(childname)
xc, yc, zc = childnode.coordinates
##update the vector
arlen=length(xc - x, yc - y, zc - z)
##update the arrow length
else:
x, y, z = self.coordinates
for childname in self.edges:
_, edge = self.edges.get(childname)
##update the position of each edge
super()._plot()
self.plot.figure.canvas.draw()
def traverse(self):
"""
Generator that traverses the tree rooted at this node.
"""
yield self
for child in self.edges:
try:
for node in self.edges.get(child)[0].traverse():
yield node
except AttributeError:
yield self.edges.get(child)[0]
def select_node(root, event):
"""
Single event function to handle all node movement.
"""
if event.name == 'button_press_event':
event_fn = lambda self: PlotNode.press(self, event)
elif event.name == 'button_release_event':
event_fn = lambda self: PlotNode.release(self, event)
elif event.name == 'motion_notify_event':
event_fn = lambda self: PlotNode.motion(self, event)
for node in root.traverse():
if event_fn(node):
return #if act on node then end
select_ids = []
def connect_select(root):
"""
Connects select_node to events.
"""
select_ids.append(figure.canvas.mpl_connect('button_press_event', lambda event: select_node(root, event)))
select_ids.append(figure.canvas.mpl_connect('button_release_event', lambda event: select_node(root, event)))
select_ids.append(figure.canvas.mpl_connect('motion_notify_event', lambda event: select_node(root, event)))
def to_dict(string):
"""
Converts a string to a dictionary.
"""
dictionary = {}
for st in string.split(','):
st = st.strip().split('=')
if st[0] not in dictionary:
try:
dictionary.update({st[0]:float(st[1])})
except ValueError:
st[1] = st[1].split(' ')[0]
dictionary.update({st[0]:float(st[1])})
return dictionary
def length(x, y, z):
"""
Returns hypotenuse.
"""
ret = math.sqrt(math.pow(x, 2) + math.pow(y, 2) + math.pow(z, 2))
if not ret:
ret = 1
return ret
figure = pyplot.figure()
axes = figure.add_subplot(111, projection='3d')
root = PlotInterNode('root')
def make_children(node, child_type, number):
x, y, z = node.coordinates
for i in range(number):
child = child_type(str(i), node)
xc, yc, zc = child.coordinates
arlen = length(xc - x, yc - y, zc - z)
edge = axes.quiver(x, y, z, xc - x, yc - y, zc - z, length=arlen, arrow_length_ratio=.5/arlen, pivot='tail')
node.edges.update({child.name():(child, edge)})
def node_depth(node, depth):
if not depth:
make_children(node, PlotNode, 3)
else:
make_children(node, PlotInterNode, 3)
for child in node.edges:
node_depth(node.edges.get(child)[0], depth-1)
node_depth(root, 3)
connect_select(root)
pyplot.show()
EDIT: All I needed to retrieve the mouse position in 3D was add
axes.button_pressed = None
before I called
axes.format_coord(event.xdata, event.ydata)
Thanks to ImportanceOfBeingErnest instead of trying to update the plot I removed it and made a new one.
# PlotNode._plot changes
self.plot.remove()
self.plot = axes.scatter([x], [y], [z])
# PlotInterNode._plot changes (both vector updates)
arlen=length(xc - x, yc - y, zc - z)
edge.remove()
edge = axes.quiver(x, y, z, xc - x, yc - y, zc - z, length=arlen,
arrow_length_ratio=.5/arlen, pivot='tail')
self.edges.update({childname:(childnode, edge)})
For the second part:
All I needed to retrieve the mouse position in 3D was add
axes.button_pressed = None
before I called
axes.format_coord(event.xdata, event.ydata)

Collision between a rect and a circle

The problem is next, I have falling circles and I need them to be deleted if they will overlap with the player. I have tried to create a bunch of methods to get coordinates of the circles and the rectangle however when i try checking if they overlap i get an error.
TypeError: unorderable types: method() > method()
Here is the code:
# Colour
# Created by Niktia Kotter
#!/usr/bin/env python
import pygame, sys, random, time
from pygame.locals import*
# set up pygame
pygame.init()
FPS=60
fpsclock = pygame.time.Clock()
# colours R G B
WHITE = (255, 255, 255)
BLACK = (0 , 0 , 0 )
RED = (237, 28 , 36 )
# set up screen
SCREEN_W = 800
SCREEN_H = 480
SCREEN = pygame.display.set_mode((SCREEN_W,SCREEN_H),0,32)
snapMultX = SCREEN_W / 5
snapMultY = SCREEN_H / 5
basicFont = pygame.font.SysFont(None, 32)
# set up functions
def isPointInsideRect(Cx, Cy, rectX, rectY, rectW, rectH ):
if ((Cx > rectX) and \
(Cx < rectY) and \
(Cy > rectW) and \
(Cy < rectH)):
return True
else:
return False
"""
def doRectsOverlap(rect1, rect2):
for a,b in [(rect1, rect2), (rect2, rect1)]:
# check if a's corners are inside b
if ((isPointInsideRect(a.left, a.top, b)) or
(isPointInsideRect(a.left, a.bottom, b)) or
(isPointInsideRect(a.right, a.top, b)) or
(isPointInsideRect(a.right, a.bottom, b))):
return True
return False
"""
# set up calsses
class Actor:
def __init__ (self):
self._x = snapMultX*2
self._y = SCREEN_H - snapMultX/5 -(snapMultX/2)
self._w = snapMultX
self._h = snapMultX/2
self._colour = WHITE
self._Rect = pygame.Rect(self._x, self._y, self._w, self._h)
def moveRight(self):
self._x += snapMultX
def moveLeft(self):
self._x -= snapMultX
def draw(self):
pygame.draw.rect(SCREEN, self._colour, (self._x, self._y, self._w, self._h))
return
def rectX(self):
return self._x
def rectY(self):
return self._y
def rectW(self):
return self._w
def rectH(self):
return self._h
class Enemy:
def __init__ (self, location):
self._x = snapMultX*location+snapMultX/2
self._y = 0
self._r = snapMultX/10
self._colour = WHITE
def move(self, dy):
self._y += dy
def draw(self):
pygame.draw.circle(SCREEN, self._colour, (int(self._x),int(self._y)), int(self._r), 0)
return
def GetCircleX(self):
return self._x
def GetCircleY(self):
return self._y
class Capture(object):
def __init__(self):
self.caption = pygame.display.set_caption('Space Invaders')
self.screen = SCREEN
self.startGame = True
self.gameOver = False
self.enemyCount = 0
self.timer = 50
self.score = 0
def main(self):
clock = pygame.time.Clock()
enemy =[]
player = Actor()
while True:
if self.startGame:
SCREEN.fill(BLACK)
pygame.draw.polygon(SCREEN,WHITE, [(snapMultX*1-snapMultX/5*2,0), (snapMultX*0+snapMultX/5*2,0), (snapMultX*0+snapMultX/2,snapMultY/4)])
pygame.draw.polygon(SCREEN,WHITE, [(snapMultX*2-snapMultX/5*2,0), (snapMultX*1+snapMultX/5*2,0), (snapMultX*1+snapMultX/2,snapMultY/4)])
pygame.draw.polygon(SCREEN,WHITE, [(snapMultX*3-snapMultX/5*2,0), (snapMultX*2+snapMultX/5*2,0), (snapMultX*2+snapMultX/2,snapMultY/4)])
pygame.draw.polygon(SCREEN,WHITE, [(snapMultX*4-snapMultX/5*2,0), (snapMultX*3+snapMultX/5*2,0), (snapMultX*3+snapMultX/2,snapMultY/4)])
pygame.draw.polygon(SCREEN,WHITE, [(snapMultX*5-snapMultX/5*2,0), (snapMultX*4+snapMultX/5*2,0), (snapMultX*4+snapMultX/2,snapMultY/4)])
player.draw()
# enemy move/spawn timer
self.timer -= 1
# enemy spawner
if self.timer <= 0:
num = random.randint(0, 5)
if num == 0:
print (0)
enemy.append(Enemy(0))
if num == 1:
print (1)
enemy.append(Enemy(1))
if num == 2:
print (2)
enemy.append(Enemy(2))
if num == 3:
print (3)
enemy.append(Enemy(3))
if num == 4:
print (4)
enemy.append(Enemy(4))
# player mover
for event in pygame.event.get():
if player._x != snapMultX*4 and (event.type == KEYDOWN) and (event.key == K_d):
player.moveRight()
if player._x != 0 and(event.type == KEYDOWN) and (event.key == K_a):
player.moveLeft()
if event.type == QUIT:
pygame.quit()
sys.exit()
# enemy logic
if self.timer <= 0:
for e in enemy:
e.move(snapMultY)
if isPointInsideRect(e.GetCircleX, e.GetCircleY, player.rectX, player.rectY, player.rectW, player.rectH):
self.score += 1
enemy.remove(e)
if e._y > snapMultY*5:
enemy.remove(e)
# reste timer
self.timer = 50
for e in enemy:
e.draw()
# score
self.myScore = "Score = " + str(self.score)
text = basicFont.render(self.myScore, True, RED, WHITE)
textRect = text.get_rect()
textRect.centerx = SCREEN.get_rect().centerx
textRect.centery = SCREEN.get_rect().centery
SCREEN.blit(text, textRect)
pygame.display.update()
fpsclock.tick(FPS)
if __name__ == '__main__':
game = Capture()
game.main()
The cause of your error is actually a typo and the joys of Python Duck Typing. The key to debugging is understanding what the error means.
Your error "TypeError: unorderable types: method() > method()". What does this mean?
Obviously you have a type error, but what does that really mean. It means that Python is trying to complete an operation that has certain requirements. In this case in has do to with unorderable types - we are trying to do a comparison on two things that don't have an orderable property so that they can't be compared in that way. The next portion says "method() > method()". That means we are attempting to compare that one method is greater than another. That probably isn't what is intended.
So now we have to look at those comparisons in our ifs. Lets look at isPointInsideRect.
def isPointInsideRect(Cx, Cy, rectX, rectY, rectW, rectH ):
if ((Cx > rectX) and (Cx < rectY) and (Cy > rectW) and (Cy < rectH)):
return True
else:
return False
Here we are doing a whole bunch of comparisons on six values. Lets look at where this method gets called? This function gets called in one uncommented-out line (Line 175).
if isPointInsideRect(e.GetCircleX, e.GetCircleY, player.rectX, player.rectY, player.rectW, player.rectH):
Do you see the issue here? Each of those values that you are passing into the function aren't values, they are method definitions. Hence, everytime isPointInsideRect is doing those comparisons, they are comparing one method to another. And that isn't a valid comparison.
Trying changing line 175 to:
if isPointInsideRect(e.GetCircleX(), e.GetCircleY(), player.rectX(), player.rectY(), player.rectW(), player.rectH()):

Resources