So I'm having an issue where I want to plot a simple box, with a wire frame, one panel coloured and spheres on the corners. However, whilst I've managed to do this, the spheres are a source of issue in that they obstruct the wire frame in places (which I like) but not in other places.
Any ideas why this is occurring and how to resolve the matter?
import numpy as NP
import os
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
os.system( 'cls' )
def main( ):
fig = plt.figure( figsize = ( cm2inch( 6 ), cm2inch ( 6 ) ), dpi = 100 )
ax = fig.add_subplot(111, projection='3d')
# Box frame plot
ax.plot( xs = [ 0, 0, 1, 1, 0 ], ys = [ 0, 1, 1, 0, 0 ], zs = [ 0, 0, 0, 0, 0 ], color = 'black', linewidth = 2 ) #<- Base
ax.plot( xs = [ 0, 0, 1, 1, 0 ], ys = [ 0, 1, 1, 0, 0 ], zs = [ 1, 1, 1, 1, 1 ], color = 'black', linewidth = 2 ) #<- Top
ax.plot( xs = [ 0, 0, 1, 1, 0 ], ys = [ 0, 0, 0, 0, 0 ], zs = [ 0, 1, 1, 0, 0 ], color = 'black', linewidth = 2 ) #<- Front
ax.plot( xs = [ 0, 0, 1, 1, 0 ], ys = [ 1, 1, 1, 1, 1 ], zs = [ 0, 1, 1, 0, 0 ], color = 'black', linewidth = 2 ) #<- Back
# Back face plot
xs = [ 0, 0, 1, 1, 0 ]
ys = [ 1, 1, 1, 1, 1 ]
zs = [ 0, 1, 1, 0, 0 ]
verts = [ list( zip( xs, ys, zs ) ) ]
temp = Poly3DCollection( verts, alpha = 0.25 )
temp.set_facecolor( 'blue' )
ax.add_collection3d( temp )
# Corner sphere plots
for x in [ 0, 1 ]:
for y in [ 0, 1 ]:
for z in [ 0, 1 ]:
xs, ys, zs = drawSphere( x, y, z, 0.05 )
ax.plot_surface(xs, ys, zs, color="r", alpha = 1)
#plt.axis('scaled')
plt.axis( 'off' )
plt.savefig( 'Example', pad_inches = 0, transparent = False, dpi = 400 )
plt.show()
plt.close( )
def drawSphere( xCenter, yCenter, zCenter, r ):
#draw sphere
u, v = NP.mgrid[ 0 : 2 * NP.pi : 20j, 0 : NP.pi : 10j ]
x = NP.cos( u ) * NP.sin( v )
y = NP.sin( u ) * NP.sin( v )
z = NP.cos( v )
# shift and scale sphere
x = r * x + xCenter
y = r * y + yCenter
z = r * z + zCenter
return( x, y, z)
def cm2inch( cm ):
inches = cm / 2.54
return inches
if __name__ == '__main__':
main( )
I have the following situation as shown in the Figure below:
I want to find out the grid points that surround the red points. The red points are trajectories of moving agents. So in many situations we have a bunch of points, therefore the solution should be as fast as possible.
The grid is plotted as points.
First step, I managed to reduce the number of grid points as shown below (plotted as x):
This is my code:
step = .5
gridX, gridY = np.meshgrid(np.arange(xmin-step, xmax+step, step), np.arange(ymin-step, ymax+step, step))
mask = False * np.empty_like(gridX, dtype=bool)
threshold = 0.5
for (x,y) in zip(df_traj['X'], df_traj['Y']):
pX = x * np.ones_like(gridX)
pY = y * np.ones_like(gridY)
distX = (pX - gridX)**2
distY = (pY - gridY)**2
dist = np.sqrt(distX + distY)
condition = (dist < threshold)
mask = mask | condition
gX = gridX*mask
gY = gridY*mask
Second step, and this is where I need a little help:
How can I efficiently filter out the inner points of the grid and keep only the "x-points" outside the "red area"?
EDIT
In this special case I have 92450 red points.
I think if you just walk around the edge, since its a evenly spaced grid, it should work. No need for far more complicated non-convex-hull to handle pnts that can be anywhere. This isn't adapted to your code and I cheat with my data structures to make the code easy so youll have to handle that but it think as psuedocode it should work.
pnts = <<lists of points>>
edge_pnts = []
fpnt = pnt_with_min_x_then_min_y
cpnt = fpnt
npnt = None
while npnt != fpnt:
if (cpnt[0] + 1, cpnt[1] ) in pnts: npnt = (cpnt[0] + 1, cpnt[1] )
elif (cpnt[0] + 1, cpnt[1] + 1) in pnts: npnt = (cpnt[0] + 1, cpnt[1] + 1)
elif (cpnt[0], cpnt[1] + 1) in pnts: npnt = (cpnt[0] , cpnt[1] + 1)
elif (cpnt[0] - 1, cpnt[1] + 1) in pnts: npnt = (cpnt[0] - 1, cpnt[1] + 1)
elif (cpnt[0] - 1, cpnt[1] ) in pnts: npnt = (cpnt[0] - 1, cpnt[1] )
elif (cpnt[0] - 1, cpnt[1] - 1) in pnts: npnt = (cpnt[0] - 1, cpnt[1] - 1)
elif (cpnt[0] , cpnt[1] - 1) in pnts: npnt = (cpnt[0] , cpnt[1] - 1)
elif (cpnt[0] + 1, cpnt[1] - 1) in pnts: npnt = (cpnt[0] + 1, cpnt[1] - 1)
else: raise ValueError("Oh no!")
edge_pnts.append(npnt)
cpnt = npnt
For non convex polygons, like your example, convex hull is not a solution. My recommendation is that, given you already have a discrete grid, that you simply attribute the value False to a bool grid cell when a sample occurs inside. Something like this:
import numpy as np
import matplotlib.pyplot as plt
# Generic data production
X, Y = np.random.normal(0, 1, 100000), np.random.normal(0, 1, 100000)
ind = np.where((X > 0) & (Y > 0))
X[ind] = 0
Y[ind] = 0
# Generic grid definition
step = 0.5
xmin, xmax = X.min(), X.max()
ymin, ymax = Y.min(), Y.max()
firstx = xmin-step/2
firsty = ymin-step/2
lastx = xmax+2*step/2
lasty = ymax+2*step/2
gridX, gridY = np.meshgrid(np.arange(firstx, lastx, step), np.arange(firsty, lasty, step))
# This is the actual code that computes inside or outside
bool_grid = np.ones(gridX.shape, dtype="bool")
bool_grid[np.int_(0.5+(Y-firsty)/step), np.int_(0.5+(X-firstx)/step)] = False
# Plot code
plt.scatter(gridX.flatten(), gridY.flatten(), marker="+", color="black", alpha=0.3)
plt.scatter(gridX[bool_grid].flatten(), gridY[bool_grid].flatten(), marker="+", s=90, color="green")
plt.scatter(X, Y, s=10, color="red")
plt.show()
, which results in the following (green crosses are True values):
NOTE: This is very fast but it has some limitations. If your data is not compact you'll have True values inside the shape (so holes are possible). It's possible to process the image to remove the holes however (a flood fill or a moving window based algorithm for example). Another possibility is to play with the resolution of the grid.
You just need to pick a point you know is on the hull (let's take the leftmost point among the topmost points), and assume you "got to it" from above (as we know there is no points above it).
now while the next point is not in your list:
Try going CCW from the direction you came from.
The code looks like that:
matrix = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
# Find the leftmost topmost point
first_point = None
for i in range(len(matrix)):
if first_point:
break
for j in range(len(matrix[0])):
if matrix[i][j]:
first_point = [i, j]
break
next_point = first_point
prev_direction = 'up'
next_direction_dict = {'up': 'left', 'left': 'down', 'down': 'right', 'right': 'up'}
opposite_direction = {'up': 'down', 'left': 'right', 'down': 'up', 'right': 'left'}
hull_points = []
def go_direction(point, direction):
# Find the point to a given direction of a given point
i = point[0]
j = point[1]
if direction == 'right':
j += 1
elif direction == 'up':
i -= 1
elif direction == 'left':
j -= 1
elif direction == 'down':
i += 1
else:
raise ValueError
return [i, j]
def find_next_point(matrix, point, prev_direction):
next_direction = next_direction_dict[prev_direction]
next_point = go_direction(point, next_direction)
prev_direction = next_direction
while not matrix[next_point[0]][next_point[1]]:
next_direction = next_direction_dict[prev_direction]
next_point = go_direction(point, next_direction)
prev_direction = next_direction
from_direction = opposite_direction[prev_direction]
return next_point, from_direction
next_point, prev_direction = find_next_point(matrix, next_point, prev_direction)
while next_point != first_point:
if next_point not in hull_points:
hull_points.append(next_point)
next_point, prev_direction = find_next_point(matrix, next_point, prev_direction)
Edit:
Now also handles single point 'tentacles' by iterating until returning to the first point
Heres another though that came to my mind --
A flood fill of the space:
pnts = <<lists of points>>
seen = set()
edges = []
stack = (0,0)
while stack:
ele = stack.pop()
if ele in pnts:
edges.append(ele)
else:
seen.add(ele)
if (ele[0] + 1, ele[1]) not in seen:
stack.append(ele[0] + 1, ele[1])
if (ele[0] - 1, ele[1]) not in seen:
stack.append(ele[0] - 1, ele[1])
if (ele[0], ele[1] + 1) not in seen:
stack.append(ele[0], ele[1] + 1)
if (ele[0], ele[1] - 1) not in seen:
stack.append(ele[0], ele[1] - 1)
Then you need to sort the points which shouldn't be too hard.
I have an array like so:
a = np.array([0.1, 0.2, 1.0, 1.0, 1.0, 0.9, 0.6, 1.0, 0.0, 1.0])
I'd like to have a running counter of instances of 1.0 that resets when it encounters a 0.0, so the result would be:
[0, 0, 1, 2, 3, 3, 3, 4, 0, 1]
My initial thought was to use something like b = np.cumsum(a[a==1.0]), but I don't know how to (1) modify this to reset at zeros or (2) quite how to structure it so the output array is the same shape as the input array. Any ideas how to do this without iteration?
I think you could do something like
def rcount(a):
without_reset = (a == 1).cumsum()
reset_at = (a == 0)
overcount = np.maximum.accumulate(without_reset * reset_at)
result = without_reset - overcount
return result
which gives me
>>> a = np.array([0.1, 0.2, 1.0, 1.0, 1.0, 0.9, 0.6, 1.0, 0.0, 1.0])
>>> rcount(a)
array([0, 0, 1, 2, 3, 3, 3, 4, 0, 1])
This works because we can use the cumulative maximum to figure out the "overcount":
>>> without_reset * reset_at
array([0, 0, 0, 0, 0, 0, 0, 0, 4, 0])
>>> np.maximum.accumulate(without_reset * reset_at)
array([0, 0, 0, 0, 0, 0, 0, 0, 4, 4])
Sanity testing:
def manual(arr):
out = []
count = 0
for x in arr:
if x == 1:
count += 1
if x == 0:
count = 0
out.append(count)
return out
def test():
for w in [1, 2, 10, 10**4]:
for trial in range(100):
for vals in [0,1],[0,1,2]:
b = np.random.choice(vals, size=w)
assert (rcount(b) == manual(b)).all()
print("hooray!")
and then
>>> test()
hooray!