Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turning in mp2 #4

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added artwork/art1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artwork/art2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artwork/art3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
190 changes: 190 additions & 0 deletions lambda_art.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"""TODO: Put your header comment here."""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:P


import random
import math
from PIL import Image


def build_random_function(min_depth, max_depth):
"""Build a random function.

Builds a random function of depth at least min_depth and depth at most
max_depth. (See the assignment write-up for the definition of depth
in this context)

Args:
min_depth: the minimum depth of the random function
max_depth: the maximum depth of the random function

Returns:
The randomly generated function represented as a nested list.
(See the assignment writ-eup for details on the representation of
these functions)
"""

if min_depth < 1:
var = random.randint(1,2)
if var == 1:
return lambda x,y: x

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your implementations of lambda functions are correct/functional, but you aren't quite realizing some of the benefits of lambda-izing - for example, lambda-izing functions allows you to choose/pass around functions instead of specifically checking things like rand_int values.

That being said, I do quite like how you've used them to make build_random_function also build the evaluation path - that's quite elegant, in my opinion! All in all, solidly done.

else:
return lambda x,y: y

min_depth -= 1
max_depth -= 1

#varies the depth of the function
d_range = max_depth - min_depth
if d_range > 0:
n = random.randint(1, d_range)
if n==1:
min_depth +=1

rand_int = random.randint(1,6)

if rand_int == 1:
inner = build_random_function(min_depth, max_depth)
return lambda x,y: math.cos(math.pi * inner(x,y))

if rand_int == 2:
inner = build_random_function(min_depth, max_depth)
return lambda x,y: math.sin(math.pi * inner(x,y))

if rand_int == 3:
inner = build_random_function(min_depth, max_depth)
return lambda x,y: inner(x,y) ** 3

if rand_int == 4:
inner1 = build_random_function(min_depth, max_depth)
inner2 = build_random_function(min_depth, max_depth)
return lambda x,y: inner1(x,y) * inner2(x,y)

if rand_int == 5:
inner1 = build_random_function(min_depth, max_depth)
inner2 = build_random_function(min_depth, max_depth)
return lambda x,y: .5*(inner1(x,y) + inner2(x,y))

if rand_int == 6:
inner = build_random_function(min_depth, max_depth)
return lambda x,y: math.sin(30 * math.pi * inner(x,y))

print('error')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out https://docs.python.org/3/tutorial/errors.html#raising-exceptions - you can also raise errors, rather than printing and then returning nothing. Essentially, you can make a custom error if you want that degree of control over the program.

(Though, unless the built-in random library broke, this error should never appear - rand_int should always give you one of those values, so you should be fine either way.)

pass

def remap_interval(val,
input_interval_start,
input_interval_end,
output_interval_start,
output_interval_end):
"""Remap a value from one interval to another.

Given an input value in the interval [input_interval_start,
input_interval_end], return an output value scaled to fall within
the output interval [output_interval_start, output_interval_end].

Args:
val: the value to remap
input_interval_start: the start of the interval that contains all
possible values for val
input_interval_end: the end of the interval that contains all possible
values for val
output_interval_start: the start of the interval that contains all
possible output values
output_inteval_end: the end of the interval that contains all possible
output values

Returns:
The value remapped from the input to the output interval

Examples:
>>> remap_interval(0.5, 0, 1, 0, 10)
5.0
>>> remap_interval(5, 4, 6, 0, 2)
1.0
>>> remap_interval(5, 4, 6, 1, 2)
1.5
"""
initial_range = input_interval_end - input_interval_start
final_range = output_interval_end - output_interval_start
initial_mag = val - input_interval_start
ratio = initial_mag / initial_range
end_mag = ratio * final_range
end_value = end_mag + output_interval_start

return end_value


def color_map(val):
"""Maps input value between -1 and 1 to an integer 0-255, suitable for use as an RGB color code.

Args:
val: value to remap, must be a float in the interval [-1, 1]

Returns:
An integer in the interval [0,255]

Examples:
>>> color_map(-1.0)
0
>>> color_map(1.0)
255
>>> color_map(0.0)
127
>>> color_map(0.5)
191
"""
color_code = remap_interval(val, -1, 1, 0, 255)
return int(color_code)


def test_image(filename, x_size=350, y_size=350):
"""Generate a test image with random pixels and save as an image file.

Args:
filename: string filename for image (should be .png)
x_size, y_size: optional args to set image dimensions (default: 350)
"""
# Create image and loop over all pixels
im = Image.new("RGB", (x_size, y_size))
pixels = im.load()
for i in range(x_size):
for j in range(y_size):
x = remap_interval(i, 0, x_size, -1, 1)
y = remap_interval(j, 0, y_size, -1, 1)
pixels[i, j] = (random.randint(0, 255), # Red channel
random.randint(0, 255), # Green channel
random.randint(0, 255)) # Blue channel

im.save(filename)

def generate_art(filename, x_size=350, y_size=350):
"""Generate computational art and save as an image file.

Args:
filename: string filename for image (should be .png)
x_size, y_size: optional args to set image dimensions (default: 350)
"""
# Functions for red, green, and blue channels - where the magic happens!
red_function = build_random_function(4,7)
green_function = build_random_function(4,7)
blue_function = build_random_function(4,7)

# Create image and loop over all pixels
im = Image.new("RGB", (x_size, y_size))
pixels = im.load()
for i in range(x_size):
for j in range(y_size):
x = remap_interval(i, 0, x_size, -1, 1)
y = remap_interval(j, 0, y_size, -1, 1)
pixels[i, j] = (
color_map(red_function(x, y)),
color_map(green_function(x, y)),
color_map(blue_function(x, y))
)

im.save(filename)


if __name__ == '__main__':
import doctest
#doctest.run_docstring_examples(evaluate_random_function, globals(), verbose=True)
generate_art("myart.png")
Binary file added myart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified noise.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 94 additions & 13 deletions recursive_art.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""TODO: Put your header comment here."""

import random
import math
from PIL import Image


Expand All @@ -20,7 +21,39 @@ def build_random_function(min_depth, max_depth):
(See the assignment writ-eup for details on the representation of
these functions)
"""
# TODO: implement this

if min_depth < 1:
var = random.randint(1,2)
if var == 1:
return "x"
else:
return "y"

min_depth -= 1
max_depth -= 1

#varies the depth of the function
d_range = max_depth - min_depth
if d_range > 0:
n = random.randint(1, d_range)
if n==1:
min_depth +=1

rand_int = random.randint(1,7)
print(rand_int)
single_inputs = ["sin_pi", "cos_pi", "x_3", "cos_30pi"]
double_inputs = ["prod", "avg", "hypot_n"]

up_to = len(single_inputs) + 1
num_funcs = len(single_inputs) + len(double_inputs)

if rand_int in range (1,up_to):
return [single_inputs[rand_int - 1], [build_random_function(min_depth, max_depth)]]

if rand_int in range (up_to, num_funcs + 1):
return [double_inputs[rand_int - up_to], [build_random_function(min_depth, max_depth), build_random_function(min_depth, max_depth)]]

print('error')
pass


Expand All @@ -42,10 +75,50 @@ def evaluate_random_function(f, x, y):
-0.5
>>> evaluate_random_function(["y"],0.1,0.02)
0.02
>>> evaluate_random_function(["avg", ["x", "y"]],5,10)
7.5
>>> evaluate_random_function(["prod", ["x", "y"]],3,5)
15
>>> evaluate_random_function(["cos_pi", ["x"]], 2 ,5)
1.0
>>> evaluate_random_function(["sin_pi", ["y"]], 3 , 3.5)
-1.0
>>> evaluate_random_function(["prod", [["sin_pi", ["y"]], "x"]], 3 , 3.5)
-3.0
"""
# TODO: implement this
pass

func = f[0]
if func == "x":
return x
if func == "y":
return y
if func == "avg":
in_func = f[1]
return .5*(evaluate_random_function(in_func[0], x, y) + evaluate_random_function(in_func[1], x, y))
if func == "x_3":
return x**3
if func == "prod":
in_func = f[1]
return evaluate_random_function(in_func[0], x, y) * evaluate_random_function(in_func[1], x, y)
if func == "sin_pi":
in_func = f[1]
return math.sin(evaluate_random_function(in_func[0], x, y)*math.pi)
if func == "cos_pi":
in_func = f[1]
return math.cos(evaluate_random_function(in_func[0], x, y)*math.pi)
if func == "cos_30pi":
in_func = f[1]
return math.cos(evaluate_random_function(in_func[0], x, y)*math.pi*30)
#nonlinear scaling of hypotenuse function
if func == "hypot_n":
in_func = f[1]
hypotenuse = math.hypot(evaluate_random_function(in_func[0], x, y), evaluate_random_function(in_func[1], x, y))
if hypotenuse > 1:
hypotenuse = 1
if hypotenuse < -1:
hypotenuse = -1
return hypotenuse
else:
pass

def remap_interval(val,
input_interval_start,
Expand Down Expand Up @@ -80,8 +153,14 @@ def remap_interval(val,
>>> remap_interval(5, 4, 6, 1, 2)
1.5
"""
# TODO: implement this
pass
initial_range = input_interval_end - input_interval_start
final_range = output_interval_end - output_interval_start
initial_mag = val - input_interval_start
ratio = initial_mag / initial_range
end_mag = ratio * final_range
end_value = end_mag + output_interval_start

return end_value


def color_map(val):
Expand All @@ -103,7 +182,6 @@ def color_map(val):
>>> color_map(0.5)
191
"""
# NOTE: This relies on remap_interval, which you must provide
color_code = remap_interval(val, -1, 1, 0, 255)
return int(color_code)

Expand Down Expand Up @@ -137,9 +215,12 @@ def generate_art(filename, x_size=350, y_size=350):
x_size, y_size: optional args to set image dimensions (default: 350)
"""
# Functions for red, green, and blue channels - where the magic happens!
red_function = ["x"]
green_function = ["y"]
blue_function = ["x"]
#red_function = ["x"]
#green_function = ["y"]
#blue_function = ["x"]
red_function = build_random_function(7, 9)
green_function = build_random_function(7, 9)
blue_function = build_random_function(7, 9)

# Create image and loop over all pixels
im = Image.new("RGB", (x_size, y_size))
Expand All @@ -159,13 +240,13 @@ def generate_art(filename, x_size=350, y_size=350):

if __name__ == '__main__':
import doctest
doctest.testmod()
#doctest.run_docstring_examples(evaluate_random_function, globals(), verbose=True)

# Create some computational art!
# TODO: Un-comment the generate_art function call after you
# implement remap_interval and evaluate_random_function
# generate_art("myart.png")
generate_art("myart.png")

# Test that PIL is installed correctly
# TODO: Comment or remove this function call after testing PIL install
test_image("noise.png")
#test_image("noise.png")
7 changes: 7 additions & 0 deletions reflection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Reflection
### Emma Westerhoff
When approaching this project, I had an idea of what I wanted the final product to look like. I took my time getting there to ensure that everything worked first. For example, when writing the generate_random_function file, my first iteration had an if statement for every possible function name. Then, after individually adding and testing each new function, I put them into a list and swept through that as I had originally intended. If I had just started with my end goal, there would have been many places for mistakes to slip through the cracks.

This project was really well scoped, as just a mini-project. I put in just enough time to do the actual project and the first extension, but rather foolishly thought the other two extensions would take an equal amount of time. I wish I had set a goal for myself before starting the project as to how far I would go, rather than saying "whatever I have time for". Surprise, at Olin, you only have time for things that you set aside time for.

Unit testing could have been more documented, as many were done using print statements. Using doctest would have made it easier for me to go back and see what had, or used to, work. At the same time, doctests can clutter up my code and make it less legible, so it's all about finding the balance.