In [1]:
import numpy as np
from numpy import random
from matplotlib import pyplot as plt
from numpy import linalg as la
from enum import Enum
In [2]:
class ProgressBar(object):
def __init__(self, total, prefix='', suffix='', decimals=1, length=100, fill='█'):
"""
Creates terminal progress bar instance.
@params:
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimal - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int):
fill - Optional : bar fill character (Str)
"""
self.total = total
self.prefix = prefix
self.suffix = suffix
self.decimals = decimals
self.length = length
self.fill = fill
def print_bar(self, iteration):
"""
Updates progress bar status.
@params:
iteration - Required : current iteration (Int)
"""
percent = ("{0:." + str(self.decimals) + "f}").format(
100 * (iteration / float(self.total)))
filled_length = int(self.length * iteration // self.total)
bar = self.fill * filled_length + '-' * (self.length - filled_length)
print('\r{} |{}| {}% {}'.format(self.prefix, bar, percent, self.suffix), end='\r')
# print a new line on complete
if iteration == self.total:
print()
# def test_progress_bar(n_items=50, length=50, sleep_interval=.2):
# from time import sleep
# pb = ProgressBar(n_items, prefix='Progress:', suffix='Complete', length=length)
# for i in range(0, n_items):
# sleep(sleep_interval)
# pb.print_bar(i)
# pb.print_bar(n_items)
# test_progress_bar(57)
In [3]:
def normalize(x):
x /= la.norm(x)
return x
class Shape(object):
def get_color(self, surface_point=None):
pass
def get_normal(self, surface_point):
pass
def intersect(self, start, direction):
return np.inf
class Sphere(Shape):
def __init__(self, center, radius, surface_color, reflection=0,
diffuse_color=1., specular_color=1., transparency=0, ior=1.1):
self.center = center
self.radius = radius
self.surface_color = surface_color
self.reflection = reflection
self.diffuse_color = diffuse_color
self.specular_color = specular_color
self.transparency = transparency
self.ior = ior
def get_color(self, surface_point=None):
"""
Return a color of a distinct ``surface_point``. If the sphere has
only one color the ``surface_point`` affects nothing and can be
omitted.
@params:
surface_point : a distinct point on the sphere surface that
color is required.
"""
if hasattr(self.surface_color, '__len__'):
return self.surface_color
else:
return self.surface_color(surface_point)
def get_normal(self, surface_point):
return normalize(surface_point - self.center)
def intersect(self, start, direction):
v = self.center - start
vd = np.dot(direction, v)
v_r = np.dot(v, v) - self.radius ** 2
disc = vd ** 2 - v_r
if disc > 0:
root = np.sqrt(disc)
t0, t1 = vd - root, vd + root
if t0 > 0:
return t0
elif t1 > 0:
return t1
return np.inf
class Plane(Shape):
def __init__(self, init_point, normal, surface_color, reflection=0, diffuse_color=1., specular_color=1., transparency=0):
self.init_point = init_point
self.normal = normal
self.surface_color = surface_color
self.reflection = reflection
self.diffuse_color = diffuse_color
self.specular_color = specular_color
self.transparency = transparency
def get_color(self, surface_point=None):
"""
Return a color of a distinct ``surface_point``. If the plane has
only one color the ``surface_point`` affects nothing and can be
omitted.
@params:
surface_point : a distinct point on the sphere surface that
color is required.
"""
if hasattr(self.surface_color, '__len__'):
return self.surface_color
else:
return self.surface_color(surface_point)
def get_normal(self, surface_point):
return self.normal
def intersect(self, start, direction):
denom = np.dot(direction, self.normal)
if np.abs(denom) < 1e-6:
return np.inf
d = np.dot(self.init_point - start, self.normal) / denom
if d < 0:
return np.inf
return d
In [4]:
class Id(object):
"""
ID generator - a singleton class.
Example:
ID = Id()
Id.get()
"""
class __Id:
def __init__(self):
self.generator = self.id_gen()
def id_gen(self):
id_ = 0
while True:
yield id_
id_ += 1
def get(self):
return next(self.generator)
def reset(self):
self.generator = self.id_gen()
instance = None
def __new__(cls):
if not Id.instance:
Id.instance = Id.__Id()
return Id.instance
def __getattr__(self, name):
return getattr(self.instance, name)
def __setattr__(self, name):
return setattr(self.instance, name)
class Ray(object):
class Type(Enum):
CAMERA = 0
REFLECTED = 1
REFRACTED = 2
def __init__(self, start, direction, att=1., cum_att=1., inside=False, type_=Type.CAMERA):
"""
Creates a new instance of ``Ray``.
@params:
ior : an index of refraction of the media
inside : show whether ray is inside of the shape or not .
"""
self.id = Id().get()
self.type = type_
# geometrical properties
self.start = start
self.direction = direction
self.end = None
self.length = -1
# Propagation parameters including transmission media change
self.att = att
self.cum_att = cum_att
self.inside = inside
def __str__(self):
string = '<{}({}) start={} end={} dir={} len={:.4}; att={:.4}({:.4}) ior={:.4}>'
return string.format(self.type.name, self.id, self.start, self.end,
self.direction, self.length, self.att,
self.cum_att, self.inside)
class RayNode(object):
def __init__(self, ray, parent=None, tree=None):
self.ray = ray
self.parent = parent
self.reflected = None
self.refracted = None
self.tree=None
self.id = ray.id
self.depth = parent.depth + 1 if parent else 0
def __str__(self):
reflected_id = str(self.reflected.ray.id) if self.reflected else '.'
refracted_id = str(self.refracted.ray.id) if self.refracted else '.'
self_id = str(self.ray.id)
return '({})<({})>({})'.format(refracted_id, self_id, reflected_id)
def add_child(self, ray):
node = RayNode(ray, parent=self, tree=tree)
if ray.type is Ray.Type.REFLECTED:
self.reflected = node
elif ray.type is Ray.Type.REFRACTED:
self.refracted = node
def view(self):
refracted_view = self.refracted.view() if self.refracted else '.'
reflected_view = self.reflected.view() if self.reflected else '.'
return '[{}]<({})>[{}]'.format(refracted_view, str(self.ray.id), reflected_view)
class RayTree(object):
def __init__(self, camera_ray):
self.root = RayNode(camera_ray, tree=self)
self.rays = dict((camera_ray.id, camera_ray))
self.levels = dict((0, camera_ray))
def __str__(self):
return camera_ray.view()
In [5]:
BG_COLOR = np.array([0.05, 0., 0.2])
class RayTracer(object):
def __init__(self, scene, light_point, color_light, cam_point, ambient, specular_k):
self.scene = scene
self.light_point = light_point
self.color_light = color_light
self.cam_point = cam_point
self.ambient = ambient
self.specular_k = specular_k
# def
def trace_ray(self, ray_start, ray_dir):
"""
The method computes find nearest intersected objects, compute ray
parameters and color.
"""
# Find the nearest intersection point with the scene.
t = np.inf
for s in self.scene:
t_i = s.intersect(ray_start, ray_dir)
if t_i < t:
t, shape = t_i, s
# Return None if the ray does not intersect any object.
if t == np.inf:
return
# Find the intersection point on the object and properties of the object.
hit_point = ray_start + ray_dir * t
normal = shape.get_normal(hit_point)
color = shape.get_color(hit_point)
to_light = normalize(self.light_point - hit_point)
to_cam = normalize(self.cam_point - hit_point)
# Shadow: find if the point is shadowed or not.
bias = normal * .0001
t_next = np.inf
for s in self.scene:
if s != shape:
t_i = s.intersect(hit_point + bias, to_light)
if t_i < t_next:
t_next, shape_next = t_i, s
shade_attenuation = 1
if t_next < np.inf:
if shape_next.transparency == 0:
return
else:
shade_attenuation = 1 - shape_next.transparency
# l = [s.intersect(hit_point + bias, to_light) for s in self.scene if s != shape]
# if l and min(l) < np.inf:
# return
# Start computing the color.
ray_color = self.ambient
# Lambert shading (diffuse).
ray_color += shape.diffuse_color * max(np.dot(normal, to_light), 0) * color
# Blinn-Phong shading (specular).
ray_color += shape.specular_color * (max(np.dot(normal, normalize(to_light + to_cam)), 0)
** self.specular_k * self.color_light)
return shape, hit_point, normal, ray_color * shade_attenuation
def trace_path(self, ray_start, ray_dir):
color = np.zeros(3)
# Define a list of rays to be traced to enable tracing both reflecting and refracting rays.
# Thus tracing procedures perform width-first traversal.
primary_ray = Ray(start=ray_start, direction=ray_dir)
rays_to_trace = [(primary_ray, 0)]
while rays_to_trace:
ray, depth = rays_to_trace.pop(0)
# If ray tracing reaches depth limit stop tracing this branch.
if depth == DEPTH_MAX:
continue
traced = self.trace_ray(ray.start, ray.direction)
if not traced:
color += BG_COLOR
continue
shape, hit_point, normal, ray_color = traced
bias = normal * .0001
neg_dn = -np.dot(ray.direction, normal)
# Reflection: create a new ray and append it to the rays list to trace further.
start = hit_point + bias
direction = ray.direction + 2 * neg_dn * normal
reflected_ray = Ray(start=start, direction=direction, att=shape.reflection,
cum_att = ray.cum_att * shape.reflection,
inside=ray.inside, type_=Ray.Type.REFLECTED)
rays_to_trace.append((reflected_ray, depth + 1))
# Refraction: create a new ray and append it to the rays list to trace further.
if shape.transparency:
eta = shape.ior if ray.inside else 1 / shape.ior
k = 1 - eta ** 2 * (1 - neg_dn ** 2)
if k >= 0: # total reflection under refraction into the environment with less ior
start = hit_point - bias
direction = ray.direction * eta + normal * (eta * neg_dn - np.sqrt(k))
refracted_ray = Ray(start=start, direction=direction, att=shape.transparency,
cum_att = ray.cum_att * shape.transparency,
inside=not ray.inside, type_=Ray.Type.REFRACTED)
rays_to_trace.append((refracted_ray, depth + 1))
fresnel_effect = .1 + (1-.1) * (1 - neg_dn) ** 3
if ray.type == Ray.Type.REFLECTED:
pass
elif ray.type == Ray.Type.REFRACTED:
fresnel_effect = 1 - fresnel_effect
else:
fresnel_effect = 1
color += ray.cum_att * ray_color
# Compute current color.
# color += ray.cum_att * ray_color
return color
In [6]:
class Render(object):
def __init__(self, scene, light_point, color_light,
specular_k, ambient, cam_point,
screen, width, height):
self.scene = scene
self.light_point = light_point
self.color_light = color_light
self.specular_k = specular_k
self.ambient = ambient
self.cam_point = cam_point
self.screen = screen
self.width = width
self.height= height
def render(self):
image = np.zeros((self.height, self.width, 3))
cam_view = np.array([0., 0., 0.]) # Camera pointing to.
tracer = RayTracer(self.scene, self.light_point, self.color_light,
self.cam_point, self.ambient, self.specular_k)
# Create a progress bar.
pb_items = self.width / 10
pb = ProgressBar(pb_items, prefix='Progress:', suffix='Complete', length=50)
for i, x in enumerate(np.linspace(self.screen[0], self.screen[2], self.width)):
if i % 10 == 0:
pb.print_bar(i / 10)
for j, y in enumerate(np.linspace(self.screen[1], self.screen[3], self.height)):
cam_view[:2] = (x, y)
cam_dir = normalize(cam_view - self.cam_point)
color = tracer.trace_path(self.cam_point, cam_dir)
image[self.height - j - 1, i, :] = np.clip(color, 0, 1)
pb.print_bar(pb_items)
return image
In [7]:
class SceneBuilder(object):
def add_sphere(center, radius, surface_color, reflection=0):
return Sphere(center=np.array(center), radius=np.array(radius),
surface_color=np.array(surface_color), reflection=.5)
def add_transparent_sphere(center, radius, surface_color, reflection=0):
return Sphere(center=np.array(center), radius=np.array(radius),
surface_color=np.array(surface_color), reflection=.1,
transparency=0.9, ior=1.1)
def add_plane(init_point, normal, reflection=0):
color_0 = 1. * np.ones(3)
color_1 = 0. * np.ones(3)
color = lambda point: (color_0
if (int(point[0] * 2) % 2) == (int(point[2] * 2) % 2)
else color_1)
return Plane(init_point=np.array(init_point), normal=np.array(normal),
surface_color=color, diffuse_color=.75,
specular_color=.5, reflection=.15)
def build():
return [SceneBuilder.add_transparent_sphere([.75, .3, 1.], .8, [0., 0., 1.]),
# SceneBuilder.add_sphere([.75, .1, 1.], .8, [0., 0., 1.]),
SceneBuilder.add_sphere([-.75, .3, 2.25], .8, [.8, .223, .2]),
SceneBuilder.add_sphere([-2.75, .3, 3.5], .8, [1., .472, .184]),
SceneBuilder.add_plane([0., -.5, 0.], [0., 1., 0.]),
]
In [37]:
WIDTH = 400
HEIGHT = 300
DEPTH_MAX = 14
# Screen coordinates: x0, y0, x1, y1.
ASPECT_RATIO = float(WIDTH) / HEIGHT
SCREEN = (-1., -1. / ASPECT_RATIO + .25, 1., 1. / ASPECT_RATIO + .25)
render = Render(scene=SceneBuilder.build(),
light_point=np.array([5., 5., -10.]),
color_light=np.ones(3),
specular_k=50,
ambient=.05,
cam_point=np.array([0., 0.35, -1.]),
screen=SCREEN,
width=WIDTH,
height=HEIGHT)
image = render.render()
# print(image.shape)
# def do_antialiasing(image):
# H2 = int(np.ceil(HEIGHT / 2))
# W2 = int(np.ceil(WIDTH / 2))
# img = np.zeros((H2, W2, 3))
# print(image.shape)
# print(img.shape)
# for i in range(H2):
# for j in range(W2):
# # 1 print(i * 2, j * 2)
# img[i, j, :] = .25 * (image[i * 2, j * 2, :] +
# image[i * 2 + 1, j * 2, :] +
# image[i * 2, j * 2 + 1, :] +
# image[i * 2 + 1, j * 2 + 1, :])
# return img
plt.imsave('fig1.png', image)
# plt.imsave('fig1-anti.png', do_antialiasing(image))
In [34]:
# image = np.array(image_old, copy=True)
# def srgb_encode(color, gamma):
# assert(color.shape == (3,))
# for i in range(3):
# if color[i] < 0.0031308:
# color[i] = 12.92 * color[i]
# else:
# color[i] = 1.055 * color[i] ** (1 / gamma) - 0.055 # inverse gamma 2.4
# return color
# def to_srgb(image, gamma):
# height = image.shape[0]
# width = image.shape[1]
# image = image.reshape(height * width, 3)
# for i, color in enumerate(image):
# # print(color)
# image[i] = srgb_encode(color, gamma)
# # print(image[i])
# return image.reshape(height, width, 3)
# plt.imsave('fig1-gamma.png', to_srgb(image, 0.5))
# # plt.imsave('fig1.png', image)
In [9]:
def id_gen():
id_ = 0
while True:
yield id_
id_ += 1
get_id = id_gen()
print(next(id_gen()))
print(next(get_id))
print(next(get_id))
print(next(get_id))
print(next(get_id))
In [12]:
# class Singleton(type):
# """
# Metaclass for all singletons in the system (e.g. Kernel).
# The example code is taken from
# http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html
# """
# instance = None
# def __init__(cls, name, bases, attrs):
# super().__init__(name, bases, attrs)
# def __call__(cls, *args, **kw):
# if not cls.instance:
# cls.instance = super(Singleton, cls).__call__(*args, **kw)
# return cls.instance
# class Dispatcher(object, metaclass=Singleton):
In [13]:
class Id(object):
class __Id:
def __init__(self):
self.generator = self.id_gen()
def id_gen(self):
id_ = 0
while True:
yield id_
id_ += 1
def get(self):
return next(self.generator)
def reset(self):
self.generator = self.id_gen()
instance = None
def __new__(cls):
if not Id.instance:
Id.instance = Id.__Id()
return Id.instance
def __getattr__(self, name):
return getattr(self.instance, name)
def __setattr__(self, name):
return setattr(self.instance, name)
print(Id().get())
print(Id().get())
print(Id().get())
ID = Id()
Id().reset()
print(ID.get())
print(ID.get())
print(ID.get())
print(ID.get())
print(ID.get())
ID = Id()
print(ID.get())
print(ID.get())
print(ID.get())
print(ID.get())
print(ID.get())
In [5]:
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
print_time(self.name, self.counter, 5)
print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ('{}: {}'.format(threadName, time.ctime(time.time())))
counter -= 1
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")