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 :)
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.
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)