Python raytracing distorts object shapes when close to the camera - python

The Problem
I recently found someone's awesome little pure-Python raytracing script from this link, and extended it a little bit for more convenient functions. However, sometimes it distorts the shapes of the objects and I'm wondering if someone with raytracing/3d experience might have any clue as to what might be causing it?
Some Info
The scene I'm testing with consists of a ground-level plane with three colored spheres placed on top of it. It produces good-looking scenes when the camera is looking down on the scene from above/at angles and at a certain distance (see the first two pics); however, when the camera gets closer to the ground level and closer to the objects the spheres end up changing their shapes and becoming oblong as if they're being stretched up towards the sky (see third pic). Note that the camera in the third pic with the distorted spheres is sort of upside down, which is because I'm still figuring out how to control the camera and not sure how to "spin it" upright when that happens; it seems to automatically look towards the general area where the spheres/light source is located, and only if I change some parameters will it look in different directions.
I'm still trying to decipher and understand what goes on in the original code that I found and based my code on, so I don't know but it could be something about the method or approach to raytracing taken by the original author. I've attached the entire code of my module script which should run when you press F5 if anyone is up for the challenge. The image rendering requires PIL, and if you want to play with the position of the camera, just look at the Camera class, and change its options in the "normaltest" function.
Update
Someone pointed out that when running the script it doesn't reproduce the problem in the third image. I have now changed the camera position for the normaltest function so that it will reproduce the problem (see the new fourth image for how it should look like). In case you're wondering why the light seems to be shooting out of the spheres it's bc I placed the lightsource somewhere in between all of them.
Im starting to think that the problem is with the camera and me not understanding it completely.
The camera options zoom, xangle, and yangle may not do what their names imply; that's just how I named them based on what they seemed to do when I changed them up. Originally they were not variables but rather some constant nr in a calculation that had to be changed manually. Specifically they are used to define and produce the rays through the scene on line 218 in the renderScene function.
For instance, sometimes when I change the zoom value it also changes the direction and position of the camera.
It's a bit odd that in the original code the camera was just defined as a point with no direction (the xangle and yangle variables were at first just static nrs with no option for defining them), and almost always starts out looking towards the object automatically.
I cant find a way to "spin"/tilt the camera around itself.
Try also to heighten the camera from its current z-coordinate of 2 to a z of 5, a very small change but it makes the distortion dramatically better looking (though still bad), so proximity to the ground or the shift in angle that comes with it seems to play some role.
"""
Pure Python ray-tracer :)
taken directly from http://pastebin.com/f8f5ghjz with modifications
another good one alternative at http://www.hxa.name/minilight/
some more equations for getting intersection with other 3d geometries, https://www.cl.cam.ac.uk/teaching/1999/AGraphHCI/SMAG/node2.html#SECTION00023200000000000000
"""
#IMPORTS
from math import sqrt, pow, pi
import time
import PIL,PIL.Image
#GEOMETRIES
class Vector( object ):
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
def dot(self, b):
return self.x*b.x + self.y*b.y + self.z*b.z
def cross(self, b):
return (self.y*b.z-self.z*b.y, self.z*b.x-self.x*b.z, self.x*b.y-self.y*b.x)
def magnitude(self):
return sqrt(self.x**2+self.y**2+self.z**2)
def normal(self):
mag = self.magnitude()
return Vector(self.x/mag,self.y/mag,self.z/mag)
def __add__(self, b):
return Vector(self.x + b.x, self.y+b.y, self.z+b.z)
def __sub__(self, b):
return Vector(self.x-b.x, self.y-b.y, self.z-b.z)
def __mul__(self, b):
assert type(b) == float or type(b) == int
return Vector(self.x*b, self.y*b, self.z*b)
class Sphere( object ):
def __init__(self, center, radius, color):
self.c = center
self.r = radius
self.col = color
def intersection(self, l):
q = l.d.dot(l.o - self.c)**2 - (l.o - self.c).dot(l.o - self.c) + self.r**2
if q < 0:
return Intersection( Vector(0,0,0), -1, Vector(0,0,0), self)
else:
d = -l.d.dot(l.o - self.c)
d1 = d - sqrt(q)
d2 = d + sqrt(q)
if 0 < d1 and ( d1 < d2 or d2 < 0):
return Intersection(l.o+l.d*d1, d1, self.normal(l.o+l.d*d1), self)
elif 0 < d2 and ( d2 < d1 or d1 < 0):
return Intersection(l.o+l.d*d2, d2, self.normal(l.o+l.d*d2), self)
else:
return Intersection( Vector(0,0,0), -1, Vector(0,0,0), self)
def normal(self, b):
return (b - self.c).normal()
class Cylinder( object ):
"just a copy of sphere, needs work. maybe see http://stackoverflow.com/questions/4078401/trying-to-optimize-line-vs-cylinder-intersection"
def __init__(self, startpoint, endpoint, radius, color):
self.s = startpoint
self.e = endpoint
self.r = radius
self.col = color
def intersection(self, l):
q = l.d.dot(l.o - self.c)**2 - (l.o - self.c).dot(l.o - self.c) + self.r**2
if q < 0:
return Intersection( Vector(0,0,0), -1, Vector(0,0,0), self)
else:
d = -l.d.dot(l.o - self.c)
d1 = d - sqrt(q)
d2 = d + sqrt(q)
if 0 < d1 and ( d1 < d2 or d2 < 0):
return Intersection(l.o+l.d*d1, d1, self.normal(l.o+l.d*d1), self)
elif 0 < d2 and ( d2 < d1 or d1 < 0):
return Intersection(l.o+l.d*d2, d2, self.normal(l.o+l.d*d2), self)
else:
return Intersection( Vector(0,0,0), -1, Vector(0,0,0), self)
def normal(self, b):
return (b - self.c).normal()
class LightBulb( Sphere ):
pass
class Plane( object ):
"infinite, no endings"
def __init__(self, point, normal, color):
self.n = normal
self.p = point
self.col = color
def intersection(self, l):
d = l.d.dot(self.n)
if d == 0:
return Intersection( vector(0,0,0), -1, vector(0,0,0), self)
else:
d = (self.p - l.o).dot(self.n) / d
return Intersection(l.o+l.d*d, d, self.n, self)
class Rectangle( object ):
"not done. like a plane, but is limited to the shape of a defined rectangle"
def __init__(self, point, normal, color):
self.n = normal
self.p = point
self.col = color
def intersection(self, ray):
desti = ray.dest.dot(self.n)
if desti == 0:
#??
return Intersection( vector(0,0,0), -1, vector(0,0,0), self)
else:
desti = (self.p - ray.orig).dot(self.n) / desti
return Intersection(ray.orig+ray.desti*desti, desti, self.n, self)
class RectangleBox( object ):
"not done. consists of multiple rectangle objects as its sides"
pass
class AnimatedObject( object ):
def __init__(self, *objs):
self.objs = objs
def __iter__(self):
for obj in self.objs:
yield obj
def __getitem__(self, index):
return self.objs[index]
def reverse(self):
self.objs = [each for each in reversed(self.objs)]
return self
#RAY TRACING INTERNAL COMPONENTS
class Ray( object ):
def __init__(self, origin, direction):
self.o = origin
self.d = direction
class Intersection( object ):
"keeps a record of a known intersection bw ray and obj?"
def __init__(self, point, distance, normal, obj):
self.p = point
self.d = distance
self.n = normal
self.obj = obj
def testRay(ray, objects, ignore=None):
intersect = Intersection( Vector(0,0,0), -1, Vector(0,0,0), None)
for obj in objects:
if obj is not ignore:
currentIntersect = obj.intersection(ray)
if currentIntersect.d > 0 and intersect.d < 0:
intersect = currentIntersect
elif 0 < currentIntersect.d < intersect.d:
intersect = currentIntersect
return intersect
def trace(ray, objects, light, maxRecur):
if maxRecur < 0:
return (0,0,0)
intersect = testRay(ray, objects)
if intersect.d == -1:
col = vector(AMBIENT,AMBIENT,AMBIENT)
elif intersect.n.dot(light - intersect.p) < 0:
col = intersect.obj.col * AMBIENT
else:
lightRay = Ray(intersect.p, (light-intersect.p).normal())
if testRay(lightRay, objects, intersect.obj).d == -1:
lightIntensity = 1000.0/(4*pi*(light-intersect.p).magnitude()**2)
col = intersect.obj.col * max(intersect.n.normal().dot((light - intersect.p).normal()*lightIntensity), AMBIENT)
else:
col = intersect.obj.col * AMBIENT
return col
def gammaCorrection(color,factor):
return (int(pow(color.x/255.0,factor)*255),
int(pow(color.y/255.0,factor)*255),
int(pow(color.z/255.0,factor)*255))
#USER FUNCTIONS
class Camera:
def __init__(self, cameraPos, zoom=50.0, xangle=-5, yangle=-5):
self.pos = cameraPos
self.zoom = zoom
self.xangle = xangle
self.yangle = yangle
def renderScene(camera, lightSource, objs, imagedims, savepath):
imgwidth,imgheight = imagedims
img = PIL.Image.new("RGB",imagedims)
#objs.append( LightBulb(lightSource, 0.2, Vector(*white)) )
print "rendering 3D scene"
t=time.clock()
for x in xrange(imgwidth):
#print x
for y in xrange(imgheight):
ray = Ray( camera.pos, (Vector(x/camera.zoom+camera.xangle,y/camera.zoom+camera.yangle,0)-camera.pos).normal())
col = trace(ray, objs, lightSource, 10)
img.putpixel((x,imgheight-1-y),gammaCorrection(col,GAMMA_CORRECTION))
print "time taken", time.clock()-t
img.save(savepath)
def renderAnimation(camera, lightSource, staticobjs, animobjs, imagedims, savepath, saveformat):
"NOTE: savepath should not have file extension, but saveformat should have a dot"
time = 0
while True:
print "time",time
timesavepath = savepath+"_"+str(time)+saveformat
objs = []
objs.extend(staticobjs)
objs.extend([animobj[time] for animobj in animobjs])
renderScene(camera, lightSource, objs, imagedims, timesavepath)
time += 1
#SOME LIGHTNING OPTIONS
AMBIENT = 0.05 #daylight/nighttime
GAMMA_CORRECTION = 1/2.2 #lightsource strength?
#COLORS
red = (255,0,0)
yellow = (255,255,0)
green = (0,255,0)
blue = (0,0,255)
grey = (120,120,120)
white = (255,255,255)
purple = (200,0,200)
def origtest():
print ""
print "origtest"
#BUILD THE SCENE
imagedims = (500,500)
savepath = "3dscene_orig.png"
objs = []
objs.append(Sphere( Vector(-2,0,-10), 2, Vector(*green)))
objs.append(Sphere( Vector(2,0,-10), 3.5, Vector(*red)))
objs.append(Sphere( Vector(0,-4,-10), 3, Vector(*blue)))
objs.append(Plane( Vector(0,0,-12), Vector(0,0,1), Vector(*grey)))
lightSource = Vector(0,10,0)
camera = Camera(Vector(0,0,20))
#RENDER
renderScene(camera, lightSource, objs, imagedims, savepath)
def normaltest():
print ""
print "normaltest"
#BUILD THE SCENE
"""
the camera is looking down on the surface with the spheres from above
the surface is like looking down on the xy axis of the xyz coordinate system
the light is down there together with the spheres, except from one of the sides
"""
imagedims = (200,200)
savepath = "3dscene.png"
objs = []
objs.append(Sphere( Vector(-4, -2, 1), 1, Vector(*red)))
objs.append(Sphere( Vector(-2, -2, 1), 1, Vector(*blue)))
objs.append(Sphere( Vector(-2, -4, 1), 1, Vector(*green)))
objs.append(Plane( Vector(0,0,0), Vector(0,0,1), Vector(*grey)))
lightSource = Vector(-2.4, -3, 2)
camera = Camera(Vector(-19,-19,2), zoom=2.0, xangle=-30, yangle=-30)
#RENDER
renderScene(camera, lightSource, objs, imagedims, savepath)
def animtest():
print ""
print "falling ball test"
#BUILD THE SCENE
imagedims = (200,200)
savepath = "3d_fallball"
saveformat = ".png"
staticobjs = []
staticobjs.append(Sphere( Vector(-4, -2, 1), 1, Vector(*red)))
staticobjs.append(Sphere( Vector(-2, -4, 1), 1, Vector(*green)))
staticobjs.append(Plane( Vector(0,0,0), Vector(0,0,1), Vector(*purple)))
animobjs = []
fallingball = AnimatedObject(Sphere( Vector(-2, -2, 20), 1, Vector(*yellow)),
Sphere( Vector(-2, -2, 15), 1, Vector(*yellow)),
Sphere( Vector(-2, -2, 9), 1, Vector(*yellow)),
Sphere( Vector(-2, -2, 5), 1, Vector(*yellow)),
Sphere( Vector(-2, -2, 1), 1, Vector(*yellow)))
animobjs.append(fallingball)
lightSource = Vector(-4,-4,10)
camera = Camera(Vector(0,0,30))
#RENDER
renderAnimation(camera, lightSource, staticobjs, animobjs, imagedims, savepath, saveformat)
#RUN TESTS
#origtest()
normaltest()
#animtest()

The key to your problems is probably this line:
ray = Ray( camera.pos,
(Vector(
x/camera.zoom+camera.xangle,
y/camera.zoom+camera.yangle,
0)
-camera.pos)
.normal())
A ray is defined as a line going from camera position (however is that point defined) over XY plane, that is zoomed and SHIFTED by the xangle and yangle parameters.
This is not how perspective projection is usually implemented. This is more like a tilt/shift camera. A typical perspective transform would keep the plane you project onto PERPENDICULAR to the ray going from the camera through the centre of the picture.
With this code you have two options: either rewrite this, or always use xangle, yangle, camera.pos.x and camera.pos.y == 0. Otherwise you get wonky results.
To be correct, this is perfectly legit perspective. It is just not what you would ever see with a typical camera.

Related

TypeError: '>' not supported between instances of 'Block' and 'Block'?

I want to define a function sortbooks(shelf) using insert_animate(blockposition, shelf, high) such that it sorts the shelf according to their size in ascending order.
I have come up with this function:
def sortbooks(shelf):
for i in shelf:
insert_animate(i.size,shelf,max(shelf))
return shelf
However, I got a TypeError as such:
File "<pyshell#275>", line 3, in sortbooks
insert_animate(i.size,shelf,max(shelf))
TypeError: '>' not supported between instances of 'Block' and 'Block'
How should I go about in defining this function? Is my approach completely wrong? I would really appreciate help/some guidance on this! Thank you!
This is the function for insert_animate:
def insert_animate(blockposition, shelf, high):
if blockposition == 0:
return shelf
a = s.pop(blockposition)
for i in range(high):
if a.size <= s[i].size:
s.insert(i, a)
break
else:
s.insert(high, a)
return shelf
Below is shelf.py:
from turtle import *
class Block(Turtle):
def __init__(self, size):
self.size = size
Turtle.__init__(self, shape="square", visible=False)
self.pu()
self.shapesize(size * 1.5, 1.5, 2) # square-->rectangle
self.fillcolor("black")
self.st()
def glow(self):
self.fillcolor("red")
def unglow(self):
self.fillcolor("black")
def __repr__(self):
return "Block size: {0}".format(self.size)
class Shelf(list):
def __init__(self, y):
"create an shelf. y is y-position of first block"
self.y = y
self.x = -150
def push(self, d):
width, _, _ = d.shapesize()
yoffset = width/2 * 20 # to align the blocks by it's bottom edge
d.sety(self.y + yoffset)
d.setx(self.x+34*len(self))
self.append(d)
def _close_gap_from_i(self, i):
for b in self[i:]:
xpos, _ = b.pos()
b.setx(xpos - 34)
def _open_gap_from_i(self, i):
for b in self[i:]:
xpos, _ = b.pos()
b.setx(xpos + 34)
def pop(self, key):
b = list.pop(self, key)
b.glow()
b.sety(200)
self._close_gap_from_i(key)
return b
def insert(self, key, b):
self._open_gap_from_i(key)
list.insert(self, key, b)
b.setx(self.x+34*key)
width, _, _ = b.shapesize()
yoffset = width/2 * 20 # to align the blocks by it's bottom edge
b.sety(self.y + yoffset)
b.unglow()
def show_text(text):
goto(0,-250)
write(text, align="center", font=("Courier", 16, "bold"))
def start_sort():
onkey(None,"space")
clear()
show_text("sort_me")
sort_func(s)
def init_shelf(vals=(4, 8, 2, 9, 3, 1, 10, 7, 5, 6)):
s = Shelf(-200)
for i in vals:
s.push(Block(i))
return s
def clear_window():
getscreen().clearscreen()
def main(func):
global sort_func
sort_func = func
getscreen().clearscreen()
ht(); penup()
init_shelf()
show_text("press spacebar to start sorting")
onkey(start_sort, "space")
onkey(bye, "Escape")
listen()
mainloop()
To solve the problem you have right now, you can use max(shelf, key=lambda b: b.size) instead. An alternative way is to add __gt__ method for block.

class Rectangle - Python

I'm just beginning working with object-oriented programming. I have created some classes and am trying to complete the rectangle class. Any and all help is appreciated.
I'm confused about when you need to refer to self and when you can just create variables. For example, in defining the length of the rectangle, I don't know if I should call the variable self.length or just length, and I haven't been able to find any rectangle classes defined in this way.
import math
class Point (object):
# constructor
def __init__ (self, x = 0, y = 0):
self.x = x
self.y = y
# get distance
def dist (self, other):
return math.hypot (self.x - other.x, self.y - other.y)
# get a string representation of a Point object
def __str__ (self):
return '(' + str(self.x) + ", " + str(self.y) + ")"
# test for equality
def __eq__ (self, other):
tol = 1.0e-16
return ((abs (self.x - other.x) < tol) and (abs(self.y - other.y) < tol))
class Circle (object):
# constructor
def __init__ (self, radius = 1, x = 0, y = 0):
self.radius = radius
self.center = Point (x, y)
# compute cirumference
def circumference (self):
return 2.0 * math.pi * self.radius
# compute area
def area (self):
return math.pi * self.radius * self.radius
# determine if point is strictly inside circle
def point_inside (self, p):
return (self.center.dist(p) < self.radius)
# determine if a circle is strictly inside this circle
def circle_inside (self, c):
distance = self.center.dist (c.center)
return (distance + c.radius) < self.radius
# determine if a circle c intersects this circle (non-zero area of overlap)
def does_intersect (self, c):
# string representation of a circle
def __str__ (self):
# test for equality of radius
def __eq__ (self, other):
tol = 1.0e-16
class Rectangle (object):
# constructor
def __init__ (self, ul_x = 0, ul_y = 1, lr_x = 1, lr_y = 0):
if ((ul_x < lr_x) and (ul_y > lr_y)):
self.ul = Point (ul_x, ul_y)
self.lr = Point (lr_x, lr_y)
else:
self.ul = Point (0, 1)
self.lr = Point (1, 0)
# determine length of Rectangle
def length (self):
# determine width of Rectangle
def width (self):
# determine the perimeter
def perimeter (self):
# determine the area
def area (self):
# determine if a point is strictly inside the Rectangle
def point_inside (self, p)
# determine if another Rectangle is inside this Rectangle
def rectangle_inside (self, r):
# determine if two Rectangles overlap
def does_intersect (self, other):
# determine the smallest rectangle that circumscribes a circle
def rect_circumscribe (self, c):
# give string representation of a rectangle
def __str__ (self):
# determine if two rectangles have the same length and width
def __eq__ (self, other):
Basically, setting a value to self.length gives you the ability to access this value from other functions inside the class and from outside of the class. If you set a value to length, you are able to access this variable only in the current function inside the class.
Just a start, try to continue yourself:
class Rectangle (object):
# constructor
def __init__ (self, ul_x = 0, ul_y = 1, lr_x = 1, lr_y = 0):
# Called if you say: my_rectancle = Rectangle (-10, 10, 10, -10)
# Puts parameters in fields of your newly created object of class Rectancle
self.ul_x = ul_x
self.ul_y = ul_y
self.lr_x = lr_x
self.lr_y = lr_y
# compute cirumference
def circumference (self):
return 2 * (self.ur_x - self.lr_x) + 2 * (self.ul_y - self.lr_y)
# compute area
def area (self):
return (self.ur_x - self.lr_x) * (self.ul_y - self.lr_y)
[EDIT]
With regard to the additional code in your comment, it's quite close to what it should be. With some corrections:
# determine length of Rectangle
def length (self):
return self.ul_y - self.lr_y
# determine width of Rectangle
def width (self):
return self.lr_x - self.ul_x
# self. has been added, since e.g. lr_x is not a parameter
# of the width function, but a field of the object you make
# by instantiation: 'rectangle = Rectangle (10, 20, 100, 200)'
# After that, self refers to the object 'rectangle' you created,
# which has class 'Rectangle'.
#
# Note that a class is a type.
# You can have a type 'Dog'.
# Dog 'fluffy' is an instance of that class, so a particular dog.
# In the methods (functions) of 'Dog', 'self' refers to the particular
# dog you're working with.
# determine the perimeter
def perimeter (self):
return 2 * self.width () + 2 * self.length ()
# Note the () after width and length.
# They're needed because width and length are
# function calls (they DO something) rather than fields (data)
# You could also have stored width and length into fields,
# just like the constructor did with ul_x, ul_y, lr_x and lr_y,
# storing them in self.ul_x, self.ul_y etc.
# Then the braces wouldn't have been needed.
# But also out some superfluous braces here
# Multiplication goes before addition anyhow
# And return returns everything after it, no braces needed.
# determine the area
def area (self):
return self.width () * self.length ()
So, how far did you get now?

Draw half infinite lines?

I use pyqtgraph for data acquisition and I have to represent some thresholds on the graphics view. For example to represent a high voltage limit, etc.
I used the class InfiniteLine from pyqtgraph, but now, I have to take into account some possible changes on the threshold value during the acquisition. It would look as a step between two infinite line (please find an example attached).
example
For this, I would have to draw a half infinite line. Do you know a simple way to do it?
I thought about using some plotCurveItem limited by the viewBox minimum and maximum :
thresholdValue = 60V # just an example
range = self.viewBox.viewRange()
xRange = range[0] # we only want ViewBox horizontal limits
minView = xRange[0]
maxView = xRange[1]
myPlotCurveItem = pyqtgraph.PlotCurveItem([minView, maxView],[thresholdValue, thresholdValue])
In case of changing on threshold value :
newThresholdValue = 70V
the x data for the plotCurveItem would become :
[minView, changingTime] #with changinTime : the moment we change the threshold
and we would add a new plotCurveItem :
myNewPlotCurveItem = pyqtgraph.plotCurveItem([changingTime, maxView],[newThresholdValue, newThresholdValue])
Does this solution looks good or do you see any problem with that?
Your approach looks good and is mostly what pyqtgraph.InfiniteLine is doing. I examined the source of InfiniteLine and extracted those parts which are absolutely necessary and added the change point and two level information, then drawing three lines (left border to change point at left level, change point to right border at right level, connection of both).
Here is the full code:
from pyqtgraph.Qt import QtGui
import numpy as np
import pyqtgraph as pg
class InfiniteLineWithBreak(pg.GraphicsObject):
def __init__(self, changeX, levelsY, pen=None):
pg.GraphicsObject.__init__(self)
self.changeX = changeX
self.levelsY = levelsY
self.maxRange = [None, None]
self.moving = False
self.movable = False
self.mouseHovering = False
pen = (200, 200, 100)
self.setPen(pen)
self.setHoverPen(color=(255,0,0), width=self.pen.width())
self.currentPen = self.pen
def setBounds(self, bounds):
self.maxRange = bounds
self.setValue(self.value())
def setPen(self, *args, **kwargs):
self.pen = pg.fn.mkPen(*args, **kwargs)
if not self.mouseHovering:
self.currentPen = self.pen
self.update()
def setHoverPen(self, *args, **kwargs):
self.hoverPen = pg.fn.mkPen(*args, **kwargs)
if self.mouseHovering:
self.currentPen = self.hoverPen
self.update()
def boundingRect(self):
br = self.viewRect()
return br.normalized()
def paint(self, p, *args):
br = self.boundingRect()
p.setPen(self.currentPen)
# three lines (left border to change point, change point vertical, change point to right)
p.drawLine(pg.Point(br.left(), self.levelsY[0]), pg.Point(self.changeX, self.levelsY[0]))
p.drawLine(pg.Point(self.changeX, self.levelsY[0]), pg.Point(self.changeX, self.levelsY[1]))
p.drawLine(pg.Point(self.changeX, self.levelsY[1]), pg.Point(br.right(), self.levelsY[1]))
def dataBounds(self, axis, frac=1.0, orthoRange=None):
if axis == 0:
return None ## x axis should never be auto-scaled
else:
return (0,0)
def setMouseHover(self, hover):
pass
app = QtGui.QApplication([])
w = pg.GraphicsWindow()
w.resize(1000, 600)
v = w.addPlot(y=np.random.normal(size=100))
v.addItem(InfiniteLineWithBreak(changeX=50, levelsY=(-1, 1)))
app.exec_()
It looks like:
What one could add is reaction to hovering and changing the values with the mouse (change point as well as levels) or even rotate by 90 degree. InfiniteLine is a good example of how to do that.
thanks you for you very complete answer !
Your code works very well.
I made some modifications on your class InfiniteLineWithBreak in order to set multiple threshold transitions.
I modified the init and the paint methods only:
def __init__(self, listOfcouplesOfThresholdAndItsDate, pen=None):
pg.GraphicsObject.__init__(self)
self.listOfcouplesOfThresholdAndItsDate=listOfcouplesOfThresholdAndItsDate
self.maxRange = [None, None]
self.moving = False
self.movable = False
self.mouseHovering = False
pen = (200, 200, 100)
self.setPen(pen)
self.setHoverPen(color=(255,0,0), width=self.pen.width())
self.currentPen = self.pen
def paint(self, p, *args):
br = self.boundingRect()
p.setPen(self.currentPen)
if len(self.listOfcouplesOfThresholdAndItsDate)==0:
pass
elif len(self.listOfcouplesOfThresholdAndItsDate)==1:
threshold = self.listOfcouplesOfThresholdAndItsDate[0][1]
date = self.listOfcouplesOfThresholdAndItsDate[0][0]
p.drawLine(pg.Point(date, threshold), pg.Point(br.right(), threshold))
else:
threshold = self.listOfcouplesOfThresholdAndItsDate[0][1]
date = self.listOfcouplesOfThresholdAndItsDate[0][0]
i=0
for i in range(0, len(self.listOfcouplesOfThresholdAndItsDate)-2):
threshold = self.listOfcouplesOfThresholdAndItsDate[i][1]
date = self.listOfcouplesOfThresholdAndItsDate[i][0]
nexteDate = self.listOfcouplesOfThresholdAndItsDate[i+1][0]
nextThreshold = self.listOfcouplesOfThresholdAndItsDate[i+1][1]
p.drawLine(pg.Point(date, threshold), pg.Point(nexteDate, threshold))
p.drawLine(pg.Point(nexteDate, threshold), pg.Point(nexteDate, nextThreshold))
threshold = self.listOfcouplesOfThresholdAndItsDate[-2][1]
date = self.listOfcouplesOfThresholdAndItsDate[-2][0]
nexteDate = self.listOfcouplesOfThresholdAndItsDate[-1][0]
nextThreshold = self.listOfcouplesOfThresholdAndItsDate[-1][1]
p.drawLine(pg.Point(date, threshold), pg.Point(nexteDate, threshold))
p.drawLine(pg.Point(nexteDate, threshold), pg.Point(nexteDate, nextThreshold))
p.drawLine(pg.Point(nexteDate, nextThreshold), pg.Point(br.right(), nextThreshold))
Moreover, I added a method to append a new threshold transition point to the listOfcouplesOfThersholdAndItsDate :
def addANewThreshold(self,date, threshold):
self.listOfcouplesOfThresholdAndItsDate.append((date, threshold))
Here is an example of what it looks like :
example of multiple thresholds
Does the code look okay to you ?
Thank you,

How to give start,end coordinates

I'm new to qgis and in here I want to find a path between two selected points on the map(Roads-vector layer). The points are selected by the user, using mouse clicks.
So here I used the astar algorithm to find path between two points.
*******************************astar.py**********************************
import heapq
class AStar(object):
def __init__(self, graphAstar):
self.graphAstar = graphAstar
def heuristic(self, node, start, end):
raise NotImplementedError
def search(self, start, end):
openset = set()
closedset = set()
current = start
openHeap = []
openset.add(current)
openHeap.append((0,current))
while openset:
temp = heapq.heappop(openHeap)
current = temp[1]
if current == end:
path = []
while current.parent:
path.append(current)
current = current.parent
path.append(current)
return path[::-1]
openset.remove(current)
closedset.add(current)
for node in self.graphAstar[current]:
if node in closedset:
continue
if node in openset:
new_g = current.gg + current.move_cost(node)
if node.gg > new_g:
node.gg = new_g
node.parent = current
else:
node.gg = current.gg + current.move_cost(node)
node.H = self.heuristic(node, start, end)
node.parent = current
openset.add(node)
heapq.heappush(openHeap, (node.H,node))
return None
class AStarNode(object):
def __init__(self):
self.gg = 0
self.H = 0
self.parent = None
def move_cost(self, other):
raise NotImplementedError
*****************************astar_grid.py*******************************
from astar import AStar, AStarNode
from math import sqrt
class AStarGrid(AStar):
def heuristic(self, node, start, end):
return sqrt((end.x - node.x)**2 + (end.y - node.y)**2)
class AStarGridNode(AStarNode):
def __init__(self, x, y):
self.x, self.y = x, y
super(AStarGridNode, self).__init__()
def move_cost(self, other):
diagonal = abs(self.x - other.x) == 1 and abs(self.y - other.y) == 1
return 14 if diagonal else 10
and in the main code, the following method is used to create graph from vector layer.
**************************plugin.py**********************************
def make_graph(self, mapinfo):
nodes = [[AStarGridNode(x, y) for y in range(mapinfo['height'])] for x in range(mapinfo['width'])]
graphAstar = {}
for x, y in product(range(mapinfo['width']), range(mapinfo['height'])):
node = nodes[x][y]
graphAstar[node] = []
for i, j in product([-1, 0, 1], [-1, 0, 1]):
if not (0 <= x + i < mapinfo['width']): continue
if not (0 <= y + j < mapinfo['height']): continue
graphAstar[nodes[x][y]].append(nodes[x+i][y+j])
return graphAstar, nodes
And I called that method in FindRoutes method..
def findRoutes(self):
vl=self.canvas.currentLayer()
director = QgsLineVectorLayerDirector( vl, -1, '', '', '', 3 )
properter = QgsDistanceArcProperter()
director.addProperter( properter )
crs = self.canvas.mapRenderer().destinationCrs()
builder = QgsGraphBuilder( crs )
global x1
global y1
global x2
global y2
pStart = QgsPoint( x1, y1 )
pStop = QgsPoint( x2, y2 )
graphAstar, nodes = self.make_graph({ "width": 8, "height": 8 })
paths = AStarGrid(graphAstar)
start, end = ??
path = paths.search(start, end)
My question is, how to pass the start and end coordinates to the function above? Because passing them just as coordinates (start, end = pStart, pStop) does not work.
How do add them to the graph created as nodes?
Or is there any easy way to do it?
Please help me to to find a solution to this problem.
Thank You
When i do an astar, the node i use are intern of the astar and contain a reference vers the original point object (your tuple of position).
Maybe it's the same with your AStarGridNode ?
In your case :
start = AStarGridNode(x1, y1)
stop = AStarGridNode(x2, y2)
This part could be in the your search function to hide this from the user.

Explain to me like I am 5: OpenGL 4.x Rendering Pipeline

I have spent the last couple of weeks in my off-time looking at openGL. And while I do not have a problem following some of the older NeHe examples, from everything I have read, OpenGL4 is a totally different process. And I have access to the red book and the super bible, but the former is still offering legacy opengl calls where as the latter uses their own library. Neither is especially helpful in understanding how to put together code in a project. For example, my current understanding is that glu and glut are legacy and shouldn't be used for opengl 4.
I can generate vertices very easily for a hypothetical model space. I have an extremely hard time understanding how a model ends up showing up on my screen. About 95% of my attempts end up with a black blank screen.
Thanks in advance.
Here's some code:
# primatives.py
from collections import Iterable
from functools import reduce
import operator
import numpy as np
from exc import UnimplementedMethod
class Primative(object):
SIZE = 1 # number of pixels on a default grid
def __init__(self, point=None, *args, **kwargs):
self.point = point if isinstance(point, Iterable) else [0, 0, 0]
self.point = np.array(self.point, dtype=np.float32)
scaler = [self.SIZE/2]*len(self.point)
self.point = (self.point * scaler).tolist()
#property
def active(self):
attr = "__active__"
if not hasattr(self, attr):
setattr(self, attr, False)
return getattr(self, attr)
#active.setter
def active(self, value):
attr = "__active__"
if value in [True, False]:
setattr(self, attr, value)
return getattr(self, attr)
#property
def vertices(self):
"""Returns a simple list of calculated vertices"""
clsname = self.__class__.__name__
raise UnimplementedMethod(clsname)
#property
def dimension(self):
return len(self.point)
#property
def scaler(self):
attr = "__scaler__"
if not hasattr(self, attr):
size = self.SIZE / 2
setattr(self, attr, [size]*self.dimension)
return getattr(self, attr)
#scaler.setter
def scaler(self, *values):
attr = "__scaler__"
values = values[0] if len(values) == 1 else values
if len(values) == 1 and len(values) != self.point:
if isinstance(values, [int, float]):
setattr(self, attr, [values]*self.dimension)
elif isinstance(values, Iterable):
data = [(v, i)
for v, i in zip(values, xrange(self.dimension))]
value = [v for v, i in data]
if len(value) != self.dimension:
raise ValueError
setattr(self, attr, value)
#property
def translation(self):
attr = "__transalation__"
if not hasattr(self, attr):
size = self.SIZE / 2
setattr(self, attr, [size]*self.dimension)
return getattr(self, attr)
#translation.setter
def transalation(self, *values):
attr = "__transalation__"
values = values[0] if len(values) == 1 else values
if isinstance(values, (int, float)):
setattr(self, attr, [values]*self.dimension)
elif isinstance(values, Iterable):
data = [(v, i)
for v, i in zip(values, xrange(self.dimension))]
value = [v for v, i in data]
if len(value) != self.dimension:
raise ValueError
setattr(self, attr, value)
#property
def rotation(self):
"""
Rotation in radians
"""
attr = "__rotation__"
if not hasattr(self, attr):
setattr(self, attr, [0]*self.dimension)
return getattr(self, attr)
#rotation.setter
def rotation(self, *values):
"""
Rotation in radians
"""
attr = "__rotation__"
values = values[0] if len(values) == 1 else values
if isinstance(values, (int, float)):
setattr(self, attr, [values]*self.dimension)
elif isinstance(values, Iterable):
data = [(v, i)
for v, i in zip(values, xrange(self.dimension))]
value = [v for v, i in data]
if len(value) != self.dimension:
raise ValueError
setattr(self, attr, value)
#property
def volume(self):
clsname = self.__class__.__name__
raise UnimplementedMethod(clsname)
class Cube(Primative):
# G H
# * --------- *
# /| /|
# C / | D / |
# * --------- * |
# | * -------|- *
# | / E | / F
# |/ |/
# * --------- *
# A B
#property
def center_of_mass(self):
"""
Uses density to calculate center of mass
"""
return self.point
#property
def material(self):
clsname = self.__class__.__name__
raise UnimplementedMethod(clsname)
#material.setter
def material(self, value):
clsname = self.__class__.__name__
raise UnimplementedMethod(clsname)
#property
def mass(self):
return self.material.density * self.volume
#property
def volume(self):
func = operator.mul
return reduce(func, self.scaler, 1)
#property
def normals(self):
"""
computes the vertex normals
"""
norm = []
if len(self.point) == 1:
norm = [
# counter clockwise
# x (left hand rule)
(-1), # A
(1) # B
]
elif len(self.point) == 2:
norm = [
# counter clockwise
# x, y (left hand rule)
(-1, -1), # A
(1, -1), # B
(1, 1), # C
(-1, 1) # D
]
elif len(self.point) == 3:
norm = [
# counter clockwise
# x, y, z (left hand rule)
(-1, -1, 1), # A 0
(1, -1, 1), # B 1
(1, 1, 1), # D 2
(-1, 1, 1), # C 3
(-1, -1, -1), # E 4
(1, -1, -1), # F 5
(1, 1, -1), # H 6
(-1, 1, -1), # G 7
]
return norm
#property
def indices(self):
indices = []
if len(self.point) == 2:
indices = [
[[1, 0, 3], [2, 3, 1]], # BAC CDB front
]
elif len(self.point) == 3:
indices = [
[[1, 0, 3], [2, 3, 1]], # BAC CDB front
[[5, 1, 2], [2, 6, 5]], # FBD DHF right
[[4, 5, 6], [6, 7, 4]], # EFH HGE back
[[5, 4, 0], [0, 1, 5]], # FEA ABF bottom
[[0, 4, 7], [7, 3, 0]], # AEG GCA left
[[2, 3, 7], [7, 6, 2]], # DCG GHD top
]
return indices
#property
def nodes(self):
normals = np.array(self.normals, dtype=np.float32)
scaler = np.array(self.scaler, dtype=np.float32)
nodes = normals * scaler
return nodes.tolist()
#property
def vertices(self):
verts = (n for node in self.nodes for n in node)
return verts
And one more:
# Voxel.py
from collections import Iterable
from time import time
import numpy as np
import pyglet
from pyglet.gl import *
from primatives import Cube
import materials
class Voxel(Cube):
"""
Standard Voxel
"""
def __init__(self, point=None, material=None):
super(Voxel, self).__init__(point=point)
if isinstance(material, materials.Material):
self.material = material
else:
self.material = materials.stone
def __str__(self):
point = ", ".join(str(p) for p in self.point)
material = self.material.name
desc = "<Voxel [%s] (%s)>" % (material, point)
return desc
def __repr__(self):
point = ", ".join(str(p) for p in self.point)
material = self.material.name
desc = "<Voxel %s(%s)>" % (material, point)
return desc
#property
def material(self):
attr = "__material__"
if not hasattr(self, attr):
setattr(self, attr, materials.ether)
return getattr(self, attr)
#material.setter
def material(self, value):
attr = "__material__"
if value in materials.valid_materials:
setattr(self, attr, value)
return getattr(self, attr)
class Chunk(Cube):
"""
A Chunk contains a specified number of Voxels. Chunks are an
optimization to manage voxels which do not change often.
"""
NUMBER = 16
NUMBER_OF_VOXELS_X = NUMBER
NUMBER_OF_VOXELS_Y = NUMBER
NUMBER_OF_VOXELS_Z = NUMBER
def __init__(self, point=None):
point = (0, 0, 0) if point is None else point
super(Chunk, self).__init__(point=point)
self.batch = pyglet.graphics.Batch()
points = []
x_scale = self.NUMBER_OF_VOXELS_X / 2
y_scale = self.NUMBER_OF_VOXELS_Y / 2
z_scale = self.NUMBER_OF_VOXELS_Z / 2
self.rebuild_mesh = True
if len(point) == 1:
points = ((x,) for x in xrange(-x_scale, x_scale))
elif len(point) == 2:
points = ((x, y)
for x in xrange(-x_scale, x_scale)
for y in xrange(-y_scale, y_scale))
elif len(point) == 3:
points = ((x, y, z)
for x in xrange(-x_scale, x_scale)
for y in xrange(-y_scale, y_scale)
for z in xrange(-z_scale, z_scale))
t = time()
self.voxels = dict((point, Voxel(point)) for point in points)
self.active_voxels = dict((p, v)
for p, v in self.voxels.iteritems()
if v.active)
self.inactive_voxels = dict((p, v)
for p, v in self.voxels.iteritems()
if not v.active)
print 'Setup Time: %s' % (time() - t)
#property
def material(self):
return ether
#material.setter
def material(self, value):
if value in materials.valid_materials:
for voxel in self.voxels:
if voxel.material != value:
voxel.material = value
self.rebuild_mesh = True
#property
def mesh(self):
"""
Returns the verticies as defined by the Chunk's Voxels
"""
attr = "__mesh__"
if self.rebuild_mesh == True:
self.mesh_vert_count = 0
vertices = []
t = time()
for point, voxel in self.active_voxels.iteritems():
if voxel.active is True:
vertices.extend(voxel.vertices)
num_verts_in_voxel = len(voxel.normals)
self.mesh_vert_count += num_verts_in_voxel
print "Mesh Generation Time: %s" % time() - t
vertices = tuple(vertices)
setattr(self, attr, vertices)
voxel_count = len(self.active_voxels)
voxel_mesh = self.mesh
count = self.mesh_vert_count
group = None
data = ('v3f/static', vertices)
self.batch.add(count, self.mode, group, data)
return getattr(self, attr)
#property
def center_of_mass(self):
"""
Uses density to calculate center of mass. This is probably only
useful if the chunk represents an object.
"""
center = self.point
points = []
for point, voxel in self.active_voxels.iteritems():
mass = voxel.mass
if mass > 0:
point = [p*mass for p in point]
points.append(point)
points = np.array(points)
means = []
if points.any():
for idx, val in enumerate(self.point):
means.append(np.mean(points[:, idx]))
if means:
center = means
return center
def add(self, voxel):
added = False
point = None
if isinstance(voxel, Voxel):
point = voxel.point
elif isinstance(voxel, Iterable):
point = voxel
if point in self.inactive_voxels.iterkeys():
last = self.voxels[point]
self.voxels[point] = voxel if isinstance(voxel, Voxel) else last
self.voxels[point].active = True
self.active_voxels[point] = self.voxels[point]
self.inactive_voxels.pop(point)
added = True
self.rebuild_mesh = True
return added
def remove(self, voxel):
removed = False
point = None
if isinstance(voxel, Voxel):
point = voxel.point
elif isinstance(voxel, Iterable):
point = voxel
if point in self.active_voxels.iterkeys():
last = self.voxels[point]
self.voxels[point] = voxel if isinstance(voxel, Voxel) else last
self.voxels[point].active = False
self.inactive_voxels[point] = self.voxels[point]
self.active_voxels.pop(point)
removed = True
self.rebuild_mesh = True
return removed
def render(self):
voxels = len(self.active_voxels)
self.batch.draw()
return voxels
if __name__ == "__main__":
import pyglet
from pyglet.gl import *
class Window(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
vox_cnt = self.setup_scene()
print 'Added: %s voxels' % (vox_cnt)
def run(self):
"""wrapper to start the gui loop"""
pyglet.app.run()
def setup_scene(self):
self.chunk = Chunk()
cnt = 0
t = time()
for x in xrange(self.chunk.NUMBER_OF_VOXELS_X):
for y in xrange(self.chunk.NUMBER_OF_VOXELS_Y):
self.chunk.add((x, y))
cnt += 1
print "Setup Scene Time: %s" % (time() - t)
return cnt
def render_scene(self):
y = h = self.height
x = w = self.width
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
# glEnable(GL_DEPTH_TEST)
# glDepthFunc(GL_LESS)
t = time()
voxels_drawn = self.chunk.render()
print 'Render Time: %s' % (time() - t)
print 'Points Rendered %s' % voxels_drawn
# array_len = len(self.vertex_data)
# glDrawArrays(GL_TRIANGLES, 0, array_len)
def on_draw(self, *args, **kwargs):
self.render_scene()
w = Window()
w.run()
There are examples in the source distributions, if you download them (link to page).
The one you want to see is in <top-dir>/examples/opengl.py -- for a torus. If you make the following modifications you will have a cube.
# line 91:
cube.draw() # previously torus.draw()
# line 178: replace the line with the below (GL_TRIANGLES for GL_QUADS)
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, indices)
# line 187:
cube = Cube(0.8) # previously torus = Torus(1, 0.3, 50, 30)
# replace lines 125 through 166 with:
class Cube(object):
Vertices =(0.,0.,0., 1.,0.,0., 0.,0.,1., 1.,0.,1.,
0.,1.,0., 1.,1.,0., 0.,1.,1., 1.,1.,1.)
def __init__(self, scale):
# Create the vertex and normal arrays.
indices = [0,1,3,2, 1,5,7,3, 5,4,6,7,
0,2,6,4, 0,4,5,1, 2,3,7,6]
normals = [ 0.0, -1.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
-1.0, 0.0, 0.0,
0.0, 0.0, -1.0,
0.0, 0.0, 1.0]
vertices = [scale * v for v in Cube.Vertices]
vertices = (GLfloat * len(vertices))(*vertices)
normals = (GLfloat * len(normals))(*normals)
I don't know about python's class wrappers and I did not do any graphics programming for quite some time. But I know that you should search for qualified answers about real workings and internals in the community of hardware guys who either create the VHDL GPU code or write low level drivers or so. They KNOW for sure how it works and some FAQ explanation should be available already in their community.
Based on that assumption this is what some Googling gave me to start with:
OpenGL 4.4 API Reference Card - page 7 - (available among top level resources on http://www.opengl.org) shows some simple picture (for 5 years old?) with the rendering pipeline split into
Blue blocks indicate various buffers that feed or get fed by the OpenGL pipeline
Green blocks indicate fixed function stages
Yellow blocks indicate programmable stages
Jarrred Walton's - Return of the DirectX vs. OpenGL Debates points to a 130-page slideshow How OpenGL Can Unlock 15x Performance Gains | NVIDIA Blog. Both articles fall into categories AMD,Intel,NVIDIA,Game Developer Converence
I have dome some simple OpenGL using C, C++, Delphi long ago and my recommendation is to get rid of the python mapping at first altogether. Look for suitable class library with good community with some good support only afterwards you know what you are looking for.
The above are IMHO the waters to start fishing in

Categories

Resources