blinn-phong shading with numpy - python

I am trying to implement blinn-phong shading in numpy for educational purposes. However I am stuck at debugging what parameters are doing for several days now.
My general idea was the following. Since the equation was given for a channel. I apply the model to each color channel to get the relative pixel intensities in the channel, then regroup the channels back togather to have all the image.
My lambertian coefficiant does not seem to take into account the light position changes, but it does change the pixel intensity but other parameters have almost no effect on the output.
Any help would be appreciated.
Here are the relative bits of the code (full code is here for anyone interested):
def normalize_1d_array(arr):
"Normalize 1d array"
assert arr.ndim == 1
result = None
if np.linalg.norm(arr) == 0:
result = arr
else:
result = arr / np.linalg.norm(arr)
return result
def normalize_3col_array(arr):
"Normalize 3 column array"
assert arr.shape[1] == 3
assert arr.ndim == 2
normal = np.copy(arr)
normal[:, 0] = normalize_1d_array(normal[:, 0])
normal[:, 1] = normalize_1d_array(normal[:, 1])
normal[:, 2] = normalize_1d_array(normal[:, 2])
return normal
def get_vector_dot(arr1, arr2):
"Get vector dot product for 2 matrices"
assert arr1.shape == arr2.shape
newarr = np.sum(arr1 * arr2, axis=1, dtype=np.float32)
return newarr
class LightSource:
"Simple implementation of a light source"
def __init__(self,
x=10.0, # x
y=5.0, # y
z=0.0, # light source at infinity
intensity=1.0, # I_p
ambient_intensity=1.0, # I_a
ambient_coefficient=0.1, # k_a
light_power=80.0):
"light source"
self.x = x
self.y = y
if z is not None:
assert isinstance(z, float)
self.z = z
self.intensity = intensity
self.power = light_power
self.ambient_intensity = ambient_intensity # I_a
self.ambient_coefficient = ambient_coefficient # k_a
# k_a can be tuned if the material is known
def copy(self):
"copy self"
return LightSource(x=self.x,
y=self.y,
z=self.z,
intensity=self.intensity,
light_power=self.power)
class ChannelShader:
"Shades channels"
def __init__(self,
coordarr: np.ndarray,
light_source: LightSource, # has I_a, I_p, k_a
surface_normal: np.ndarray,
imagesize: (int, int),
color: np.ndarray, # they are assumed to be O_d and O_s
spec_coeff=0.5, # k_s
screen_gamma=2.2,
diffuse_coeff=0.9, # k_d
attenuation_c1=2.0, # f_attr c1
attenuation_c2=0.0, # f_attr c2 d_L coefficient
attenuation_c3=0.0, # f_attr c3 d_L^2 coefficient
shininess=270.0 # n
):
self.light_source = light_source
self.light_intensity = self.light_source.intensity # I_p
self.ambient_coefficient = self.light_source.ambient_coefficient # k_a
self.ambient_intensity = self.light_source.ambient_intensity # I_a
self.coordarr = coordarr
self.surface_normal = np.copy(surface_normal)
self.screen_gamma = screen_gamma
self.shininess = shininess
self.diffuse_coeff = diffuse_coeff # k_d
self.diffuse_color = normalize_1d_array(color) # O_d: object diffuse color
self.spec_color = normalize_1d_array(color) # O_s: object specular color
self.spec_coeff = spec_coeff # k_s: specular coefficient
self.imsize = imagesize
self.att_c1 = attenuation_c1
self.att_c2 = attenuation_c2
self.att_c3 = attenuation_c3
def copy(self):
return ChannelShader(coordarr=np.copy(self.coordarr),
light_source=self.light_source.copy(),
surface_normal=np.copy(self.surface_normal),
color=np.copy(self.diffuse_coeff) * 255.0)
#property
def distance(self):
yarr = self.coordarr[:, 0] # row nb
xarr = self.coordarr[:, 1] # col nb
xdist = (self.light_source.x - xarr)**2
ydist = (self.light_source.y - yarr)**2
return xdist + ydist
#property
def distance_factor(self):
resx = self.imsize[1]
factor = self.distance / self.light_source.z * resx
return 1.0 - factor
#property
def light_direction(self):
"get light direction matrix (-1, 3)"
yarr = self.coordarr[:, 0]
xarr = self.coordarr[:, 1]
xdiff = self.light_source.x - xarr
ydiff = self.light_source.y - yarr
light_matrix = np.zeros((self.coordarr.shape[0], 3))
light_matrix[:, 0] = ydiff
light_matrix[:, 1] = xdiff
light_matrix[:, 2] = self.light_source.z
# light_matrix[:, 2] = 0.0
return light_matrix
#property
def light_attenuation(self):
"""
Implementing from Foley JD 1996, p. 726
f_att : light source attenuation function:
f_att = min(\frac{1}{c_1 + c_2{\times}d_L + c_3{\times}d^2_{L}} , 1)
"""
second = self.att_c2 * self.distance
third = self.att_c3 * self.distance * self.distance
result = self.att_c1 + second + third
result = 1 / result
return np.where(result < 1, result, 1)
#property
def normalized_light_direction(self):
"Light Direction matrix normalized"
return normalize_3col_array(self.light_direction)
#property
def normalized_surface_normal(self):
return normalize_3col_array(self.surface_normal)
#property
def costheta(self):
"set costheta"
# pdb.set_trace()
costheta = get_vector_dot(
arr1=self.normalized_light_direction,
arr2=self.normalized_surface_normal)
# products of vectors
costheta = np.abs(costheta) # as per (Foley J.D, et.al. 1996, p. 724)
return costheta
#property
def ambient_term(self):
"Get the ambient term I_a * k_a * O_d"
term = self.ambient_coefficient * self.ambient_intensity
return term * self.diffuse_color
#property
def view_direction(self):
"Get view direction"
# pdb.set_trace()
cshape = self.coordarr.shape
coord = np.zeros((cshape[0], 3)) # x, y, z
coord[:, :2] = -self.coordarr
coord[:, 2] = 0.0 # viewer at infinity
coord = normalize_3col_array(coord)
return coord
#property
def half_direction(self):
"get half direction"
# pdb.set_trace()
arr = self.view_direction + self.normalized_light_direction
return normalize_3col_array(arr)
#property
def spec_angle(self):
"get spec angle"
specAngle = get_vector_dot(
arr1=self.half_direction,
arr2=self.normalized_surface_normal)
return np.where(specAngle > 0.0, specAngle, 0.0)
#property
def specular(self):
return self.spec_angle ** self.shininess
#property
def channel_color_blinn_phong(self):
"""compute new channel color intensities
Implements: Foley J.D. 1996 p. 730 - 731, variation on equation 16.15
"""
second = 1.0 # added for structuring code in this fashion, makes
# debugging easier
# lambertian terms
second *= self.diffuse_coeff # k_d
second *= self.costheta # (N \cdot L)
second *= self.light_intensity # I_p
# adding phong terms
second *= self.light_attenuation # f_attr
second *= self.diffuse_color # O_d
third = 1.0
third *= self.spec_color # O_s
third *= self.specular # (N \cdot H)^n
third *= self.spec_coeff # k_s
result = 0.0
result += self.ambient_term # I_a × k_a × O_d
result += second
result += third
pdb.set_trace()
return result
Thanks

Well after all, the implementation did not have a lot of problems, however the images I was working with required very weird values for parameters due to their specific condition in which they are produced.
Most images I have used contained rough surfaces with unglazed clay as material and the images were taken in a controlled environment with a single source of light, contrary to real world environments where objects are illuminated from multiple light spots.
So most of the parameters about ambient illumination and specular reflection did not make much sense in usage.
I am putting here the relative parts of my implementation as a reference for future users, be sure NOT to use default values.
Some details about the implementation:
It largely follows the equation specified in Foley J.D. et.al., 1996, p. 730 - 731, no 16.15 with the variation of adding a halfway vector, required for blinn-phong.
ChannelShader expects the following:
Coordinates of channel pixels of shape: (-1, 2)
Surface normals of shape: (-1, 3)
Channel colors of shape: (-1,)
A light source of the type LightSource
As stated above, please do change the default values before proceeding with experimentation.
You can use same surface normals for each channel, if you are shading multiple channels.
Final word of caution, it is noticably slow, even with numpy.
The proper way to render shading is gpu based libraries like pyopengl etc. I have not tested it with gpu ports of numpy, like cupy, nor with other libraries like numba etc though:
def normalize_1d_array(arr):
"Normalize 1d array"
assert arr.ndim == 1
result = None
if np.linalg.norm(arr) == 0:
result = arr
else:
result = arr / np.linalg.norm(arr)
return result
def normalize_3col_array(arr):
"Normalize 3 column array"
assert arr.shape[1] == 3
assert arr.ndim == 2
normal = np.copy(arr)
normal[:, 0] = normalize_1d_array(normal[:, 0])
normal[:, 1] = normalize_1d_array(normal[:, 1])
normal[:, 2] = normalize_1d_array(normal[:, 2])
return normal
def get_vector_dot(arr1, arr2):
"Get vector dot product for 2 matrices"
assert arr1.shape == arr2.shape
newarr = np.sum(arr1 * arr2, axis=1, dtype=np.float32)
return newarr
class ImageArray:
"Image array have some additional properties besides np.ndarray"
def __init__(self, image: np.ndarray):
assert isinstance(image, np.ndarray)
self.image = image
#property
def norm_coordinates(self):
"Get normalized coordinates of the image pixels"
# pdb.set_trace()
rownb, colnb = self.image.shape[0], self.image.shape[1]
norm = np.empty_like(self.coordinates, dtype=np.float32)
norm[:, 0] = self.coordinates[:, 0] / rownb
norm[:, 1] = self.coordinates[:, 1] / colnb
return norm
#property
def norm_image(self):
"Get normalized image with pixel values divided by 255"
return self.image / 255.0
#property
def coordinates(self):
"Coordinates of the image pixels"
rownb, colnb = self.image.shape[:2]
coords = [[(row, col) for col in range(colnb)] for row in range(rownb)]
coordarray = np.array(coords)
return coordarray.reshape((-1, 2))
#property
def arrshape(self):
"get array shape"
return self.image.shape
#property
def flatarr(self):
"get flattened array"
return self.image.flatten()
def interpolateImage(imarr: ImageArray):
"Interpolate image array"
imshape = imarr.image.shape
newimage = imarr.image.flatten()
newimage = np.uint8(np.interp(newimage,
(newimage.min(),
newimage.max()),
(0, 255))
)
newimage = newimage.reshape(imshape)
return ImageArray(newimage)
class LightSource:
"Simple implementation of a light source"
def __init__(self,
x=1.0, # x
y=1.0, # y
z=20.0, # light source distance: 0 to make it at infinity
intensity=1.0, # I_p
ambient_intensity=1.0, # I_a
ambient_coefficient=0.000000002, # k_a
):
"light source"
self.x = x
self.y = y
if z is not None:
assert isinstance(z, float)
self.z = z
self.intensity = intensity
self.ambient_intensity = ambient_intensity # I_a
self.ambient_coefficient = ambient_coefficient # k_a
# k_a can be tuned if the material is known
def copy(self):
"copy self"
return LightSource(x=self.x,
y=self.y,
z=self.z,
intensity=self.intensity,
light_power=self.power)
class ChannelShader:
"Shades channels"
def __init__(self,
coordarr: np.ndarray,
light_source: LightSource, # has I_a, I_p, k_a
surface_normal: np.ndarray,
color: np.ndarray, # they are assumed to be O_d and O_s
spec_coeff=0.1, # k_s
spec_color=1.0, # O_s: obj specular color. It can be
# optimized with respect to surface material
screen_gamma=2.2,
diffuse_coeff=0.008, # k_d
# a good value is between 0.007 and 0.1
attenuation_c1=1.0, # f_attr c1
attenuation_c2=0.0, # f_attr c2 d_L coefficient
attenuation_c3=0.0, # f_attr c3 d_L^2 coefficient
shininess=20.0 # n
):
self.light_source = light_source
self.light_intensity = self.light_source.intensity # I_p
self.ambient_coefficient = self.light_source.ambient_coefficient # k_a
self.ambient_intensity = self.light_source.ambient_intensity # I_a
self.coordarr = coordarr
self.surface_normal = np.copy(surface_normal)
self.screen_gamma = screen_gamma
self.shininess = shininess
self.diffuse_coeff = diffuse_coeff # k_d
# self.diffuse_color = normalize_1d_array(color) # O_d: obj diffuse color
self.diffuse_color = color # O_d: obj diffuse color
self.spec_color = spec_color # O_s
self.spec_coeff = spec_coeff # k_s: specular coefficient
self.att_c1 = attenuation_c1
self.att_c2 = attenuation_c2
self.att_c3 = attenuation_c3
def copy(self):
return ChannelShader(coordarr=np.copy(self.coordarr),
light_source=self.light_source.copy(),
surface_normal=np.copy(self.surface_normal),
color=np.copy(self.diffuse_color))
#property
def distance(self):
yarr = self.coordarr[:, 0] # row nb
xarr = self.coordarr[:, 1] # col nb
xdist = (self.light_source.x - xarr)**2
ydist = (self.light_source.y - yarr)**2
return xdist + ydist
#property
def light_direction(self):
"get light direction matrix (-1, 3)"
yarr = self.coordarr[:, 0]
xarr = self.coordarr[:, 1]
xdiff = self.light_source.x - xarr
ydiff = self.light_source.y - yarr
light_matrix = np.zeros((self.coordarr.shape[0], 3))
light_matrix[:, 0] = ydiff
light_matrix[:, 1] = xdiff
light_matrix[:, 2] = self.light_source.z
# light_matrix[:, 2] = 0.0
# pdb.set_trace()
return light_matrix
#property
def light_attenuation(self):
"""
Implementing from Foley JD 1996, p. 726
f_att : light source attenuation function:
f_att = min(\frac{1}{c_1 + c_2{\times}d_L + c_3{\times}d^2_{L}} , 1)
"""
second = self.att_c2 * self.distance
third = self.att_c3 * self.distance * self.distance
result = self.att_c1 + second + third
result = 1 / result
return np.where(result < 1, result, 1)
#property
def normalized_light_direction(self):
"Light Direction matrix normalized"
return normalize_3col_array(self.light_direction)
#property
def normalized_surface_normal(self):
return normalize_3col_array(self.surface_normal)
#property
def costheta(self):
"set costheta"
# pdb.set_trace()
costheta = get_vector_dot(
arr1=self.normalized_light_direction,
arr2=self.normalized_surface_normal)
# products of vectors
# costheta = np.abs(costheta) # as per (Foley J.D, et.al. 1996, p. 724)
costheta = np.where(costheta > 0, costheta, 0)
return costheta
#property
def ambient_term(self):
"Get the ambient term I_a * k_a * O_d"
term = self.ambient_coefficient * self.ambient_intensity
term *= self.diffuse_color
# pdb.set_trace()
return term
#property
def view_direction(self):
"Get view direction"
# pdb.set_trace()
cshape = self.coordarr.shape
coord = np.zeros((cshape[0], 3)) # x, y, z
coord[:, :2] = -self.coordarr
coord[:, 2] = 0.0 # viewer at infinity
coord = normalize_3col_array(coord)
return coord
#property
def half_direction(self):
"get half direction"
# pdb.set_trace()
arr = self.view_direction + self.normalized_light_direction
return normalize_3col_array(arr)
#property
def spec_angle(self):
"get spec angle"
specAngle = get_vector_dot(
arr1=self.half_direction,
arr2=self.normalized_surface_normal)
return np.where(specAngle > 0.0, specAngle, 0.0)
#property
def specular(self):
return self.spec_angle ** self.shininess
#property
def channel_color_blinn_phong(self):
"""compute new channel color intensities
Implements: Foley J.D. 1996 p. 730 - 731, variation on equation 16.15
"""
second = 1.0 # added for structuring code in this fashion, makes
# debugging easier
# lambertian terms
second *= self.diffuse_coeff # k_d
second *= self.costheta # (N \cdot L)
second *= self.light_intensity # I_p
# adding phong terms
second *= self.light_attenuation # f_attr
second *= self.diffuse_color # O_d
third = 1.0
third *= self.spec_color # O_s
third *= self.specular # (N \cdot H)^n
third *= self.spec_coeff # k_s
result = 0.0
#
result += self.ambient_term # I_a × k_a × O_d
result += second
result += third
# pdb.set_trace()
return result

Related

TypeError: unsipported operand type(s) for -: 'tuple' and 'int'

I am trying to modifying a python codes. Basically I made an GUI to rotate an object inside the model in one direction only (z axis). The code was written to handle vector. However, I need to use as orientation matrix. It is written like this:
def OrientationFromRxRyRzAngles(RxRyRzAnglesInRadians):
S1, S2, S3 = map(sin, RxRyRzAnglesInRadians)
C1, C2, C3 = map(cos, RxRyRzAnglesInRadians)
return (
(+C2 * C3, +C2 * S3, -S2), # components in global axes directions, of unit vector in vessel x-axis direction
# components in global axes directions, of unit vector in vessel y-axis direction
(-C1 * S3 + S1 * S2 * C3, +C1 * C3 + S1 * S2 * S3, +S1 * C2),
# components in global axes directions, of unit vector in vessel z-axis direction
(+S1 * S3 + C1 * S2 * C3, -S1 * C3 + C1 * S2 * S3, +C1 * C2)
)
class ExternallyCalculatedRotation(object):
def Initialise(self, info):
# Settings for outputting results to external output at a set period in seconds
self.StartTime = 1
self.TimeStep = info.Model['General'].ImplicitConstantTimeStep
# Settings for outputting results to external output at a set period in seconds
self.StartTime = 1
self.TimeStep = info.Model['General'].ImplicitConstantTimeStep
# Control object names in the model
self.SupportMaster = "Constraint1"
# Input to define the scale for the GUI. [Piston offset, Support offset, tension]
self.GUItext = 'Support Offset (m)'
self.ScaleMax = 100.0 # Scale maximum. Units: mm, m
self.ScaleMin = 0 # Scale minimum. Units: mm, m
self.RateMaxList = 0.30 # Maximum rate of cchange. Units: m/s. Note tension does not need to be gradually changed.
self.ScaleRes = 0.01 # Slider scale resolution. Units: mm, m
# Control settings for changing support offset
if info.ModelObject.Name == self.SupportMaster:
self.RateMax = self.RateMaxList # m per second
self.RateStep = self.RateMax * self.TimeStep # m per time step
self.maxOffset = self.ScaleMax # Defines the upper limit of the control slider scale
self.TargetOffset = self.ScaleMin
self.TargetOffsetScale = 0
info.Workspace['supportdata'] = self.TargetOffsetScale # Workspace allows sharing of data between model objects
# Define the PySimpleGUI slider
# We only want to trigger the GUI once to create a single interface window. Workspace is used to share data between mode objects.
if info.ModelObject.Name == self.SupportMaster:
gui.theme('DarkAmber')
layout = [
[gui.Text(self.GUItext)],
[gui.Slider(range=(self.ScaleMin, self.ScaleMax), default_value=self.ScaleMin, resolution=self.ScaleRes, size=(15, 15), orientation="v",
enable_events=True, key="support")]
]
self.window = gui.Window("slider GUI", layout)
def Calculate(self,info):
# Only run for new time steps
if not info.NewTimeStep:
return
# Read data from the control slider GUI. This will only be done for one model object, so we need to share values in workspace.
if info.ModelObject.Name == self.SupportMaster:
# Read data from the GUI slider if it is changed (an event). If no event after 0.01 seconds, then continue the code.
event, values = self.window.Read(timeout=10)
# Exits the program if exit selected on the window
if event in (None, 'Exit'):
sys.exit()
# Record the result if there is an input change.
if event is not None:
if event == "support":
info.Workspace['supportdata'] = values["support"]
# Define the target offset position and tension
if info.ModelObject.Name == self.SupportMaster:
self.TargetOffsetScale = info.Workspace['supportdata']
self.TargetOffset = self.TargetOffsetScale
t = self.TargetOffset
# Only start after the specified time
if info.SimulationTime < self.StartTime:
return
# Rotational motion:
EulerAnglesInRadians = (
0, # = Rx
0, # = Ry
0 + 0.25 * (1.0 - cos(0.05 * t)) # = Rz
)
info.StructValue.Orientation = OrientationFromRxRyRzAngles(EulerAnglesInRadians)
# Calculate the change in X-offset position
# Find the position error from the defined target position
self.PositionLast = info.StructValue.Orientation[2]
self.PositionError = self.PositionLast - t
self.ErrorDir = numpy.sign(self.PositionError)
# Get the position change required for the time step
if abs(self.PositionError) > self.RateStep:
PositionNew = self.PositionLast - (self.RateStep * self.ErrorDir)
else:
if info.ModelObject.Name == self.SupportMaster:
PositionNew = self.PositionLast
else: PositionNew = self.PositionLast - (self.PositionError * self.ErrorDir)
# Return the X position to feed into the model
info.StructValue.Orientation[2] = PositionNew
# Close the GUI window on reset
def Finalise(self, info):
self.window.close()
It gave some error line 104 in Calculate self.PositionError = self.PositionLast - t
TypeError: unsipported operand type(s) for -: 'tuple' and 'int'
how to make that formula can handle a tuple? Please help me on this!
Thanks in advance!
Nubie
If you want to subtract an int from the tuple you could do
self.PositionError = tuple(map(lambda i, j: i - j, self.PositionLast, (t, t)))
but probably cleaner, as this was adapted from geeksforgeeks
You can refer the code for turtle.Vec2D to build a Vec3D class.
Refer turtle.Vec2D, I have following code
import math
from functools import cached_property
class Coordinate(tuple):
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
#cached_property
def x(self):
return self[0]
#cached_property
def y(self):
return self[1]
#cached_property
def distance(self):
return math.dist(self, (0, 0))
def __add__(self, other):
if isinstance(other, Coordinate):
return Coordinate(self.x+other.x, self.y+other.y)
elif isinstance(other, (int, float)):
return Coordinate(self.x+other, self.y+other)
else:
raise ValueError
def __sub__(self, other):
if isinstance(other, Coordinate):
return Coordinate(self.x-other.x, self.y-other.y)
elif isinstance(other, (int, float)):
return Coordinate(self.x-other, self.y-other)
else:
raise ValueError
def __mul__(self, other):
if isinstance(other, Coordinate):
return Coordinate(self.x*other.x, self.y*other.y)
elif isinstance(other, (int, float)):
return Coordinate(self.x*other, self.y*other)
else:
raise ValueError
def __pos__(self):
return self
def __neg__(self):
return Coordinate(-self.x, -self.y)
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
return -self.__sub__(other)
def __rmul__(self, other):
return self.__mul__(other)
def __repr__(self):
return f'({self.x}, {self.y})'
def rotate(self, angle):
angle = angle * math.pi / 180.0
cos, sin = math.cos(angle), math.sin(angle)
return Coordinate(self.x*cos-self.y*sin, self.x*sin+self.y*cos)
a = Coordinate(3, 4)
print(a + 2) # (5, 6)
print(a + a) # (6, 8)
print(2 + a) # (5, 6)
print(a - 2) # (1, 2)
print(a - a) # (0, 0)
print(2 - a) # (-1, -2)
print(a * 2) # (6, 8)
print(2 * a) # (6, 8)
print(-a) # (-3, -4)
print(a.rotate(90)) # (-4.0, 3.0000000000000004)
print(a.x, a.y, a.distance) # 3 4 5.0

multidimensional Array out of bounds

I am doing something wrong and cannot figure out. I have a multidimentional matrix total this stores. I am messing up the sequencing somewhere just cannot understand how or why. The error is IndexError: index 253 is out of bounds for axis 0 with size 253
class pricing_floatinglookback:
def __init__(self, spot, rate, sigma, time, sims, steps):
self.spot = spot
self.rate = rate
self.sigma = sigma
self.time = time
self.sims = sims
self.steps = steps+1
self.dt = self.time / self.steps
def call_floatingstrike(self):
SimPriceMin = np.array([])
SimPriceAtMaturity = np.array([])
call2 = np.array([])
pathwiseS= np.zeros((self.steps,),float)
total = np.zeros((self.sims,self.steps),float)
for j in range(self.sims):
pathwiseS[0] =self.spot## This will be one dimensional array from 0 to 253
total[j,0] = self.spot ## This will be multidimensional array with columns 0 to 800 and rows 0 to 253
for i in range(self.steps):
phi = np.random.normal()
pathwiseS[i+1] = pathwiseS[i]*(1+self.rate*self.dt+self.sigma*phi*np.sqrt(self.dt))## -->This is where i am going wrong.
total[j,i+1]= pathwiseS[i+1] ## -->This is where i am going wrong.
SimPriceAtMaturity = np.append(SimPriceAtMaturity, pathwiseS[self.steps - 1])
call2 = np.append(call2,max((pathwiseS[self.steps - 1])-self.spot,0))
SimPriceMin = np.append(SimPriceMin, min(pathwiseS))
callbsm = np.average(call2)
call = max(np.average(SimPriceAtMaturity) - np.average(SimPriceMin), 0)
return call, total.reshape(self.sims, self.steps), np.average(SimPriceMin), callbsm*np.exp(-self.rate*self.time)
pricelookback = pricing_floatinglookback(100, 0.05, 0.2, 1, 800, 252)
clookback, callmatrix, calmin, callbsm = pricelookback.call_floatingstrike()
print (callbsm)
plt.plot(callmatrix.T)
So I fixed it. I don't know why or how but it's fixed
class pricing_floatinglookback:
def __init__(self, spot, rate, sigma, time, sims, steps):
self.spot = spot
self.rate = rate
self.sigma = sigma
self.time = time
self.sims = sims
self.steps = steps+1
self.dt = self.time / self.steps
def call_floatingstrike(self):
SimPriceMin = np.array([])
SimPriceAtMaturity = np.array([])
call2 = np.array([])
pathwiseS= np.zeros((self.steps,),float)
total = np.zeros((self.sims,self.steps),float)
for j in range(self.sims):
pathwiseS[0] =self.spot
total[j,0] = self.spot
for i in range(self.steps-1):##--->This was the main reason, dont know why but it was!
phi = np.random.normal()
pathwiseS[i+1] = pathwiseS[i]*(1+self.rate*self.dt+self.sigma*phi*np.sqrt(self.dt))
total[j,i]= pathwiseS[i+1]##--->This as suggested in the comments
SimPriceAtMaturity = np.append(SimPriceAtMaturity, pathwiseS[self.steps - 1])
call2 = np.append(call2,max((pathwiseS[self.steps - 1])-self.spot,0))
SimPriceMin = np.append(SimPriceMin, min(pathwiseS))
callbsm = np.average(call2)
call = max(np.average(SimPriceAtMaturity) - np.average(SimPriceMin), 0)
return call, total.reshape(self.sims, self.steps), np.average(SimPriceMin), callbsm*np.exp(-self.rate*self.time)

Counting number of pixels in a cluster (Kmeans color detection)

I'm making a code that is analyzing an image and finding the three most dominant colors using KMeans clustering. The code works fine but I want to count the number of pixels in the three clusters.
code:
class Cluster(object):
def __init__(self):
self.pixels = []
self.centroid = None
def addPoint(self, pixel):
self.pixels.append(pixel)
def setNewCentroid(self):
R = [colour[0] for colour in self.pixels]
G = [colour[1] for colour in self.pixels]
B = [colour[2] for colour in self.pixels]
R = sum(R) / len(R)
G = sum(G) / len(G)
B = sum(B) / len(B)
self.centroid = (R, G, B)
self.pixels = []
return self.centroid
class Kmeans(object):
def __init__(self, k=2, max_iterations=5, min_distance=2.0, size=200):
self.k = k
self.max_iterations = max_iterations
self.min_distance = min_distance
self.size = (size, size)
def run(self, image):
self.image = image
self.image.thumbnail(self.size)
self.pixels = numpy.array(image.getdata(), dtype=numpy.uint8)
self.clusters = [None for i in range(self.k)]
self.oldClusters = None
randomPixels = random.sample(self.pixels, self.k)
for idx in range(self.k):
self.clusters[idx] = Cluster()
self.clusters[idx].centroid = randomPixels[idx]
iterations = 0
while self.shouldExit(iterations) is False:
self.oldClusters = [cluster.centroid for cluster in self.clusters]
print iterations
for pixel in self.pixels:
self.assignClusters(pixel)
for cluster in self.clusters:
cluster.setNewCentroid()
iterations += 1
return [cluster.centroid for cluster in self.clusters]
def assignClusters(self, pixel):
shortest = float('Inf')
for cluster in self.clusters:
distance = self.calcDistance(cluster.centroid, pixel)
if distance < shortest:
shortest = distance
nearest = cluster
nearest.addPoint(pixel)
def calcDistance(self, a, b):
result = numpy.sqrt(sum((a - b) ** 2))
return result
def shouldExit(self, iterations):
if self.oldClusters is None:
return False
for idx in range(self.k):
dist = self.calcDistance(
numpy.array(self.clusters[idx].centroid),
numpy.array(self.oldClusters[idx])
)
if dist < self.min_distance:
return True
if iterations <= self.max_iterations:
return False
return True
Is there a way that I can count how many pixels that is assigned to each cluster? I don't want how many that has the same value, just the total number.
You mean, you want to do
for cluster in self.clusters:
print len(cluster.pixels)
for each cluster?

set the properties of a class fixed once in order to avoid tedious calculation

I have two classes, namely PositionsD and makemock which are defined as following:
import numpy as np
cdef class PositionsD(object):
property x:
def __get__(self):
return np.array(self._x)
def __set__(self, x):
self._x = x
property y:
def __get__(self):
return np.array(self._y)
def __set__(self, y):
self._y = y
def __init__(self, positions):
self._x = positions[:,0]
self._y = positions[:,1]
class makemock(object):
def __init__(self):
self.scale = 0.238
self.arcsec2rad = np.pi/180./60./60.
self.g1 = None
self.g2 = None
self.source_pos = None
self.z = None
self.h_pos = None
self.h_z = None
def get_pos(self):
return PositionsD(self.source_pos)
pos = property(get_shear_pos)
def get_center(self):
return PositionsD(self.h_pos)
center = property(get_center)
def get_dist(self):
dx_mpc = (self.pos.x-self.center.x)*self.arcsec2rad*self.scale
dy_mpc = (self.pos.y-self.center.y)*self.arcsec2rad*self.scale
return np.vectorize(complex)(dx_mpc, dy_mpc)
dist = property(get_dist)
def get_r(self):
return abs(self.dist)
r = property(get_r)
def get_norm(self):
return -self.dist/np.conjugate(self.dist)
norm = property(get_norm)
def get_gabs(self):
return np.sqrt(self.g1**2 + self.g2**2 )
gabs = property(get_gabs)
def get_g(self):
phiell=np.arctan2(self.g2, self.g1) /2.
phipos=np.arctan2( (self.pos.y-self.center.y), (self.pos.x-self.center.x) )
et = -self.gabs * np.cos( 2*(phiell-phipos) )
ec = -self.gabs * np.sin( 2*(phiell-phipos) )
return np.vectorize(complex)(et, ec)
obs_g = property(get_g)
def data2model(self,params):
rs = params
x = self.r/rs
P = len(self.r)
gamma = np.zeros((P,), dtype=np.float64, order='C')
kappa = np.zeros((P,), dtype=np.float64, order='C')
farcth = np.zeros((P,), dtype=np.float64, order='C')
m1 = np.where(x < 1.0)[0]
kappa[m1] = 2/(x[m1]**2 - 1) * \
(1 - np.log((1 + ((1 - x[m1])/(x[m1] + 1))**0.5)/(1 - ((1 - x[m1])/(x[m1] + 1))**0.5))/(1 - x[m1]**2)**0.5)
farcth[m1]=0.5*np.log((1.+((1.-x[m1])/(x[m1]+1.))**0.5)/(1.-((1.-x[m1])/(x[m1]+1.))**0.5))/(1-x[m1]**2)**0.5
gamma[m1] = 4*(np.log(x[m1]/2) + 2*farcth[m1]) * x[m1]**(-2) - kappa[m1]
model_g = self.norm* gamma /(1. - kappa )
e = (self.obs_g+model_g)/(1+np.conjugate(model_g)*self.obs_g)
mask=(abs(model_g)>1.)
if (np.sum(mask)>0):
e[mask]=1./np.conjugate(e[mask])
return e
My Question is:
I need to run the data2model method from makemock in a loop for different values of params. I figured the process is quite slow while it seems each time in the loop properties of a class such as r, dist, obs_g get computed in each iteration. Is there any way to set them once, in order to increase the speed of loop?
After fiddling around to find a way to initialize some instances at the begining and avoid calculating them in each iteration of each function, I found out it is the best to use a dict and leave the value of each instance in this dictionary and update them at the end of initializing the instances. That is my solution:
class makemock(object):
def __init__(self, g1, g2, source_pos, z, h_pos, h_z, **kw):
self.scale = 0.238
self.arcsec2rad = np.pi/180./60./60.
self.g1 = g1
self.g2 = g2
self.source_pos = source_pos
self.z = z
self.h_pos = h_pos
self.h_z = h_z
pos= PositionsD(self.source_pos)
center=PositionsD(self.h_pos)
dx_mpc = (pos.x-center.x)*self.arcsec2rad*self.scale
dy_mpc = (pos.y-center.y)*self.arcsec2rad*self.scale
dist= np.vectorize(complex)(dx_mpc, dy_mpc)
r= abs(dist)
norm= -dist/np.conjugate(dist)
gabs= np.sqrt(self.g1**2 + self.g2**2 )
phiell=np.arctan2(self.g2, self.g1) /2.
phipos=np.arctan2( (pos.y-center.y), (pos.x-center.x) )
et = -gabs * np.cos( 2*(phiell-phipos) )
ec = -gabs * np.sin( 2*(phiell-phipos) )
obs_g=np.vectorize(complex)(et, ec)
self.__dict__.update(kw)
del kw
self.__dict__.update(locals())
del self.self
def dump(self):
print repr(self.__dict__)
def data2model(self,params):
rs = params
x = self.r/rs
P = len(self.r)
gamma = np.zeros((P,), dtype=np.float64, order='C')
kappa = np.zeros((P,), dtype=np.float64, order='C')
farcth = np.zeros((P,), dtype=np.float64, order='C')
m1 = np.where(x < 1.0)[0]
kappa[m1] = 2/(x[m1]**2 - 1) * \
(1 - np.log((1 + ((1 - x[m1])/(x[m1] + 1))**0.5)/(1 - ((1 - x[m1])/(x[m1] + 1))**0.5))/(1 - x[m1]**2)**0.5)
farcth[m1]=0.5*np.log((1.+((1.-x[m1])/(x[m1]+1.))**0.5)/(1.-((1.-x[m1])/(x[m1]+1.))**0.5))/(1-x[m1]**2)**0.5
gamma[m1] = 4*(np.log(x[m1]/2) + 2*farcth[m1]) * x[m1]**(-2) - kappa[m1]
model_g = self.norm* gamma /(1. - kappa )
e = (self.obs_g+model_g)/(1+np.conjugate(model_g)*self.obs_g)
mask=(abs(model_g)>1.)
if (np.sum(mask)>0):
e[mask]=1./np.conjugate(e[mask])
return e

How to extend a line segment in both directions

I've been stuck on this annoying problems for eons. I'm trying to write code so that I can scale a line segment meaning if the amount that I was to scale by(for example) is 2 and the current length of the line is 33 it will increase the entire length to 67. Meaning I add half to the beginning and half to the end...
new front ---a--------b--- new back... But I'm having trouble translating it into code. Here is an example of the code.. The endpoints method should return the endpoints in a tuple such as (p1, p2)
from point import Point
import math
class Line:
def __init__(self,aPoint=Point(), bPoint=Point()):
self.firstPoint = aPoint
self.secondPoint = bPoint
def getEndPoints(self):
return (self.firstPoint, self.secondPoint)
def scale(self,factor):
if factor < 1:
x1 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * (factor)
x2 = self.secondPoint.x +(self.firstPoint.x - self.secondPoint.x) * (factor)
print(str(x1))
y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * (factor)
y2 = self.secondPoint.y +(self.firstPoint.y - self.secondPoint.y) * (factor)
else:
x1 = -(self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * (factor))
x2 = -(self.secondPoint.x +(self.firstPoint.x - self.secondPoint.x) * (factor))
y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * (factor)
y2 = self.secondPoint.y +(self.firstPoint.y - self.secondPoint.y) * (factor)
self.firstPoint = Point(x1, y1)
self.secondPoint = Point(x2, y2)
if __name__ == "__main__":
p1 = Point(5,5)
p2 = Point(20,35)
l1 = Line(p1,p2)
l1.scale(2)
p5 = Point(-2.5,-10)
p6 = Point(27.5,50)
assert l1.getEndPoints() == (p5,p6)
These tests are not working correctly but the above are.. I'm getting a(5.0, 5.0) and b(20.0, 35.0)
l1.scale(0.5)
p5 = Point(8.75,12.5)
p6 = Point(16.25,27.5)
class Point:
'''Point class represents and manipulates
x,y coordinates.'''
def __init__(self,x=0,y=0):
'''Create a new point with default
x,y coordinates at 0,0.'''
self.x = x
self.y = y
def distanceTo(self,aPoint):
return ((self.x-aPoint.x) ** 2 + (self.y-aPoint.y) ** 2)** .5
not sure if I get it right but
use linear interpolation (parametric line equation)
You got line defined by endpoints p0,p1 in form of vectors so any point on it is defined as:
p(t)=p0+(p1-p0)*t
where p(t) is the point (vector) and t is scalar parameter in range
t=<0.0,1.0>
if you do not know the vector math then rewrite it to scalars
x(t)=x0+(x1-x0)*t
y(t)=y0+(y1-y0)*t
so if t=0 then you get the point p0 and if t=1 then you get the point p1
Now just rescale the t range
so you have scale s
t0=0.5-(0.5*s)` ... move from half of line by scale towards p0
t1=0.5+(0.5*s)` ... move from half of line by scale towards p1
so new endpoints are
q0=p0+(p1-p0)*t0
q1=p0+(p1-p0)*t1
[edit1] I see it like this
def scale(self,factor):
t0=0.5*(1.0-factor)
t1=0.5*(1.0+factor)
x1 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * t0
y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * t0
x2 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * t1
y2 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * t1
self.firstPoint = Point(x1, y1)
self.secondPoint = Point(x2, y2)
Take in mind I do not code in python so handle with prejudice ...
For a scale factor s, the coordinates of the new points are given by
Xa' = Xa (1+s)/2 + Xb (1-s)/2
Ya' = Ya (1+s)/2 + Yb (1-s)/2
Xb' = Xb (1+s)/2 + Xa (1-s)/2
Yb' = Yb (1+s)/2 + Ya (1-s)/2
With the common metrik, you only need to adjust each dimension seperately.
I rewrote some parts of the code, to fit it better to the usual Python style
You might want to work through the things, you are unfamiliar with to save yourself a lot of time in the future.
class Line:
def __init__(self, point_one, point_two):
self.point_one = point_one
self.point_two = point_two
def __str__(self):
return 'Line(p1:{},p2:{})'.format(self.point_one, self.point_two)
#property
def points(self):
return self.point_one, self.point_two
#property
def length(self):
return ((self.point_one.x - self.point_two.x)**2 + (self.point_one.y - self.point_two.y)**2)**0.5
def scale(self, factor):
self.point_one.x, self.point_two.x = Line.scale_dimension(self.point_one.x, self.point_two.x, factor)
self.point_one.y, self.point_two.y = Line.scale_dimension(self.point_one.y, self.point_two.y, factor)
#staticmethod
def scale_dimension(dim1, dim2, factor):
base_length = dim2 - dim1
ret1 = dim1 - (base_length * (factor-1) / 2)
ret2 = dim2 + (base_length * (factor-1) / 2)
return ret1, ret2
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return 'Point(x={},y={})'.format(self.x, self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
if __name__ == "__main__":
p1 = Point(5, 5)
p2 = Point(20, 35)
l1 = Line(p1, p2)
print(l1)
print(l1.length)
l1.scale(2)
print(l1)
print(l1.length)
p5 = Point(-2.5, -10)
p6 = Point(27.5, 50)
assert l1.points == (p5, p6)
Note, that the scale method modifies the orginal line and points. If you want to get a new line, the method should be:
def scale(self, factor):
x1, x2 = Line.scale_dimension(self.point_one.x, self.point_two.x, factor)
y1, y2 = Line.scale_dimension(self.point_one.y, self.point_two.y, factor)
return Line(Point(x1, y1), Point(x2, y2))

Categories