Draw half infinite lines? - python

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,

Related

Python Class method to ID intersection of Point w Rectangle uwing set() and Intersection() not working

..trying to define a method within my Point Class that checks interaction with objects of my Rectangle class on interior or boundary using type based dispatch. I tried the code below, but yields: AttributeError: 'set' object has no attribute 'intersects'.
Also, seeking a way to clearly set what intersects at boundary vs. interior. Please advise.
class Point(object):
def __init__(self, x, y, height=0):
self.x = float(x)
self.y = float(y)
self.height = float(height)
def intersects(self, other):
if isinstance(other, Point):
s1=set([self.x, self.y])
s2=set([other.x, other.y])
if s1.intersection(s2):
return True
else:
return False
elif isinstance(other, Rectangle):
s1=set([self.x, self.y])
s2=set(other.pt_ll, other.pt_ur)
if s1.intersection(s2):
return True
else:
return False
class Rectangle(object):
def __init__(self, pt_ll, pt_ur):
"""Constructor.
Takes the lower left and upper right point defining the Rectangle.
"""
self.ll = pt_ll
self.lr = Point(pt_ur.x, pt_ll.y)
self.ur = pt_ur
self.ul = Point(pt_ll.x, pt_ur.y)
these are my calling statements:
pt0 = (.5, .5)
r=Rectangle(Point(0, 0),Point(10, 10))
s1 = set([pt0])
s2 = set([r])
print s1.intersects(s2)
it would be intersection() s1.intersection(s2), you are using a set not a Point object:
s1 = set([pt0]) # <- creates a python set
To use your intersects method you need Point objects:
p = Point(3,5) # <- creates a Point object that has intersects method
p2 = Point(3,5)
print(p.intersects(p2))
So using your example, you need to access the Point objects using the attributes of the Rectangle class:
r = Rectangle(Point(0, 0),Point(10, 10))
print(r.lr.intersects(r.ul)) # <- the Rectangle attributes lr and ul are Point objects because you passed in Point's when you initialised the instance r
You can simplify the assignments in Rectangle:
class Rectangle(object):
def __init__(self, pt_ll, pt_ur):
"""Constructor.
Takes the lower left and upper right point defining the Rectangle.
"""
self.lr = Point(pt_ur.x, pt_ll.y)
self.ul = Point(pt_ll.x, pt_ur.y)
You can also just use set literals:
s1 = {self.x, self.y}
s2 = {other.x, other.y}

python class rectangle new type

I am new to python and I am trying to write class RectangleCollection. class Rectangle is given, and I need to write class RectangleCollection part.
class RectangleCollection has one list instance variable, rectangles, that should initially refer to an empty list.
get_same_area_rects takes a number as a parameter and returns a list of all Rectangles from the rectangles list that have that area.
class Rectangle:
""" A rectangle with a width and height. """
def __init__(self, w, h):
""" (Rectangle, number, number)
Create a new rectangle of width w and height h.
>>> r = Rectangle(1, 2)
>>> r.width
1
>>> r.height
2
"""
self.width = w
self.height = h
def area(self):
""" (Rectangle) -> number
Return the area of this rectangle.
>>> r = Rectangle(10, 20)
>>> r.area()
200
"""
return self.width * self.height
These are what I have done :
class RectangleCollection:
def __init__(self):
""" (RectangleCollection) -> NoneType
>>> rc = RectangleCollection()
>>> rc.rectangles
[]
"""
self.rectangles = []
def get_same_area_rects(self, number):
"""
>>>rc = RectangleCollection()
>>>r1 = Rectangle(10, 20)
>>>r2 = Rectangle(15, 20)
>>> r3 = Rectangle(20, 10)
>>>rc.rectangles.extend([r1, r2, r3])
>>>res = rc.get_same_area_rects(200)
>>>res == [r1, r3]
True
"""
self.number = number
a = self.rectangles.expend(self.area())
if number == self.rectangles.area():
return True
return False
but for get_same_area_rects part, I always get False..
I have no idea what I did wrong. Please help
i think it is because rc.rectangles gives me r1,r2,r3 addresses, not the areas. I should get [200,300,200] but I get the addresses. I think this is why I always get False.. How can I fix this problem?
How about use filter function to only take rectangles whose area is number
def get_same_area_rects(self, number):
return filter(lambda rect: rect.area() == number, self.rectangles)
CSC108 right? This function within class is not asking you to return True of False, it is asking you to call this function to get a list of rectangles that their area is 200
You have a typographic error in your code. It should be:
extend not expend as follows:
a = self.rectangles.extend(self.area())
if number == self.rectangles.extend(self.area()):
return True
Or simply:
a = self.rectangles.extend(self.area())
if number == a:
return True
You have to create a temporary list and then loop over the rectangles. This is because since we have to return a list which has the same area, we would need to use the rectangle.area() to compare if they are true or not and then add into the list.
def get_same_area_rects(self, number):
temp_list = []
for rectangle in self.rectangles:
if number == rectangle.area():
temp_list.append(rectangle)
return temp_list
hope it helps :)

Python raytracing distorts object shapes when close to the camera

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.

What do I do if a method in one class uses a method from another?

I have this class (in a file named "occ_grid.py"):
class Grid:
def __init__(self, width, height, occupancy_value):
self.width = width
self.height = height
self.cells = []
# initialize grid to all specified occupancy value
for row in range(0, self.height):
self.cells.append([])
for col in range(0, self.width):
self.cells[row].append(occupancy_value)
def set_cell(self, point, value):
self.cells[point.y][point.x] = value
def get_cell(self, point):
return self.cells[point.y][point.x]
And I have another class/method(s) (in the file "worldmodel.py"):
class WorldModel:
def __init__(self, num_rows, num_cols, background):
self.background = occ_grid.Grid(num_cols, num_rows, background)
self.num_rows = num_rows
self.num_cols = num_cols
self.occupancy = occ_grid.Grid(num_cols, num_rows, None)
self.entities = []
self.action_queue = ordered_list.OrderedList()
def is_occupied(self, pt):
return (self.within_bounds(pt) and
occ_grid.get_cell(self.occupancy, pt) != None)
Notice how "def is_occupied" uses the method "get_cell". The thing is, "get_cell" is in a different file and it's a method in a different class. I thought about creating a new "grid" object, but I'm confused on where in my code I should create this object.
You're pretty close. It's just:
self.occupancy.get_cell(pt)
You could write:
occ_grid.Grid.get_cell(self.occupancy, pt)
and that would be equivalent -- But, that'd be pretty unidiomatic.
In order to use Grid at all from the file worldmodel.py, you need to import the Grid class.
You can do this in two possible ways.
import occ_grid. If you do this, you will have to access Grid with occ_grid.Grid
from occ_grid import Grid. Then you can use it with just Grid.
mgilson's answer is very clear about how to use it after you import it.

Can I make matplotlib sliders more discrete?

I'm using matplotlib sliders, similar to this demo. The sliders currently use 2 decimal places and 'feel' quite continuous (though they have to be discrete on some level). Can I decide on what level they are discrete? Integer steps? 0.1-sized steps? 0.5? My google-fu failed me.
If you just want integer values, just pass in an approriate valfmt when you create the slider (e.g. valfmt='%0.0f')
However, if you want non-integer invervals, you'll need to manually set the text value each time. Even if you do this, though, the slider will still progress smoothly, and it won't "feel" like discrete intervals.
Here's an example:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider
class ChangingPlot(object):
def __init__(self):
self.inc = 0.5
self.fig, self.ax = plt.subplots()
self.sliderax = self.fig.add_axes([0.2, 0.02, 0.6, 0.03],
axisbg='yellow')
self.slider = Slider(self.sliderax, 'Value', 0, 10, valinit=self.inc)
self.slider.on_changed(self.update)
self.slider.drawon = False
x = np.arange(0, 10.5, self.inc)
self.ax.plot(x, x, 'ro')
self.dot, = self.ax.plot(self.inc, self.inc, 'bo', markersize=18)
def update(self, value):
value = int(value / self.inc) * self.inc
self.dot.set_data([[value],[value]])
self.slider.valtext.set_text('{}'.format(value))
self.fig.canvas.draw()
def show(self):
plt.show()
p = ChangingPlot()
p.show()
If you wanted to make the slider "feel" completely like discrete values, you could subclass matplotlib.widgets.Slider. The key effect is controlled by Slider.set_val
In that case, you'd do something like this:
class DiscreteSlider(Slider):
"""A matplotlib slider widget with discrete steps."""
def __init__(self, *args, **kwargs):
"""Identical to Slider.__init__, except for the "increment" kwarg.
"increment" specifies the step size that the slider will be discritized
to."""
self.inc = kwargs.pop('increment', 0.5)
Slider.__init__(self, *args, **kwargs)
def set_val(self, val):
discrete_val = int(val / self.inc) * self.inc
# We can't just call Slider.set_val(self, discrete_val), because this
# will prevent the slider from updating properly (it will get stuck at
# the first step and not "slide"). Instead, we'll keep track of the
# the continuous value as self.val and pass in the discrete value to
# everything else.
xy = self.poly.xy
xy[2] = discrete_val, 1
xy[3] = discrete_val, 0
self.poly.xy = xy
self.valtext.set_text(self.valfmt % discrete_val)
if self.drawon:
self.ax.figure.canvas.draw()
self.val = val
if not self.eventson:
return
for cid, func in self.observers.iteritems():
func(discrete_val)
And as a full example of using it:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider
class ChangingPlot(object):
def __init__(self):
self.inc = 0.5
self.fig, self.ax = plt.subplots()
self.sliderax = self.fig.add_axes([0.2, 0.02, 0.6, 0.03],
facecolor='yellow')
self.slider = DiscreteSlider(self.sliderax, 'Value', 0, 10,
increment=self.inc, valinit=self.inc)
self.slider.on_changed(self.update)
x = np.arange(0, 10.5, self.inc)
self.ax.plot(x, x, 'ro')
self.dot, = self.ax.plot(self.inc, self.inc, 'bo', markersize=18)
def update(self, value):
self.dot.set_data([[value],[value]])
self.fig.canvas.draw()
def show(self):
plt.show()
class DiscreteSlider(Slider):
"""A matplotlib slider widget with discrete steps."""
def __init__(self, *args, **kwargs):
"""Identical to Slider.__init__, except for the "increment" kwarg.
"increment" specifies the step size that the slider will be discritized
to."""
self.inc = kwargs.pop('increment', 0.5)
Slider.__init__(self, *args, **kwargs)
self.val = 1
def set_val(self, val):
discrete_val = int(val / self.inc) * self.inc
# We can't just call Slider.set_val(self, discrete_val), because this
# will prevent the slider from updating properly (it will get stuck at
# the first step and not "slide"). Instead, we'll keep track of the
# the continuous value as self.val and pass in the discrete value to
# everything else.
xy = self.poly.xy
xy[2] = discrete_val, 1
xy[3] = discrete_val, 0
self.poly.xy = xy
self.valtext.set_text(self.valfmt % discrete_val)
if self.drawon:
self.ax.figure.canvas.draw()
self.val = val
if not self.eventson:
return
for cid, func in self.observers.items():
func(discrete_val)
p = ChangingPlot()
p.show()
If you would rather not subclass the Slider, I picked a few lines off #Joe Kington's answer to accomplish the discretization within the callback function:
sldr = Slider(ax,'name',0.,5.,valinit=0.,valfmt="%i")
sldr.on_changed(partial(set_slider,sldr))
and then:
def set_slider(s,val):
s.val = round(val)
s.poly.xy[2] = s.val,1
s.poly.xy[3] = s.val,0
s.valtext.set_text(s.valfmt % s.val)

Categories

Resources