In python, I have written a 3D rendering program. The Y rotation works fine, but the X rotation zooms in for some obscure reason. I couldn't spot it, so I put it up here.
def plotLine(W, H, (x, y, z), (x2, y2, z2), rotX, rotY, FOV=1.0):
try:
x = float(x)
y = float(y)
z = float(z)
x2 = float(x2)
y2 = float(y2)
z2 = float(z2)
if z == 0:
z = 0.01
if z2 == 0:
z2 = 0.01
x, y, z = rotateY((x, y, z), rotY)
x, y, z = rotateX((x, y, z), rotX)
x2, y2, z2 = rotateY((x2, y2, z2), rotY)
x2, y2, z2 = rotateX((x2, y2, z2), rotX)
scX = (x/z)*FOV
scY = (y/z)*FOV
scX *= min(W, H)
scY *= min(W, H)
scX += W/2
scY += H/2
scX2 = (x2/z2)*FOV
scY2 = (y2/z2)*FOV
scX2 *= min(W, H)
scY2 *= min(W, H)
scX2 += W/2
scY2 += H/2
pygame.draw.aaline(display, (0, 255, 0), (scX, scY), (scX2, scY2))
except (OverflowError, ZeroDivisionError):
return
def rotateY((x, y, z), degrees): # Looking left and right.
x, y, z = float(x), float(y), float(z)
rads = math.radians(degrees)
newX = (math.cos(rads)*x)+(math.sin(rads)*z)
newY = y
newZ = (-math.sin(rads)*x)+(math.cos(rads)*z)
return (newX, newY, newZ)
def rotateX((x, y, z), degrees):
x, y, z = float(x), float(y), float(z)
rads = math.radians(degrees)
newX = x
newY = (math.cos(rads)*y)+(math.sin(rads)*z)
newZ = (math.sin(rads)*y)+(math.cos(rads)*z)
return (newX, newY, newZ)
Any help would be appreciated!
BTW, I have looked up the matrix rotations on Wikipedia. Either Wikipedia got the matrices wrong, or I multiplied the matrices wrong, which is not likely. I have looked over them several times.
I think you have an error in your rotateX function
newY = (math.cos(rads)*y)+(math.sin(rads)*z)
newZ = (math.sin(rads)*y)+(math.cos(rads)*z)
should be
newY = (math.cos(rads)*y)+(math.sin(rads)*z)
newZ = (-math.sin(rads)*y)+(math.cos(rads)*z)
^
^
without the negative sign you will not get a rotation. You have done this correctly in your rotateY function but not the rotateX function.
If you look at the 2D submatrix of your currently coded 3D rotation you have
[cos(rads) sin(rads)]
[sin(rads) cos(rads)]
and the determinant of this is
1/(cos(rads)*cos(rads) - sin(rads)sin(rads))
= 1/cos(2*rads)
This is not equal to 1 for all angles rads and hence is not a rotation for all values of rads.
Note also that this rotation angle would be in the negative sense to what is usually associated with a rotation. You can see more information about this here on wikepedia
Related
So I'm new to Python and I'd like to convert a 3D-array containing cartesian coordinates to spherical coordinates. I have done this function that calculates the conversion:
def cart2sph(x, y, z):
xy = np.sqrt(x**2 + y**2) # sqrt(x² + y²)
x_2 = x**2
y_2 = y**2
z_2 = z**2
r = np.sqrt(x_2 + y_2 + z_2) # r = sqrt(x² + y² + z²)
theta = np.arctan2(y, x)
phi = np.arctan2(xy, z)
return r, theta, phi
However, if I have a random array (N,N,N), such as
N = 3
array = np.random.rand(N, N, N).astype(dtype=np.float16)
And pass the x, y and z coordinates to my function to convert from cartesian to spherical
x = np.asarray(array_np)[:,0].astype(dtype=np.float16)
y = np.asarray(array_np)[:,1].astype(dtype=np.float16)
z = np.asarray(array_np)[:,2].astype(dtype=np.float16)
sphere_coord = cart2sph(x,y,z)
I keep getting wrong conversion results. I've tried different approaches but still couldn't figure out what I am doing wrong.
I have checked the function with a unique (x, y, z) and it seems to be converting to (r, theta, phi) just fine.
I think your problem is on how are you getting the random (x, y, z). Maybe try something like this:
import numpy as np
def cart2sph(x, y, z):
xy = np.sqrt(x**2 + y**2) # sqrt(x² + y²)
x_2 = x**2
y_2 = y**2
z_2 = z**2
r = np.sqrt(x_2 + y_2 + z_2) # r = sqrt(x² + y² + z²)
theta = np.arctan2(y, x)
phi = np.arctan2(xy, z)
return r, theta, phi
N = 3
array_np = np.random.rand(N).astype(dtype=np.float16)
print('array_np:')
print(array_np)
x = np.asarray(array_np)[0].astype(dtype=np.float16)
y = np.asarray(array_np)[1].astype(dtype=np.float16)
z = np.asarray(array_np)[2].astype(dtype=np.float16)
sphere_coord = cart2sph(x,y,z)
print('\nCartesian:')
print('x',x,'\ny',y,'\nz',z)
print('\nSpherical:')
print(sphere_coord)
Output:
array_np: [0.2864 0.938 0.9243]
Cartesian: x 0.2864 y 0.938 z 0.9243
Spherical: (1.3476626409849026, 1.274, 0.8150028593437515)
I am having trouble creating a method that can create a FILLED IN circle within a 2d array of pixels. So far, the Image class I have created can create a 2d array of pixels, change individual pixel values, etc. What I have been able to complete so far is use Bresenham's circle algorithm to create a hollow circle of any radius around any given point in the array. I cannot, however, figure out how to make this circle filled in.
I am open to any solution! I have tried creating a "flood fill" method, only to be greeted by various recursion errors. I have also tried just calling the circleBres method several times with decrementing radii, but this does not work either. For the sake of space, just assume that the writePixel method works.
class Image:
def drawCircle(self, centerX, centerY, x, y):
self.writePixel(centerX + x, centerY + y, 50.0)
self.writePixel(centerX - x, centerY + y, 50.0)
self.writePixel(centerX + x, centerY - y, 50.0)
self.writePixel(centerX - x, centerY - y, 50.0)
self.writePixel(centerX + y, centerY + x, 50.0)
self.writePixel(centerX - y, centerY + x, 50.0)
self.writePixel(centerX + y, centerY - x, 50.0)
self.writePixel(centerX - y, centerY - x, 50.0)
def circleBres(self, xc, yc, r):
x = 0
y = r
d = 3 - (2*r)
self.drawCircle(xc, yc, x, y)
while(y>=x):
x+=1
if(d>0):
y-=1
d = d+4*(x-y)+10
else:
d = d + 4 * x + 6
self.drawCircle(xc, yc, x, y)
time.sleep(.06)
obj = Image(50, 50, 51.0)
obj.circleBres(35, 35, 10)
The Image constructor's third parameter is the value that all pixels are assigned upon creation (51.0), and the third parameter of the writePixel method is the value that the pixel is being changed to (50.0).
Any help is greatly appreciated. Thanks!
When you write
self.writePixel(centerX + x, centerY + y, 50.0)
self.writePixel(centerX - x, centerY + y, 50.0)
you draw the leftmost and the rightmost pixels of some scanline.
To fill it, just make for loops instead of these 8 lines:
for xx in range(centerX - x, centerX + x + 1):
self.writePixel(xx, centerY + y, 50.0)
self.writePixel(xx, centerY - y, 50.0)
for xx in range(centerX - y, centerX + y):
self.writePixel(centerX + y, centerY + x, 50.0)
self.writePixel(centerX - y, centerY - x, 50.0)
Here's an example of how I would handle a problem like this:
Assuming you have a correct outline of circle, just iterate through the rows and fill all pixels between edges of the circle.
grid = [
[0,0,0,1,1,1,0,0,0],
[0,0,1,0,0,0,1,0,0],
[0,1,0,0,0,0,0,1,0],
[0,1,0,0,0,0,0,1,0],
[0,0,1,0,0,0,1,0,0],
[0,0,0,1,1,1,0,0,0],
]
def fill_circle(grid):
for r in grid: # For each row
j1 = None # left endpoint
j2 = None # right endpoint
for j, v in enumerate(r):
if v == 1 and j1 is None:
j1 = j
continue
if v == 1 and j2 is None:
j2 = j
break
else: # Did not find j1 AND j2
continue
for j in range(j1, j2): # Fill all points between
r[j] = 1
fill_circle(grid)
grid
[[0,0,0,1,1,1,0,0,0],
[0,0,1,1,1,1,1,0,0],
[0,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,0],
[0,0,1,1,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0]]
I'm trying to check if a point is within a sphere with a center point of (x, y, z) where (x, y, z) is not (0, 0, 0).
This code I'm using to generate the points I want to check:
def generatecoords(self, i):
x, y, z = generatepoint()
if i >= 1:
valid = False
while valid == False:
coords = self.checkpoint(x, y, z)
for b in world.starlist:
if coords == world.starlist[b].coords:
coords = self.checkpoint(x, y, z)
else:
valid = True
else:
coords = self.checkpoint(x, y, z)
return coords
def checkpoint(self, x, y, z):
d = math.sqrt(x * x + y * y + z * z)
while d >= self.radius:
x, y, z = generatepoint()
d = math.sqrt(x * x + y * y + z * z)
coords = (int(x), int(y), int(z))
return coords
def generatepoint():
x, y, z = [int(random.uniform(-self.radius, self.radius)) \
for b in range(3)]
return x, y, z
These function are called in a for loop to generate the points in a dictionary, while also checking the unlikely chance that points aren't placed on top of another(mostly because I can).
I trying to figure out what I need to add to math.sqrt(x * x + y * y + z * z) so that it accounts for a center that isn't (0, 0, 0). I do know of one way to do it, but it would require several lines of code and I'd rather do it in one. I would have asked this in the comments of the answer in another question, but I'm not allowed to comment on answers yet.
The formula is:
A point (x,y,z) is inside the sphere with center (cx,cy,cz) and radius r if
(x - cx)^2 + (y - cy)^2 + (z - cz)^2 < r^2
Here is a very short function that returns True if the point is in the sphere, and False if not.
The inputs are two numpy arrays: point = [x,y,z] and ref = [x,y,z] and the radius should be a float.
import numpy as np
def inSphere(self, point, ref, radius):
# Calculate the difference between the reference and measuring point
diff = np.subtract(point, ref)
# Calculate square length of vector (distance between ref and point)^2
dist = np.sum(np.power(diff, 2))
# If dist is less than radius^2, return True, else return False
return dist < radius ** 2
I am trying to find the intersection point of a straight(dashed red) with the contour-line highlighted in red(see plot). I used .get_paths in the second plot to isolate said contour line form the others(second plot).
I have looked at a contour intersection problem, How to find all the intersection points between two contour-set in an efficient way, and have tried to use it as a base but have not been able to reproduce anything useful.
http://postimg.org/image/hz01fouvn/
http://postimg.org/image/m6utofwb7/
Does any one have any ideas?
relevant functions to recreate plot,
#for contour
def p_0(num,t) :
esc_p = np.sum((((-1)**n)*(np.exp(t)**n)*((math.factorial(n)*((n+1)**0.5))**-1)) for n in range(1,num,1))
return esc_p+1
tau = np.arange(-2,3,0.1)
r=[]
p1 = p_0(51,tau)
p2 = p_0(51,tau)
for i in p1:
temp_r=i/p2
r.append(temp_r)
x,y= np.meshgrid(tau,tau)
cs = plt.contour(x, y, np.log(r),50,colors='k')
whichContour =20
pa = CS.collections[whichContour].get_paths()[0]
v = pa.vertices
xx = v[:, 0]
yy = v[:, 1]
plt.plot(xx, yy, 'r-', label='Crossing contour')
#straight line
p=0.75
logp = (np.log(p*np.exp(tau)))
plt.plot(tau,logp)
Current attempt,
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import math
def intercepting_line() :
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
#fake data
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
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)
#plot
cs = plt.contour(X,Y,Z)
whichContour = 2 # change this to find the right contour lines
#get the vertices to calculate an intercept with a line
p = cs.collections[whichContour].get_paths()[0]
#see: http://matplotlib.org/api/path_api.html#module-matplotlib.path
v = p.vertices
xx = v[:, 0]
yy = v[:, 1]
#this shows the innermost ring now
plt.plot(xx, yy, 'r--', label='inner ring')
#fake line
x = np.arange(-2, 3.0, 0.1)
y=lambda x,m:(m*x)
y=y(x,0.9)
lineMesh = np.meshgrid(x,y)
plt.plot(x,y,'r' ,label='line')
#get the intercepts, two in this case
x, y = find_intersections(v, lineMesh[1])
print x
print y
#plot the intercepting points
plt.plot(x[0], y[0], 'bo', label='first intercept')
#plt.plot(x[1], y[1], 'rs', label='second intercept')
plt.legend(shadow=True, fancybox=True, numpoints=1, loc='best')
plt.show()
#now we need to calculate the intercept of the vertices and whatever line
#this is pseudo code but works in case of two intercepting contour vertices
def find_intersections(A, B):
# min, max and all for arrays
amin = lambda x1, x2: np.where(x1<x2, x1, x2)
amax = lambda x1, x2: np.where(x1>x2, x1, x2)
aall = lambda abools: np.dstack(abools).all(axis=2)
slope = lambda line: (lambda d: d[:,1]/d[:,0])(np.diff(line, axis=0))
x11, x21 = np.meshgrid(A[:-1, 0], B[:-1, 0])
x12, x22 = np.meshgrid(A[1:, 0], B[1:, 0])
y11, y21 = np.meshgrid(A[:-1, 1], B[:-1, 1])
y12, y22 = np.meshgrid(A[1:, 1], B[1:, 1])
m1, m2 = np.meshgrid(slope(A), slope(B))
m1inv, m2inv = 1/m1, 1/m2
yi = (m1*(x21-x11-m2inv*y21) + y11)/(1 - m1*m2inv)
xi = (yi - y21)*m2inv + x21
xconds = (amin(x11, x12) < xi, xi <= amax(x11, x12),
amin(x21, x22) < xi, xi <= amax(x21, x22) )
yconds = (amin(y11, y12) < yi, yi <= amax(y11, y12),
amin(y21, y22) < yi, yi <= amax(y21, y22) )
return xi[aall(xconds)], yi[aall(yconds)]
At the moment it finds intersecting points but only where the line is uniform, the main reason why I cannot find a solution here is that I dont understand the original authors train of thinking here,
yi = (m1*(x21-x11-m2inv*y21) + y11)/(1 - m1*m2inv)
xi = (yi - y21)*m2inv + x21
Use shapely can find the intersection point, than use the point as the init guess value for fsolve() to find the real solution:
#for contour
def p_0(num,t) :
esc_p = np.sum((((-1)**n)*(np.exp(t)**n)*((math.factorial(n)*((n+1)**0.5))**-1)) for n in range(1,num,1))
return esc_p+1
tau = np.arange(-2,3,0.1)
x,y= np.meshgrid(tau,tau)
cs = plt.contour(x, y, np.log(p_0(51, y)/p_0(51, x)),[0.2],colors='k')
p=0.75
logp = (np.log(p*np.exp(tau)))
plt.plot(tau,logp)
from shapely.geometry import LineString
v1 = cs.collections[0].get_paths()[0].vertices
ls1 = LineString(v1)
ls2 = LineString(np.c_[tau, logp])
points = ls1.intersection(ls2)
x, y = points.x, points.y
from scipy import optimize
def f(p):
x, y = p
e1 = np.log(0.75*np.exp(x)) - y
e2 = np.log(p_0(51, y)/p_0(51, x)) - 0.2
return e1, e2
x2, y2 = optimize.fsolve(f, (x, y))
plt.plot(x, y, "ro")
plt.plot(x2, y2, "gx")
print x, y
print x2, y2
Here is the output:
0.273616328952 -0.0140657435002
0.275317387697 -0.0123646847549
and the plot:
See your contour lines as polylines and plug the vertex coordinates into the implicit line equation (F(P) = a.X + b.Y + c = 0). Every change of sign is an intersection, computed by solving 2x2 linear equations. You need no sophisticated solver.
If you need to detect the contour lines simultaneously, it is not much more complicated: consider the section of the terrain by a vertical plane through the line. You will obtain altitudes by linear interpolation along the edges of the grid tiles that are crossed. Finding the intersections with the grid is closely related to the Bresenham line drawing algorithm.
Then what you get is a profile, i.e. a function of a single variable. Locating the intersections with the horizontal planes (iso-values) is also done by detecting changes of sign.
This is a way that I used to solve this problem
def straight_intersection(straight1, straight2):
p1x = straight1[0][0]
p1y = straight1[0][1]
p2x = straight1[1][0]
p2y = straight1[1][1]
p3x = straight2[0][0]
p3y = straight2[0][1]
p4x = straight2[1][0]
p4y = straight2[1][1]
x = p1y * p2x * p3x - p1y * p2x * p4x - p1x * p2y * p4x + p1x * p2y * p3x - p2x * p3x * p4y + p2x * p3y * p4x + p1x * p3x * p4y - p1x * p3y * p4x
x = x / (p2x * p3y - p2x * p4y - p1x * p3y + p1x * p4y + p4x * p2y - p4x * p1y - p3x * p2y + p3x * p1y)
y = ((p2y - p1y) * x + p1y * p2x - p1x * p2y) / (p2x - p1x)
return (x, y)
While this question is old now, i want to share my answer for this problem for future generations to come.
Actually, there is two more solutions i found to this problem that is relatively efficient.
First solution is using pointPolygonTest in opencv recursively like that.
# Enumerate the line segment points between 2 points
for pt in zip(*line(*p1, *p2)):
if cv2.pointPolygonTest(conts[0], pt, False) == 0: # If the point is on the contour
return pt
Second solution, you can simply draw the contour and line and make a np.logical_and() to get the answer
blank = np.zeros((1000, 1000))
blank_contour = drawContour(blank.copy(), cnt[0], 0, 1, 1)
blank_line = cv2.line(blank.copy(), line[0], line[1], 1, 1)
intersections = np.logical_and(blank_contour, black_line)
points = np.where(intersections == 1)
I'm drawing a bunch of lines with the Python Imaging Library's ImageDraw.line(), but they look horrid since I can't find a way to anti-alias them. How can I anti-alias lines in PIL?
This is a really quickly hacked together function to draw an anti-aliased line with PIL that I wrote after googling for the same issue, seeing this post and failing to install aggdraw and being on a tight deadline. It's an implementation of Xiaolin Wu's line algorithm. I hope it helps anyone googling for the same thing!!
:)
"""Library to draw an antialiased line."""
# http://stackoverflow.com/questions/3122049/drawing-an-anti-aliased-line-with-thepython-imaging-library
# https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
import math
def plot(draw, img, x, y, c, col, steep, dash_interval):
"""Draws an antiliased pixel on a line."""
if steep:
x, y = y, x
if x < img.size[0] and y < img.size[1] and x >= 0 and y >= 0:
c = c * (float(col[3]) / 255.0)
p = img.getpixel((x, y))
x = int(x)
y = int(y)
if dash_interval:
d = dash_interval - 1
if (x / dash_interval) % d == 0 and (y / dash_interval) % d == 0:
return
draw.point((x, y), fill=(
int((p[0] * (1 - c)) + col[0] * c),
int((p[1] * (1 - c)) + col[1] * c),
int((p[2] * (1 - c)) + col[2] * c), 255))
def iround(x):
"""Rounds x to the nearest integer."""
return ipart(x + 0.5)
def ipart(x):
"""Floors x."""
return math.floor(x)
def fpart(x):
"""Returns the fractional part of x."""
return x - math.floor(x)
def rfpart(x):
"""Returns the 1 minus the fractional part of x."""
return 1 - fpart(x)
def draw_line_antialiased(draw, img, x1, y1, x2, y2, col, dash_interval=None):
"""Draw an antialised line in the PIL ImageDraw.
Implements the Xialon Wu antialiasing algorithm.
col - color
"""
dx = x2 - x1
if not dx:
draw.line((x1, y1, x2, y2), fill=col, width=1)
return
dy = y2 - y1
steep = abs(dx) < abs(dy)
if steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
dx, dy = dy, dx
if x2 < x1:
x1, x2 = x2, x1
y1, y2 = y2, y1
gradient = float(dy) / float(dx)
# handle first endpoint
xend = round(x1)
yend = y1 + gradient * (xend - x1)
xgap = rfpart(x1 + 0.5)
xpxl1 = xend # this will be used in the main loop
ypxl1 = ipart(yend)
plot(draw, img, xpxl1, ypxl1, rfpart(yend) * xgap, col, steep,
dash_interval)
plot(draw, img, xpxl1, ypxl1 + 1, fpart(yend) * xgap, col, steep,
dash_interval)
intery = yend + gradient # first y-intersection for the main loop
# handle second endpoint
xend = round(x2)
yend = y2 + gradient * (xend - x2)
xgap = fpart(x2 + 0.5)
xpxl2 = xend # this will be used in the main loop
ypxl2 = ipart(yend)
plot(draw, img, xpxl2, ypxl2, rfpart(yend) * xgap, col, steep,
dash_interval)
plot(draw, img, xpxl2, ypxl2 + 1, fpart(yend) * xgap, col, steep,
dash_interval)
# main loop
for x in range(int(xpxl1 + 1), int(xpxl2)):
plot(draw, img, x, ipart(intery), rfpart(intery), col, steep,
dash_interval)
plot(draw, img, x, ipart(intery) + 1, fpart(intery), col, steep,
dash_interval)
intery = intery + gradient
I had a similar problem, my lines had rough edges where changing directions. I took a clue from how lines are drawn in IOS and came up with this code. It puts rounded line caps on the ends of the lines and really cleans things up. Not exactly anti-aliasing, but am totally new to PIL and had such a hard time finding an answer I figured I would share. Needs some tweaking and there is probably a better way but does what I need :)
from PIL import Image
import ImageDraw
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class DrawLines:
def draw(self, points, color, imageName):
img = Image.new("RGBA", [1440,1080], (255,255,255,0))
draw = ImageDraw.Draw(img)
linePoints = []
for point in points:
draw.ellipse((point.x-7, point.y-7, point.x+7, point.y+7), fill=color)
linePoints.append(point.x)
linePoints.append(point.y)
draw.line(linePoints, fill=color, width=14)
img.save(imageName)
p1 = Point(100,200)
p2 = Point(190,250)
points = [p1,p2]
red = (255,0,0)
drawLines = DrawLines()
drawLines.draw(points, red, "C:\\test.png")