|
|
### A Pluto.jl notebook ###
|
|
|
# v0.12.15
|
|
|
|
|
|
using Markdown
|
|
|
using InteractiveUtils
|
|
|
|
|
|
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
|
|
|
macro bind(def, element)
|
|
|
quote
|
|
|
local el = $(esc(element))
|
|
|
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
|
|
|
el
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# ╔═╡ a4937996-f314-11ea-2ff9-615c888afaa8
|
|
|
begin
|
|
|
using Images
|
|
|
using TestImages
|
|
|
using ImageFiltering
|
|
|
using Statistics
|
|
|
using PlutoUI
|
|
|
using BenchmarkTools
|
|
|
end
|
|
|
|
|
|
# ╔═╡ e6b6760a-f37f-11ea-3ae1-65443ef5a81a
|
|
|
md"_homework 2, version 2.1_"
|
|
|
|
|
|
# ╔═╡ 85cfbd10-f384-11ea-31dc-b5693630a4c5
|
|
|
md"""
|
|
|
|
|
|
# **Homework 2**: _Dynamic programming_
|
|
|
`18.S191`, fall 2020
|
|
|
|
|
|
This notebook contains _built-in, live answer checks_! In some exercises you will see a coloured box, which runs a test case on your code, and provides feedback based on the result. Simply edit the code, run it, and the check runs again.
|
|
|
|
|
|
_For MIT students:_ there will also be some additional (secret) test cases that will be run as part of the grading process, and we will look at your notebook and write comments.
|
|
|
|
|
|
Feel free to ask questions!
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 33e43c7c-f381-11ea-3abc-c942327456b1
|
|
|
# edit the code below to set your name and kerberos ID (i.e. email without @mit.edu)
|
|
|
|
|
|
student = (name = "Jazzy Doe", kerberos_id = "jazz")
|
|
|
|
|
|
# you might need to wait until all other cells in this notebook have completed running.
|
|
|
# scroll around the page to see what's up
|
|
|
|
|
|
# ╔═╡ ec66314e-f37f-11ea-0af4-31da0584e881
|
|
|
md"""
|
|
|
|
|
|
Submission by: **_$(student.name)_** ($(student.kerberos_id)@mit.edu)
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 938185ec-f384-11ea-21dc-b56b7469f798
|
|
|
md"_Let's create a package environment:_"
|
|
|
|
|
|
# ╔═╡ 0d144802-f319-11ea-0028-cd97a776a3d0
|
|
|
#img = load(download("https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Piet_Mondriaan%2C_1930_-_Mondrian_Composition_II_in_Red%2C_Blue%2C_and_Yellow.jpg/300px-Piet_Mondriaan%2C_1930_-_Mondrian_Composition_II_in_Red%2C_Blue%2C_and_Yellow.jpg"))
|
|
|
#img = load(download("https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/Hilma_af_Klint_-_Group_IX_SUW%2C_The_Swan_No._1_%2813947%29.jpg/477px-Hilma_af_Klint_-_Group_IX_SUW%2C_The_Swan_No._1_%2813947%29.jpg"))
|
|
|
img = load(download("https://i.imgur.com/4SRnmkj.png"))
|
|
|
|
|
|
# ╔═╡ cc9fcdae-f314-11ea-1b9a-1f68b792f005
|
|
|
md"""
|
|
|
# Arrays: Slices and views
|
|
|
|
|
|
In the lecture (included below) we learned about what array views are. In this exercise we will add to that understanding and look at an important use of `view`s: to reduce the amount of memory allocations when reading sub-sequences within an array.
|
|
|
|
|
|
We will use the `BenchmarkTools` package to emperically understand the effects of using views.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ b49a21a6-f381-11ea-1a98-7f144c55c9b7
|
|
|
html"""
|
|
|
<iframe width="100%" height="450px" src="https://www.youtube.com/embed/gTGJ80HayK0?rel=0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ b49e8cc8-f381-11ea-1056-91668ac6ae4e
|
|
|
md"""
|
|
|
## Shrinking an array
|
|
|
|
|
|
Below is a function called `remove_in_each_row(img, pixels)`. It takes a matrix `img` and a vector of integers, `pixels`, and shrinks the image by 1 pixel in width by removing the element `img[i, pixels[i]]` in every row. This function is one of the building blocks of the Image Seam algorithm we saw in the lecture.
|
|
|
|
|
|
Read it and convince yourself that it is correct.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ e799be82-f317-11ea-3ae4-6d13ece3fe10
|
|
|
function remove_in_each_row(img, column_numbers)
|
|
|
@assert size(img, 1) == length(column_numbers) # same as the number of rows
|
|
|
m, n = size(img)
|
|
|
local img′ = similar(img, m, n-1) # create a similar image with one less column
|
|
|
|
|
|
# The prime (′) in the variable name is written as \prime<TAB>
|
|
|
# You cannot use apostrophe for this! (Apostrophe means the transpose of a matrix)
|
|
|
|
|
|
for (i, j) in enumerate(column_numbers)
|
|
|
img′[i, :] = vcat(img[i, 1:j-1], img[i, j+1:end])
|
|
|
end
|
|
|
img′
|
|
|
end
|
|
|
|
|
|
# ╔═╡ c075a8e6-f382-11ea-2263-cd9507324f4f
|
|
|
md"Let's use it to remove the pixels on the diagonal. These are the image dimensions before and after doing so:"
|
|
|
|
|
|
# ╔═╡ 9cced1a8-f326-11ea-0759-0b2f22e5a1db
|
|
|
(before=size(img), after=size(remove_in_each_row(img, 1:size(img, 1))))
|
|
|
|
|
|
# ╔═╡ 1d893998-f366-11ea-0828-512de0c44915
|
|
|
md"""
|
|
|
## **Exercise 1** - _Making it efficient_
|
|
|
|
|
|
We can use the `@benchmark` macro from the [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) package to benchmark fragments of Julia code.
|
|
|
|
|
|
`@benchmark` takes an expression and runs it a number of times to obtain statistics about the run time and memory allocation. We generally take the minimum time as the most stable measurement of performance ([for reasons discussed in the paper on BenchmarkTools](http://www-math.mit.edu/~edelman/publications/robust_benchmarking.pdf))
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 59991872-f366-11ea-1036-afe313fb4ec1
|
|
|
md"""
|
|
|
First, as an example, let's benchmark the `remove_in_each_row` function we defined above by passing in our image and a some indices to remove.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ e501ea28-f326-11ea-252a-53949fd9ef57
|
|
|
performance_experiment_default = @benchmark remove_in_each_row(img, 1:size(img, 1))
|
|
|
|
|
|
# ╔═╡ f7915918-f366-11ea-2c46-2f4671ae8a22
|
|
|
md"""
|
|
|
#### Exercise 1.1
|
|
|
|
|
|
`vcat(x, y)` is used in julia to concatenate two arrays vertically. This actually creates a new array of size `length(x) + length(y)` and copies `x` and `y` into it. We use it in `remove_in_each_row` to create rows which have one pixel less.
|
|
|
|
|
|
While using `vcat` might make it easy to write the first version of our function, it's strictly not necessary.
|
|
|
|
|
|
👉 In `remove_in_each_row_no_vcat` below, figure out a way to avoid the use of `vcat` and modify the function to avoid it.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 37d4ea5c-f327-11ea-2cc5-e3774c232c2b
|
|
|
function remove_in_each_row_no_vcat(img, column_numbers)
|
|
|
@assert size(img, 1) == length(column_numbers) # same as the number of rows
|
|
|
m, n = size(img)
|
|
|
local img′ = similar(img, m, n-1) # create a similar image with one less column
|
|
|
|
|
|
for (i, j) in enumerate(column_numbers)
|
|
|
# EDIT THE FOLLOWING LINE and split it into two lines
|
|
|
# to avoid using `vcat`.
|
|
|
img′[i, 1:j-1] .= img[i, 1:j-1]
|
|
|
img′[i, j:end] .= img[i, j+1:end]
|
|
|
end
|
|
|
img′
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 67717d02-f327-11ea-0988-bfe661f57f77
|
|
|
performance_experiment_without_vcat = @benchmark remove_in_each_row_no_vcat(img, 1:size(img, 1))
|
|
|
|
|
|
# ╔═╡ 9e149cd2-f367-11ea-28ef-b9533e8a77bb
|
|
|
md"""
|
|
|
If you did it correctly, you should see that this benchmark shows the function running faster! And "memory estimate" should also show a smaller number, and so should "allocs estimate" which is the number of allocations done per call.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ ba1619d4-f389-11ea-2b3f-fd9ba71cf7e3
|
|
|
md"""
|
|
|
#### Exercise 1.2
|
|
|
|
|
|
👉 How many estimated allocations did this optimization reduce, and how can you explain most of them?
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ e49235a4-f367-11ea-3913-f54a4a6b2d6b
|
|
|
no_vcat_observation = md"""
|
|
|
<Your answer here>
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 837c43a4-f368-11ea-00a3-990a45cb0cbd
|
|
|
md"""
|
|
|
|
|
|
#### Exercise 1.3 - `view`-based optimization
|
|
|
|
|
|
👉 In the below `remove_in_each_row_views` function, implement the same optimization to remove `vcat` and use `@view` or `@views` to avoid creating copies or slices of the `img` array.
|
|
|
|
|
|
Pluto will automatically time your change with `@benchmark` below.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 90a22cc6-f327-11ea-1484-7fda90283797
|
|
|
function remove_in_each_row_views(img, column_numbers)
|
|
|
@assert size(img, 1) == length(column_numbers) # same as the number of rows
|
|
|
m, n = size(img)
|
|
|
local img′ = similar(img, m, n-1) # create a similar image with one less column
|
|
|
|
|
|
for (i, j) in enumerate(column_numbers)
|
|
|
# EDIT THE FOLLOWING LINE and split it into two lines
|
|
|
# to avoid using `vcat`.
|
|
|
img′[i, 1:j-1] .= @view img[i, 1:j-1]
|
|
|
img′[i, j:end] .= @view img[i, j+1:end]
|
|
|
end
|
|
|
img′
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 3335e07c-f328-11ea-0e6c-8d38c8c0ad5b
|
|
|
performance_experiment_views = @benchmark begin
|
|
|
remove_in_each_row_views(img, 1:size(img, 1))
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 40d6f562-f329-11ea-2ee4-d7806a16ede3
|
|
|
md"Final tally:"
|
|
|
|
|
|
# ╔═╡ 4f0975d8-f329-11ea-3d10-59a503f8d6b2
|
|
|
(
|
|
|
default = performance_experiment_default,
|
|
|
without_vcat = performance_experiment_without_vcat,
|
|
|
views = performance_experiment_views,
|
|
|
)
|
|
|
|
|
|
# ╔═╡ dc63d32a-f387-11ea-37e2-6f3666a72e03
|
|
|
⧀(a, b) = minimum(a).allocs + size(img, 1) ÷ 2 < minimum(b).allocs;
|
|
|
|
|
|
# ╔═╡ 7eaa57d2-f368-11ea-1a70-c7c7e54bd0b1
|
|
|
md"""
|
|
|
|
|
|
#### Exercise 1.4
|
|
|
|
|
|
Nice! If you did your optimizations right, you should be able to get down the estimated allocations to a single digit number!
|
|
|
|
|
|
👉 How many allocations were avoided by adding the `@view` optimization over the `vcat` optimization? Why is this?
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ fd819dac-f368-11ea-33bb-17148387546a
|
|
|
views_observation = md"""
|
|
|
<your answer here>
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 318a2256-f369-11ea-23a9-2f74c566549b
|
|
|
md"""
|
|
|
## _Brightness and Energy_
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 7a44ba52-f318-11ea-0406-4731c80c1007
|
|
|
md"""
|
|
|
First, we will define a `brightness` function for a pixel (a color) as the mean of the red, green and blue values.
|
|
|
|
|
|
You should use this function whenever the problem set asks you to deal with _brightness_ of a pixel.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 6c7e4b54-f318-11ea-2055-d9f9c0199341
|
|
|
begin
|
|
|
brightness(c::RGB) = mean((c.r, c.g, c.b))
|
|
|
brightness(c::RGBA) = mean((c.r, c.g, c.b))
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 74059d04-f319-11ea-29b4-85f5f8f5c610
|
|
|
Gray.(brightness.(img))
|
|
|
|
|
|
# ╔═╡ 0b9ead92-f318-11ea-3744-37150d649d43
|
|
|
md"""We provide you with a convolve function below.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ d184e9cc-f318-11ea-1a1e-994ab1330c1a
|
|
|
convolve(img, k) = imfilter(img, reflect(k)) # uses ImageFiltering.jl package
|
|
|
# behaves the same way as the `convolve` function used in Lecture 2
|
|
|
# You were asked to implement this in homework 1.
|
|
|
|
|
|
# ╔═╡ cdfb3508-f319-11ea-1486-c5c58a0b9177
|
|
|
float_to_color(x) = RGB(max(0, -x), max(0, x), 0)
|
|
|
|
|
|
# ╔═╡ 5fccc7cc-f369-11ea-3b9e-2f0eca7f0f0e
|
|
|
md"""
|
|
|
finally we define the `energy` function which takes the Sobel gradients along x and y directions and computes the norm of the gradient for each pixel.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 6f37b34c-f31a-11ea-2909-4f2079bf66ec
|
|
|
begin
|
|
|
energy(∇x, ∇y) = sqrt.(∇x.^2 .+ ∇y.^2)
|
|
|
function energy(img)
|
|
|
∇y = convolve(brightness.(img), Kernel.sobel()[1])
|
|
|
∇x = convolve(brightness.(img), Kernel.sobel()[2])
|
|
|
energy(∇x, ∇y)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 9fa0cd3a-f3e1-11ea-2f7e-bd73b8e3f302
|
|
|
float_to_color.(energy(img))
|
|
|
|
|
|
# ╔═╡ 87afabf8-f317-11ea-3cb3-29dced8e265a
|
|
|
md"""
|
|
|
## **Exercise 2** - _Building up to dynamic programming_
|
|
|
|
|
|
In this exercise and the following ones, we will use the computational problem of Seam carving. We will think through all the "gut reaction" solutions, and then finally end up with the dynamic programming solution that we saw in the lecture.
|
|
|
|
|
|
In the process we will understand the performance and accuracy of each iteration of our solution.
|
|
|
|
|
|
### How to implement the solutions:
|
|
|
|
|
|
For every variation of the algorithm, your job is to write a function which takes a matrix of energies, and an index for a pixel on the first row, and computes a "seam" starting at that pixel.
|
|
|
|
|
|
The function should return a vector of as many integers as there are rows in the input matrix where each number points out a pixel to delete from the corresponding row. (it acts as the input to `remove_in_each_row`).
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 8ba9f5fc-f31b-11ea-00fe-79ecece09c25
|
|
|
md"""
|
|
|
#### Exercise 2.1 - _The greedy approach_
|
|
|
|
|
|
The first approach discussed in the lecture (included below) is the _greedy approach_: you start from your top pixel, and at each step you just look at the three neighbors below. The next pixel in the seam is the neighbor with the lowest energy.
|
|
|
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ f5a74dfc-f388-11ea-2577-b543d31576c6
|
|
|
html"""
|
|
|
<iframe width="100%" height="450px" src="https://www.youtube.com/embed/rpB6zQNsbQU?start=777&end=833" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ c3543ea4-f393-11ea-39c8-37747f113b96
|
|
|
md"""
|
|
|
👉 Implement the greedy approach.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 2f9cbea8-f3a1-11ea-20c6-01fd1464a592
|
|
|
random_seam(m, n, i) = reduce((a, b) -> [a..., clamp(last(a) + rand(-1:1), 1, n)], 1:m-1; init=[i])
|
|
|
|
|
|
# ╔═╡ abf20aa0-f31b-11ea-2548-9bea4fab4c37
|
|
|
function greedy_seam(energies, starting_pixel::Int)
|
|
|
# you can delete the body of this function - it's just a placeholder.
|
|
|
m, n = size(energies)
|
|
|
seam = Array{Int64}(undef, m)
|
|
|
seam[1] = starting_pixel
|
|
|
e = energies[1,starting_pixel]
|
|
|
for i in 1:m-1
|
|
|
v, idx = findmin(energies[i+1,max(1,seam[i]-1):min(n,seam[i]+1)])
|
|
|
e += v
|
|
|
seam[i+1] = seam[i] + idx - 2 + (seam[i] == 1)
|
|
|
end
|
|
|
return seam
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 5430d772-f397-11ea-2ed8-03ee06d02a22
|
|
|
md"Before we apply your function to our test image, let's try it out on a small matrix of energies (displayed here in grayscale), just like in the lecture snippet above (clicking on the video will take you to the right part of the video). Light pixels have high energy, dark pixels signify low energy."
|
|
|
|
|
|
# ╔═╡ f580527e-f397-11ea-055f-bb9ea8f12015
|
|
|
# try
|
|
|
# if length(Set(greedy_seam(greedy_test, 5))) == 1
|
|
|
# md"Right now you are seeing the placeholder function. (You haven't done the exercise yet!) This is a straight line from the starting pixel."
|
|
|
# end
|
|
|
# catch end
|
|
|
|
|
|
# ╔═╡ 7ddee6fc-f394-11ea-31fc-5bd665a65bef
|
|
|
greedy_test = Gray.(rand(Float64, (8,10)));
|
|
|
|
|
|
# ╔═╡ 6f52c1a2-f395-11ea-0c8a-138a77f03803
|
|
|
md"Starting pixel: $(@bind greedy_starting_pixel Slider(1:size(greedy_test, 2); show_value=true))"
|
|
|
|
|
|
# ╔═╡ 9945ae78-f395-11ea-1d78-cf6ad19606c8
|
|
|
md"_Let's try it on the bigger image!_"
|
|
|
|
|
|
# ╔═╡ 87efe4c2-f38d-11ea-39cc-bdfa11298317
|
|
|
md"Compute shrunk image: $(@bind shrink_greedy CheckBox())"
|
|
|
|
|
|
# ╔═╡ 52452d26-f36c-11ea-01a6-313114b4445d
|
|
|
md"""
|
|
|
#### Exercise 2.2 - _Recursion_
|
|
|
|
|
|
A common pattern in algorithm design is the idea of solving a problem as the combination of solutions to subproblems.
|
|
|
|
|
|
The classic example, is a [Fibonacci number](https://en.wikipedia.org/wiki/Fibonacci_number) generator.
|
|
|
|
|
|
The recursive implementation of Fibonacci looks something like this
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 2a98f268-f3b6-11ea-1eea-81c28256a19e
|
|
|
function fib(n)
|
|
|
# base case (basis)
|
|
|
if n == 0 || n == 1 # `||` means "or"
|
|
|
return 1
|
|
|
end
|
|
|
|
|
|
# recursion (induction)
|
|
|
return fib(n-1) + fib(n-2)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 32e9a944-f3b6-11ea-0e82-1dff6c2eef8d
|
|
|
md"""
|
|
|
Notice that you can call a function from within itself which may call itself and so on until a base case is reached. Then the program will combine the result from the base case up to the final result.
|
|
|
|
|
|
In the case of the Fibonacci function, we added the solutions to the subproblems `fib(n-1)`, `fib(n-2)` to produce `fib(n)`.
|
|
|
|
|
|
An analogy can be drawn to the process of mathematical induction in mathematics. And as with mathematical induction there are parts to constructing such a recursive algorithm:
|
|
|
|
|
|
- Defining a base case
|
|
|
- Defining an recursion i.e. finding a solution to the problem as a combination of solutions to smaller problems.
|
|
|
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 9101d5a0-f371-11ea-1c04-f3f43b96ca4a
|
|
|
md"""
|
|
|
👉 Define a `least_energy` function which returns:
|
|
|
1. the lowest possible total energy for a seam starting at the pixel at $(i, j)$;
|
|
|
2. the column to jump to on the next move (in row $i + 1$),
|
|
|
which is one of $j-1$, $j$ or $j+1$, up to boundary conditions.
|
|
|
|
|
|
Return these two values in a tuple.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 8ec27ef8-f320-11ea-2573-c97b7b908cb7
|
|
|
## returns lowest possible sum energy at pixel (i, j), and the column to jump to in row i+1.
|
|
|
function least_energy(energies, i, j)
|
|
|
m, n = size(energies)
|
|
|
if i == m
|
|
|
return (energies[i,j], Int64[j])
|
|
|
end
|
|
|
|
|
|
v = energies[i,j]
|
|
|
les = [least_energy(energies, i+1, j2)
|
|
|
for j2 in max(1,j-1):min(n,j+1)]
|
|
|
min_v, min_idx = findmin([le[1] for le in les])
|
|
|
next_idx = j + min_idx - 2 + (j == 1)
|
|
|
return (v + min_v, vcat([next_idx], les[min_idx][2]))
|
|
|
end
|
|
|
|
|
|
# ╔═╡ a7f3d9f8-f3bb-11ea-0c1a-55bbb8408f09
|
|
|
md"""
|
|
|
This is so elegant, correct, but inefficient! If you **check this checkbox** $(@bind compute_access CheckBox()), you will see the number of accesses made to the energies array it took to compute the least energy from the pixel (1,7):
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 18e0fd8a-f3bc-11ea-0713-fbf74d5fa41a
|
|
|
md"Whoa!"
|
|
|
|
|
|
# ╔═╡ cbf29020-f3ba-11ea-2cb0-b92836f3d04b
|
|
|
begin
|
|
|
struct AccessTrackerArray{T,N} <: AbstractArray{T, N}
|
|
|
data::Array{T,N}
|
|
|
accesses::Ref{Int}
|
|
|
end
|
|
|
track_access(x) = AccessTrackerArray(x, Ref(0))
|
|
|
|
|
|
Base.IndexStyle(::Type{AccessTrackerArray}) = IndexLinear()
|
|
|
|
|
|
Base.size(x::AccessTrackerArray) = size(x.data)
|
|
|
Base.getindex(x::AccessTrackerArray, i::Int...) = (x.accesses[] += 1; x.data[i...])
|
|
|
Base.setindex!(x::AccessTrackerArray, v, i...) = (x.accesses[] += 1; x.data[i...] = v;)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 8bc930f0-f372-11ea-06cb-79ced2834720
|
|
|
md"""
|
|
|
#### Exercise 2.3 - _Exhaustive search with recursion_
|
|
|
|
|
|
Now use the `least_energy` function you defined above to define the `recursive_seam` function which takes the energies matrix and a starting pixel, and computes the seam with the lowest energy from that starting pixel.
|
|
|
|
|
|
This will give you the method used in the lecture to perform [exhaustive search of all possible paths](https://youtu.be/rpB6zQNsbQU?t=839).
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 85033040-f372-11ea-2c31-bb3147de3c0d
|
|
|
function recursive_seam(energies, starting_pixel)
|
|
|
m, n = size(energies)
|
|
|
min_v, seam = least_energy(energies, 1, starting_pixel)
|
|
|
return seam
|
|
|
# Replace the following line with your code.
|
|
|
#seams = [least_energy(energies, 1, j) for j in 1:n]
|
|
|
#min_v, min_idx = findmin([seam[1] for seam in seams])
|
|
|
#return seams[min_idx][2]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 1d55333c-f393-11ea-229a-5b1e9cabea6a
|
|
|
md"Compute shrunk image: $(@bind shrink_recursive CheckBox())"
|
|
|
|
|
|
# ╔═╡ c572f6ce-f372-11ea-3c9a-e3a21384edca
|
|
|
md"""
|
|
|
#### Exercise 2.4
|
|
|
|
|
|
- State clearly why this algorithm does an exhaustive search of all possible paths.
|
|
|
- How does the number of possible seam grow as n increases for a `m×n` image? (Big O notation is fine, or an approximation is fine).
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 6d993a5c-f373-11ea-0dde-c94e3bbd1552
|
|
|
exhaustive_observation = md"""
|
|
|
<your answer here>
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ ea417c2a-f373-11ea-3bb0-b1b5754f2fac
|
|
|
md"""
|
|
|
## **Exercise 3** - _Memoization_
|
|
|
|
|
|
**Memoization** is the name given to the technique of storing results to expensive function calls that will be accessed more than once.
|
|
|
|
|
|
As stated in the video, the function `least_energy` is called repeatedly with the same arguments. In fact, we call it on the order of $3^n$ times, when there are only really $m \times n$ unique ways to call it!
|
|
|
|
|
|
Lets implement memoization on this function with first a [dictionary](https://docs.julialang.org/en/v1/base/collections/#Dictionaries) for storage.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 56a7f954-f374-11ea-0391-f79b75195f4d
|
|
|
md"""
|
|
|
#### Exercise 3.1 - _Dictionary as storage_
|
|
|
|
|
|
Let's make a memoized version of least_energy function which takes a dictionary and
|
|
|
first checks to see if the dictionary contains the key (i,j) if it does, returns the value stored in that place, if not, will compute it, and store it in the dictionary at key (i, j) and return the value it computed.
|
|
|
|
|
|
|
|
|
`memoized_least_energy(energies, starting_pixel, memory)`
|
|
|
|
|
|
This function must recursively call itself, and pass the same `memory` object it received as an argument.
|
|
|
|
|
|
You are expected to read and understand the [documentation on dictionaries](https://docs.julialang.org/en/v1/base/collections/#Dictionaries) to find out how to:
|
|
|
|
|
|
1. Create a dictionary
|
|
|
2. Check if a key is stored in the dictionary
|
|
|
3. Access contents of the dictionary by a key.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ b1d09bc8-f320-11ea-26bb-0101c9a204e2
|
|
|
function memoized_least_energy(energies, i, j, memory)
|
|
|
m, n = size(energies)
|
|
|
if i == m
|
|
|
return (energies[i,j], 0)
|
|
|
end
|
|
|
|
|
|
if haskey(memory, (i, j))
|
|
|
return memory[(i, j)]
|
|
|
end
|
|
|
|
|
|
v = energies[i,j]
|
|
|
les = [memoized_least_energy(energies, i+1, j2, memory)
|
|
|
for j2 in max(1,j-1):min(n,j+1)]
|
|
|
min_v, min_idx = findmin([le[1] for le in les])
|
|
|
next_idx = j + min_idx - 2 + (j == 1)
|
|
|
le = (v + min_v, next_idx)
|
|
|
memory[(i, j)] = le
|
|
|
return le
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 3e8b0868-f3bd-11ea-0c15-011bbd6ac051
|
|
|
function recursive_memoized_seam(energies, starting_pixel)
|
|
|
memory = Dict{Tuple{Int,Int}, Tuple{Float64,Int64}}()
|
|
|
m, n = size(energies)
|
|
|
seam = Array{Int64}(undef, m)
|
|
|
seam[1] = starting_pixel
|
|
|
for i in 1:m-1
|
|
|
_, seam[i+1] = memoized_least_energy(energies, i, seam[i], memory)
|
|
|
end
|
|
|
return seam
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 4e3bcf88-f3c5-11ea-3ada-2ff9213647b7
|
|
|
md"Compute shrunk image: $(@bind shrink_dict CheckBox())"
|
|
|
|
|
|
# ╔═╡ 820b1ec4-337e-11eb-0a09-fdbc9f2bf983
|
|
|
md"Shrink by: $(@bind shrink_by Slider(1:200, show_value=true))"
|
|
|
|
|
|
# ╔═╡ cf39fa2a-f374-11ea-0680-55817de1b837
|
|
|
md"""
|
|
|
### Exercise 3.2 - _Matrix as storage_
|
|
|
|
|
|
The dictionary-based memoization we tried above works well in general as there is no restriction on what type of keys can be used.
|
|
|
|
|
|
But in our particular case, we can use a matrix as a storage, since a matrix is naturally keyed by two integers.
|
|
|
|
|
|
Write a variation of `matrix_memoized_least_energy` and `matrix_memoized_seam` which use a matrix as storage.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ c8724b5e-f3bd-11ea-0034-b92af21ca12d
|
|
|
function matrix_memoized_least_energy(energies, i, j, memory)
|
|
|
m, n = size(energies)
|
|
|
if i == m
|
|
|
return energies[i,j]
|
|
|
end
|
|
|
|
|
|
mv = memory[i,j]
|
|
|
if mv > -1
|
|
|
return mv
|
|
|
end
|
|
|
|
|
|
v = energies[i,j]
|
|
|
les = [matrix_memoized_least_energy(energies, i+1, j2, memory)
|
|
|
for j2 in max(1,j-1):min(n,j+1)]
|
|
|
v += minimum(les)
|
|
|
memory[i, j] = v
|
|
|
return v
|
|
|
end
|
|
|
|
|
|
# ╔═╡ be7d40e2-f320-11ea-1b56-dff2a0a16e8d
|
|
|
function matrix_memoized_seam(energies, starting_pixel)
|
|
|
memory = fill(-1.0, size(energies))
|
|
|
|
|
|
m, n = size(energies)
|
|
|
seam = Array{Int64}(undef, m)
|
|
|
seam[1] = starting_pixel
|
|
|
for i in 1:m-1
|
|
|
j = seam[i]
|
|
|
les = [matrix_memoized_least_energy(energies, i+1, j2, memory)
|
|
|
for j2 in max(1,j-1):min(n,j+1)]
|
|
|
min_v, min_idx = findmin(les)
|
|
|
seam[i+1] = j + min_idx - 2 + (j == 1)
|
|
|
end
|
|
|
return seam
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 507f3870-f3c5-11ea-11f6-ada3bb087634
|
|
|
md"Compute shrunk image: $(@bind shrink_matrix CheckBox())"
|
|
|
|
|
|
# ╔═╡ bd8974ca-3386-11eb-1bd6-956b45de09db
|
|
|
md"Shrink by: $(@bind matrix_shrink_by Slider(1:20, show_value=true))"
|
|
|
|
|
|
# ╔═╡ 24792456-f37b-11ea-07b2-4f4c8caea633
|
|
|
md"""
|
|
|
## **Exercise 4** - _Dynamic programming without recursion_
|
|
|
|
|
|
Now it's easy to see that the above algorithm is equivalent to one that populates the memory matrix in a for loop.
|
|
|
|
|
|
#### Exercise 4.1
|
|
|
|
|
|
👉 Write a function which takes the energies and returns the least energy matrix which has the least possible seam energy for each pixel. This was shown in the lecture, but attempt to write it on your own.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ ff055726-f320-11ea-32f6-2bf38d7dd310
|
|
|
function least_energy_matrix(im)
|
|
|
m, n = size(im)
|
|
|
sm = Array{Float64}(undef, size(im))
|
|
|
sd = Array{Int}(undef, size(im))
|
|
|
sm[end, :] .= im[end, :]
|
|
|
for i in m-1:-1:1
|
|
|
for j in 1:n
|
|
|
val = im[i,j]
|
|
|
min_below = 1000.0
|
|
|
path = 0
|
|
|
for k in max(1,j-1):min(n,j+1)
|
|
|
candidate = sm[i+1,k]
|
|
|
if candidate < min_below
|
|
|
min_below = candidate
|
|
|
path = k-j
|
|
|
end
|
|
|
end
|
|
|
sm[i,j] = min_below + val
|
|
|
sd[i,j] = path
|
|
|
end
|
|
|
end
|
|
|
return sm, sd
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 92e19f22-f37b-11ea-25f7-e321337e375e
|
|
|
md"""
|
|
|
#### Exercise 4.2
|
|
|
|
|
|
👉 Write a function which, when given the matrix returned by `least_energy_matrix` and a starting pixel (on the first row), computes the least energy seam from that pixel.
|
|
|
"""
|
|
|
|
|
|
# ╔═╡ 795eb2c4-f37b-11ea-01e1-1dbac3c80c13
|
|
|
function seam_from_precomputed_least_energy(energies, starting_pixel::Int)
|
|
|
least_energies, paths = least_energy_matrix(energies)
|
|
|
|
|
|
m, n = size(least_energies)
|
|
|
seam = Array{Int64}(undef, m)
|
|
|
|
|
|
seam[1] = starting_pixel
|
|
|
for i in 2:m
|
|
|
seam[i] = seam[i-1] + paths[i-1,seam[i-1]]
|
|
|
end
|
|
|
return seam
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 51df0c98-f3c5-11ea-25b8-af41dc182bac
|
|
|
md"Compute shrunk image: $(@bind shrink_bottomup CheckBox())"
|
|
|
|
|
|
# ╔═╡ cbb2e2bc-344d-11eb-3bc3-a1db9e697e32
|
|
|
md"Shrink by: $(@bind bottomup_n Slider(1:200, show_value=true))"
|
|
|
|
|
|
# ╔═╡ 0fbe2af6-f381-11ea-2f41-23cd1cf930d9
|
|
|
if student.kerberos_id === "jazz"
|
|
|
md"""
|
|
|
!!! danger "Oops!"
|
|
|
**Before you submit**, remember to fill in your name and kerberos ID at the top of this notebook!
|
|
|
"""
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 6b4d6584-f3be-11ea-131d-e5bdefcc791b
|
|
|
md"## Function library
|
|
|
|
|
|
Just some helper functions used in the notebook."
|
|
|
|
|
|
# ╔═╡ ef88c388-f388-11ea-3828-ff4db4d1874e
|
|
|
function mark_path(img, path)
|
|
|
img′ = copy(img)
|
|
|
m = size(img, 2)
|
|
|
for (i, j) in enumerate(path)
|
|
|
# To make it easier to see, we'll color not just
|
|
|
# the pixels of the seam, but also those adjacent to it
|
|
|
for j′ in j-1:j+1
|
|
|
img′[i, clamp(j′, 1, m)] = RGB(1,0,1)
|
|
|
end
|
|
|
end
|
|
|
img′
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 437ba6ce-f37d-11ea-1010-5f6a6e282f9b
|
|
|
function shrink_n(img, n, min_seam, imgs=[]; show_lightning=true)
|
|
|
n==0 && return push!(imgs, img)
|
|
|
|
|
|
e = energy(img)
|
|
|
seam_energy(seam) = sum(e[i, seam[i]] for i in 1:size(img, 1))
|
|
|
_, min_j = findmin(map(j->seam_energy(min_seam(e, j)), 1:size(e, 2)))
|
|
|
min_seam_vec = min_seam(e, min_j)
|
|
|
img′ = remove_in_each_row(img, min_seam_vec)
|
|
|
if show_lightning
|
|
|
push!(imgs, mark_path(img, min_seam_vec))
|
|
|
else
|
|
|
push!(imgs, img′)
|
|
|
end
|
|
|
shrink_n(img′, n-1, min_seam, imgs)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ f6571d86-f388-11ea-0390-05592acb9195
|
|
|
if shrink_greedy
|
|
|
greedy_carved = shrink_n(img, 200, greedy_seam)
|
|
|
md"Shrink by: $(@bind greedy_n Slider(1:200; show_value=true))"
|
|
|
end
|
|
|
|
|
|
# ╔═╡ f626b222-f388-11ea-0d94-1736759b5f52
|
|
|
if shrink_greedy
|
|
|
greedy_carved[greedy_n]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 51e28596-f3c5-11ea-2237-2b72bbfaa001
|
|
|
if shrink_bottomup
|
|
|
bottomup_carved = shrink_n(img, bottomup_n, seam_from_precomputed_least_energy)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 0a10acd8-f3c6-11ea-3e2f-7530a0af8c7f
|
|
|
if shrink_bottomup
|
|
|
bottomup_carved[15]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ ef26374a-f388-11ea-0b4e-67314a9a9094
|
|
|
function pencil(X)
|
|
|
f(x) = RGB(1-x,1-x,1-x)
|
|
|
map(f, X ./ maximum(X))
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 6bdbcf4c-f321-11ea-0288-fb16ff1ec526
|
|
|
function decimate(img, n)
|
|
|
img[1:n:end, 1:n:end]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ ddba07dc-f3b7-11ea-353e-0f67713727fc
|
|
|
# Do not make this image bigger, it will be infeasible to compute.
|
|
|
pika = decimate(load(download("https://art.pixilart.com/901d53bcda6b27b.png")),150)
|
|
|
|
|
|
# ╔═╡ 73b52fd6-f3b9-11ea-14ed-ebfcab1ce6aa
|
|
|
size(pika)
|
|
|
|
|
|
# ╔═╡ fa8e2772-f3b6-11ea-30f7-699717693164
|
|
|
if compute_access
|
|
|
tracked = track_access(energy(pika))
|
|
|
least_energy(tracked, 1,7)
|
|
|
tracked.accesses[]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ d88bc272-f392-11ea-0efd-15e0e2b2cd4e
|
|
|
if shrink_recursive
|
|
|
recursive_carved = shrink_n(pika, 3, recursive_seam)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ e66ef06a-f392-11ea-30ab-7160e7723a17
|
|
|
if shrink_recursive
|
|
|
recursive_carved[recursive_n]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ e1273072-337f-11eb-339e-9dfcf929e8f9
|
|
|
pika2 = decimate(load(download("https://art.pixilart.com/901d53bcda6b27b.png")),10)[10:end-10,10:end-10]
|
|
|
|
|
|
# ╔═╡ ef32b196-337f-11eb-30ba-4f3f70c7af00
|
|
|
size(pika2)
|
|
|
|
|
|
# ╔═╡ 4e3ef866-f3c5-11ea-3fb0-27d1ca9a9a3f
|
|
|
if shrink_dict
|
|
|
dict_carved = shrink_n(pika2, shrink_by, recursive_memoized_seam)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 6e73b1da-f3c5-11ea-145f-6383effe8a89
|
|
|
if shrink_dict
|
|
|
dict_carved[shrink_by]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 50829af6-f3c5-11ea-04a8-0535edd3b0aa
|
|
|
if shrink_matrix
|
|
|
matrix_carved = shrink_n(pika2, matrix_shrink_by, matrix_memoized_seam)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 9e56ecfa-f3c5-11ea-2e90-3b1839d12038
|
|
|
if shrink_matrix
|
|
|
matrix_carved[matrix_shrink_by]
|
|
|
end
|
|
|
|
|
|
# ╔═╡ ffc17f40-f380-11ea-30ee-0fe8563c0eb1
|
|
|
hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text]))
|
|
|
|
|
|
# ╔═╡ 9f18efe2-f38e-11ea-0871-6d7760d0b2f6
|
|
|
hint(md"You can call the `least_energy` function recursively within itself to obtain the least energy of the adjacent cells and add the energy at the current cell to get the total energy.")
|
|
|
|
|
|
# ╔═╡ ffc40ab2-f380-11ea-2136-63542ff0f386
|
|
|
almost(text) = Markdown.MD(Markdown.Admonition("warning", "Almost there!", [text]))
|
|
|
|
|
|
# ╔═╡ ffceaed6-f380-11ea-3c63-8132d270b83f
|
|
|
still_missing(text=md"Replace `missing` with your answer.") = Markdown.MD(Markdown.Admonition("warning", "Here we go!", [text]))
|
|
|
|
|
|
# ╔═╡ ffde44ae-f380-11ea-29fb-2dfcc9cda8b4
|
|
|
keep_working(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("danger", "Keep working on it!", [text]))
|
|
|
|
|
|
# ╔═╡ 980b1104-f394-11ea-0948-21002f26ee25
|
|
|
function visualize_seam_algorithm(algorithm, test_img, starting_pixel)
|
|
|
seam = algorithm(test_img, starting_pixel)
|
|
|
|
|
|
display_img = RGB.(test_img)
|
|
|
for (i, j) in enumerate(seam)
|
|
|
try
|
|
|
display_img[i, j] = RGB(0.9, 0.3, 0.6)
|
|
|
catch ex
|
|
|
if ex isa BoundsError
|
|
|
return keep_working("")
|
|
|
end
|
|
|
# the solution might give an illegal index
|
|
|
end
|
|
|
end
|
|
|
display_img
|
|
|
end;
|
|
|
|
|
|
# ╔═╡ 2a7e49b8-f395-11ea-0058-013e51baa554
|
|
|
visualize_seam_algorithm(greedy_seam, greedy_test, greedy_starting_pixel)
|
|
|
|
|
|
# ╔═╡ ffe326e0-f380-11ea-3619-61dd0592d409
|
|
|
yays = [md"Great!", md"Yay ❤", md"Great! 🎉", md"Well done!", md"Keep it up!", md"Good job!", md"Awesome!", md"You got the right answer!", md"Let's move on to the next section."]
|
|
|
|
|
|
# ╔═╡ fff5aedc-f380-11ea-2a08-99c230f8fa32
|
|
|
correct(text=rand(yays)) = Markdown.MD(Markdown.Admonition("correct", "Got it!", [text]))
|
|
|
|
|
|
# ╔═╡ e3519118-f387-11ea-0c61-e1c2de1c24c1
|
|
|
if performance_experiment_without_vcat ⧀ performance_experiment_default
|
|
|
correct()
|
|
|
else
|
|
|
keep_working(md"We are still using (roughly) the same number of allocations as the default implementation.")
|
|
|
end
|
|
|
|
|
|
# ╔═╡ d4ea4222-f388-11ea-3c8d-db0d651f5282
|
|
|
if performance_experiment_views ⧀ performance_experiment_default
|
|
|
if minimum(performance_experiment_views).allocs < 10
|
|
|
correct()
|
|
|
else
|
|
|
keep_working(md"We are still using (roughly) the same number of allocations as the implementation without `vcat`.")
|
|
|
end
|
|
|
else
|
|
|
keep_working(md"We are still using (roughly) the same number of allocations as the default implementation.")
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 00026442-f381-11ea-2b41-bde1fff66011
|
|
|
not_defined(variable_name) = Markdown.MD(Markdown.Admonition("danger", "Oopsie!", [md"Make sure that you define a variable called **$(Markdown.Code(string(variable_name)))**"]))
|
|
|
|
|
|
# ╔═╡ 145c0f58-f384-11ea-2b71-09ae83f66da2
|
|
|
if !@isdefined(views_observation)
|
|
|
not_defined(:views_observation)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ d7a9c000-f383-11ea-1516-cf71102d8e94
|
|
|
if !@isdefined(views_observation)
|
|
|
not_defined(:views_observation)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ e0622780-f3b4-11ea-1f44-59fb9c5d2ebd
|
|
|
if !@isdefined(least_energy_matrix)
|
|
|
not_defined(:least_energy_matrix)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 946b69a0-f3a2-11ea-2670-819a5dafe891
|
|
|
if !@isdefined(seam_from_precomputed_least_energy)
|
|
|
not_defined(:seam_from_precomputed_least_energy)
|
|
|
end
|
|
|
|
|
|
# ╔═╡ fbf6b0fa-f3e0-11ea-2009-573a218e2460
|
|
|
function hbox(x, y, gap=16; sy=size(y), sx=size(x))
|
|
|
w,h = (max(sx[1], sy[1]),
|
|
|
gap + sx[2] + sy[2])
|
|
|
|
|
|
slate = fill(RGB(1,1,1), w,h)
|
|
|
slate[1:size(x,1), 1:size(x,2)] .= RGB.(x)
|
|
|
slate[1:size(y,1), size(x,2) + gap .+ (1:size(y,2))] .= RGB.(y)
|
|
|
slate
|
|
|
end
|
|
|
|
|
|
# ╔═╡ f010933c-f318-11ea-22c5-4d2e64cd9629
|
|
|
begin
|
|
|
hbox(
|
|
|
float_to_color.(convolve(brightness.(img), Kernel.sobel()[1])),
|
|
|
float_to_color.(convolve(brightness.(img), Kernel.sobel()[2])))
|
|
|
end
|
|
|
|
|
|
# ╔═╡ 256edf66-f3e1-11ea-206e-4f9b4f6d3a3d
|
|
|
vbox(x,y, gap=16) = hbox(x', y')'
|
|
|
|
|
|
# ╔═╡ 00115b6e-f381-11ea-0bc6-61ca119cb628
|
|
|
bigbreak = html"<br><br><br><br><br>";
|
|
|
|
|
|
# ╔═╡ c086bd1e-f384-11ea-3b26-2da9e24360ca
|
|
|
bigbreak
|
|
|
|
|
|
# ╔═╡ 8d558c4c-f328-11ea-0055-730ead5d5c34
|
|
|
bigbreak
|
|
|
|
|
|
# ╔═╡ f7eba2b6-f388-11ea-06ad-0b861c764d61
|
|
|
bigbreak
|
|
|
|
|
|
# ╔═╡ 4f48c8b8-f39d-11ea-25d2-1fab031a514f
|
|
|
bigbreak
|
|
|
|
|
|
# ╔═╡ 48089a00-f321-11ea-1479-e74ba71df067
|
|
|
bigbreak
|
|
|
|
|
|
# ╔═╡ Cell order:
|
|
|
# ╟─e6b6760a-f37f-11ea-3ae1-65443ef5a81a
|
|
|
# ╟─ec66314e-f37f-11ea-0af4-31da0584e881
|
|
|
# ╟─85cfbd10-f384-11ea-31dc-b5693630a4c5
|
|
|
# ╠═33e43c7c-f381-11ea-3abc-c942327456b1
|
|
|
# ╟─938185ec-f384-11ea-21dc-b56b7469f798
|
|
|
# ╠═a4937996-f314-11ea-2ff9-615c888afaa8
|
|
|
# ╠═0d144802-f319-11ea-0028-cd97a776a3d0
|
|
|
# ╟─cc9fcdae-f314-11ea-1b9a-1f68b792f005
|
|
|
# ╟─b49a21a6-f381-11ea-1a98-7f144c55c9b7
|
|
|
# ╟─b49e8cc8-f381-11ea-1056-91668ac6ae4e
|
|
|
# ╠═e799be82-f317-11ea-3ae4-6d13ece3fe10
|
|
|
# ╟─c075a8e6-f382-11ea-2263-cd9507324f4f
|
|
|
# ╠═9cced1a8-f326-11ea-0759-0b2f22e5a1db
|
|
|
# ╟─c086bd1e-f384-11ea-3b26-2da9e24360ca
|
|
|
# ╟─1d893998-f366-11ea-0828-512de0c44915
|
|
|
# ╟─59991872-f366-11ea-1036-afe313fb4ec1
|
|
|
# ╠═e501ea28-f326-11ea-252a-53949fd9ef57
|
|
|
# ╟─f7915918-f366-11ea-2c46-2f4671ae8a22
|
|
|
# ╠═37d4ea5c-f327-11ea-2cc5-e3774c232c2b
|
|
|
# ╠═67717d02-f327-11ea-0988-bfe661f57f77
|
|
|
# ╟─9e149cd2-f367-11ea-28ef-b9533e8a77bb
|
|
|
# ╟─e3519118-f387-11ea-0c61-e1c2de1c24c1
|
|
|
# ╟─ba1619d4-f389-11ea-2b3f-fd9ba71cf7e3
|
|
|
# ╠═e49235a4-f367-11ea-3913-f54a4a6b2d6b
|
|
|
# ╟─145c0f58-f384-11ea-2b71-09ae83f66da2
|
|
|
# ╟─837c43a4-f368-11ea-00a3-990a45cb0cbd
|
|
|
# ╠═90a22cc6-f327-11ea-1484-7fda90283797
|
|
|
# ╠═3335e07c-f328-11ea-0e6c-8d38c8c0ad5b
|
|
|
# ╟─d4ea4222-f388-11ea-3c8d-db0d651f5282
|
|
|
# ╟─40d6f562-f329-11ea-2ee4-d7806a16ede3
|
|
|
# ╟─4f0975d8-f329-11ea-3d10-59a503f8d6b2
|
|
|
# ╟─dc63d32a-f387-11ea-37e2-6f3666a72e03
|
|
|
# ╟─7eaa57d2-f368-11ea-1a70-c7c7e54bd0b1
|
|
|
# ╠═fd819dac-f368-11ea-33bb-17148387546a
|
|
|
# ╟─d7a9c000-f383-11ea-1516-cf71102d8e94
|
|
|
# ╟─8d558c4c-f328-11ea-0055-730ead5d5c34
|
|
|
# ╟─318a2256-f369-11ea-23a9-2f74c566549b
|
|
|
# ╟─7a44ba52-f318-11ea-0406-4731c80c1007
|
|
|
# ╠═6c7e4b54-f318-11ea-2055-d9f9c0199341
|
|
|
# ╠═74059d04-f319-11ea-29b4-85f5f8f5c610
|
|
|
# ╟─0b9ead92-f318-11ea-3744-37150d649d43
|
|
|
# ╠═d184e9cc-f318-11ea-1a1e-994ab1330c1a
|
|
|
# ╠═cdfb3508-f319-11ea-1486-c5c58a0b9177
|
|
|
# ╟─f010933c-f318-11ea-22c5-4d2e64cd9629
|
|
|
# ╟─5fccc7cc-f369-11ea-3b9e-2f0eca7f0f0e
|
|
|
# ╠═6f37b34c-f31a-11ea-2909-4f2079bf66ec
|
|
|
# ╠═9fa0cd3a-f3e1-11ea-2f7e-bd73b8e3f302
|
|
|
# ╟─f7eba2b6-f388-11ea-06ad-0b861c764d61
|
|
|
# ╟─87afabf8-f317-11ea-3cb3-29dced8e265a
|
|
|
# ╟─8ba9f5fc-f31b-11ea-00fe-79ecece09c25
|
|
|
# ╟─f5a74dfc-f388-11ea-2577-b543d31576c6
|
|
|
# ╟─c3543ea4-f393-11ea-39c8-37747f113b96
|
|
|
# ╟─2f9cbea8-f3a1-11ea-20c6-01fd1464a592
|
|
|
# ╠═abf20aa0-f31b-11ea-2548-9bea4fab4c37
|
|
|
# ╟─5430d772-f397-11ea-2ed8-03ee06d02a22
|
|
|
# ╟─f580527e-f397-11ea-055f-bb9ea8f12015
|
|
|
# ╟─6f52c1a2-f395-11ea-0c8a-138a77f03803
|
|
|
# ╠═2a7e49b8-f395-11ea-0058-013e51baa554
|
|
|
# ╟─7ddee6fc-f394-11ea-31fc-5bd665a65bef
|
|
|
# ╟─980b1104-f394-11ea-0948-21002f26ee25
|
|
|
# ╟─9945ae78-f395-11ea-1d78-cf6ad19606c8
|
|
|
# ╟─87efe4c2-f38d-11ea-39cc-bdfa11298317
|
|
|
# ╟─f6571d86-f388-11ea-0390-05592acb9195
|
|
|
# ╟─f626b222-f388-11ea-0d94-1736759b5f52
|
|
|
# ╟─52452d26-f36c-11ea-01a6-313114b4445d
|
|
|
# ╠═2a98f268-f3b6-11ea-1eea-81c28256a19e
|
|
|
# ╟─32e9a944-f3b6-11ea-0e82-1dff6c2eef8d
|
|
|
# ╟─9101d5a0-f371-11ea-1c04-f3f43b96ca4a
|
|
|
# ╠═ddba07dc-f3b7-11ea-353e-0f67713727fc
|
|
|
# ╠═73b52fd6-f3b9-11ea-14ed-ebfcab1ce6aa
|
|
|
# ╠═8ec27ef8-f320-11ea-2573-c97b7b908cb7
|
|
|
# ╟─9f18efe2-f38e-11ea-0871-6d7760d0b2f6
|
|
|
# ╟─a7f3d9f8-f3bb-11ea-0c1a-55bbb8408f09
|
|
|
# ╟─fa8e2772-f3b6-11ea-30f7-699717693164
|
|
|
# ╟─18e0fd8a-f3bc-11ea-0713-fbf74d5fa41a
|
|
|
# ╟─cbf29020-f3ba-11ea-2cb0-b92836f3d04b
|
|
|
# ╟─8bc930f0-f372-11ea-06cb-79ced2834720
|
|
|
# ╠═85033040-f372-11ea-2c31-bb3147de3c0d
|
|
|
# ╠═1d55333c-f393-11ea-229a-5b1e9cabea6a
|
|
|
# ╠═e1273072-337f-11eb-339e-9dfcf929e8f9
|
|
|
# ╠═ef32b196-337f-11eb-30ba-4f3f70c7af00
|
|
|
# ╠═d88bc272-f392-11ea-0efd-15e0e2b2cd4e
|
|
|
# ╠═e66ef06a-f392-11ea-30ab-7160e7723a17
|
|
|
# ╟─c572f6ce-f372-11ea-3c9a-e3a21384edca
|
|
|
# ╠═6d993a5c-f373-11ea-0dde-c94e3bbd1552
|
|
|
# ╟─ea417c2a-f373-11ea-3bb0-b1b5754f2fac
|
|
|
# ╟─56a7f954-f374-11ea-0391-f79b75195f4d
|
|
|
# ╠═b1d09bc8-f320-11ea-26bb-0101c9a204e2
|
|
|
# ╠═3e8b0868-f3bd-11ea-0c15-011bbd6ac051
|
|
|
# ╟─4e3bcf88-f3c5-11ea-3ada-2ff9213647b7
|
|
|
# ╠═820b1ec4-337e-11eb-0a09-fdbc9f2bf983
|
|
|
# ╠═4e3ef866-f3c5-11ea-3fb0-27d1ca9a9a3f
|
|
|
# ╠═6e73b1da-f3c5-11ea-145f-6383effe8a89
|
|
|
# ╟─cf39fa2a-f374-11ea-0680-55817de1b837
|
|
|
# ╠═c8724b5e-f3bd-11ea-0034-b92af21ca12d
|
|
|
# ╠═be7d40e2-f320-11ea-1b56-dff2a0a16e8d
|
|
|
# ╟─507f3870-f3c5-11ea-11f6-ada3bb087634
|
|
|
# ╠═bd8974ca-3386-11eb-1bd6-956b45de09db
|
|
|
# ╠═50829af6-f3c5-11ea-04a8-0535edd3b0aa
|
|
|
# ╠═9e56ecfa-f3c5-11ea-2e90-3b1839d12038
|
|
|
# ╟─4f48c8b8-f39d-11ea-25d2-1fab031a514f
|
|
|
# ╟─24792456-f37b-11ea-07b2-4f4c8caea633
|
|
|
# ╠═ff055726-f320-11ea-32f6-2bf38d7dd310
|
|
|
# ╟─e0622780-f3b4-11ea-1f44-59fb9c5d2ebd
|
|
|
# ╟─92e19f22-f37b-11ea-25f7-e321337e375e
|
|
|
# ╠═795eb2c4-f37b-11ea-01e1-1dbac3c80c13
|
|
|
# ╠═51df0c98-f3c5-11ea-25b8-af41dc182bac
|
|
|
# ╠═cbb2e2bc-344d-11eb-3bc3-a1db9e697e32
|
|
|
# ╠═51e28596-f3c5-11ea-2237-2b72bbfaa001
|
|
|
# ╠═0a10acd8-f3c6-11ea-3e2f-7530a0af8c7f
|
|
|
# ╟─946b69a0-f3a2-11ea-2670-819a5dafe891
|
|
|
# ╟─0fbe2af6-f381-11ea-2f41-23cd1cf930d9
|
|
|
# ╟─48089a00-f321-11ea-1479-e74ba71df067
|
|
|
# ╟─6b4d6584-f3be-11ea-131d-e5bdefcc791b
|
|
|
# ╠═437ba6ce-f37d-11ea-1010-5f6a6e282f9b
|
|
|
# ╟─ef88c388-f388-11ea-3828-ff4db4d1874e
|
|
|
# ╟─ef26374a-f388-11ea-0b4e-67314a9a9094
|
|
|
# ╟─6bdbcf4c-f321-11ea-0288-fb16ff1ec526
|
|
|
# ╟─ffc17f40-f380-11ea-30ee-0fe8563c0eb1
|
|
|
# ╟─ffc40ab2-f380-11ea-2136-63542ff0f386
|
|
|
# ╟─ffceaed6-f380-11ea-3c63-8132d270b83f
|
|
|
# ╟─ffde44ae-f380-11ea-29fb-2dfcc9cda8b4
|
|
|
# ╟─ffe326e0-f380-11ea-3619-61dd0592d409
|
|
|
# ╟─fff5aedc-f380-11ea-2a08-99c230f8fa32
|
|
|
# ╟─00026442-f381-11ea-2b41-bde1fff66011
|
|
|
# ╟─fbf6b0fa-f3e0-11ea-2009-573a218e2460
|
|
|
# ╟─256edf66-f3e1-11ea-206e-4f9b4f6d3a3d
|
|
|
# ╟─00115b6e-f381-11ea-0bc6-61ca119cb628
|