seam carving

main
Bryce Allen 4 years ago
parent ebd9466c93
commit 4b9f7fcbe8

@ -0,0 +1,263 @@
### A Pluto.jl notebook ###
# v0.11.12
using Markdown
using InteractiveUtils
# ╔═╡ 0d3aec92-edeb-11ea-3adb-cd0dc17cbdab
md"# A basic Julia syntax cheatsheet
This notebook briefly summarizes some of the basic Julia syntax that we will need for the problem sets.
"
# ╔═╡ 3b038ee0-edeb-11ea-0977-97cc30d1c6ff
md"## Variables
We can define a variable using `=` (assignment). Then we can use its value in other expressions:
"
# ╔═╡ 3e8e0ea0-edeb-11ea-22e0-c58f7c2168ce
x = 3
# ╔═╡ 59b66862-edeb-11ea-2d62-71dcc79dbfab
y = 2x
# ╔═╡ 5e062a24-edeb-11ea-256a-d938f77d7815
md"By default Julia displays the output of the last operation. (You can suppress the output by adding `;` (a semicolon) at the end.)
"
# ╔═╡ 7e46f0e8-edeb-11ea-1092-4b5e8acd9ee0
md"We can ask what type a variable has using `typeof`:"
# ╔═╡ 8a695b86-edeb-11ea-08cc-17263bec09df
typeof(y)
# ╔═╡ 8e2dd3be-edeb-11ea-0703-354fb31c12f5
md"## Functions"
# ╔═╡ 96b5a28c-edeb-11ea-11c0-597615962f54
md"We can use a short-form, one-line function definition for simple functions:"
# ╔═╡ a7453572-edeb-11ea-1e27-9f710fd856a6
f(x) = 2 + x
# ╔═╡ b341db4e-edeb-11ea-078b-b71ac00089d7
md"Typing the function's name gives information about the function. To call it we must use parentheses:"
# ╔═╡ 23f9afd4-eded-11ea-202a-9f0f1f91e5ad
f
# ╔═╡ cc1f6872-edeb-11ea-33e9-6976fd9b107a
f(10)
# ╔═╡ ce9667c2-edeb-11ea-2665-d789032abd11
md"For longer functions we use the following syntax with the `function` keyword and `end`:"
# ╔═╡ d73d3400-edeb-11ea-2dea-95e8c4a6563b
function g(x, y)
z = x + y
return z^2
end
# ╔═╡ e04ccf10-edeb-11ea-36d1-d11969e4b2f2
g(1, 2)
# ╔═╡ e297c5cc-edeb-11ea-3bdd-090f415685ab
md"## For loops"
# ╔═╡ ec751446-edeb-11ea-31ba-2372e7c71b42
md"Use `for` to loop through a pre-determined set of values:"
# ╔═╡ fe3fa290-edeb-11ea-121e-7114e5c573c1
let s = 0
for i in 1:10
s += i # Equivalent to s = s + i
end
s
end
# ╔═╡ 394b0ec8-eded-11ea-31fb-27392068ef8f
md"Here, `1:10` is a **range** representing the numbers from 1 to 10:"
# ╔═╡ 4dc00908-eded-11ea-25c5-0f7b2b7e18f9
typeof(1:10)
# ╔═╡ 6c44abb4-edec-11ea-16bd-557800b5f9d2
md"Above we used a `let` block to define a new local variable `s`.
But blocks of code like this are usually better inside functions, so that they can be reused. For example, we could rewrite the above as follows:
"
# ╔═╡ 683af3e2-eded-11ea-25a5-0d90bf099d98
function mysum(n)
s = 0
for i in 1:n
s += i
end
return s
end
# ╔═╡ 76764ea2-eded-11ea-1aa6-296f3421de1c
mysum(100)
# ╔═╡ 93a231f4-edec-11ea-3b39-299b3be2da78
md"## Conditionals: `if`"
# ╔═╡ 82e63a24-eded-11ea-3887-15d6bfabea4b
md"We can evaluate whether a condition is true or not by simply writing the condition:"
# ╔═╡ 9b339b2a-eded-11ea-10d7-8fc9a907c892
a = 3
# ╔═╡ 9535eb40-eded-11ea-1651-e33c9c23dbfb
a < 5
# ╔═╡ a16299a2-eded-11ea-2b56-93eb7a1010a7
md"We see that conditions have a Boolean (`true` or `false`) value.
We can then use `if` to control what we do based on that value:"
# ╔═╡ bc6b124e-eded-11ea-0290-b3760cb81024
if a < 5
"small"
else
"big"
end
# ╔═╡ cfb21014-eded-11ea-1261-3bc30952a88e
md"""Note that the `if` also returns the last value that was evaluated, in this case the string `"small"` or `"big"`, Since Pluto is reactive, changing the definition of `a` above will automatically cause this to be reevaluated!"""
# ╔═╡ ffee7d80-eded-11ea-26b1-1331df204c67
md"## Arrays"
# ╔═╡ cae4137e-edee-11ea-14af-59a32227de1b
md"### 1D arrays (`Vector`s)"
# ╔═╡ 714f4fca-edee-11ea-3410-c9ab8825d836
md"We can make a `Vector` (1-dimensional, or 1D array) using square brackets:"
# ╔═╡ 82cc2a0e-edee-11ea-11b7-fbaa5ad7b556
v = [1, 2, 3]
# ╔═╡ 85916c18-edee-11ea-0738-5f5d78875b86
typeof(v)
# ╔═╡ 881b7d0c-edee-11ea-0b4a-4bd7d5be2c77
md"The `1` in the type shows that this is a 1D array.
We access elements also using square brackets:"
# ╔═╡ a298e8ae-edee-11ea-3613-0dd4bae70c26
v[2]
# ╔═╡ a5ebddd6-edee-11ea-2234-55453ea59c5a
v[2] = 10
# ╔═╡ a9b48e54-edee-11ea-1333-a96181de0185
md"Note that Pluto does not automatically update cells when you modify elements of an array, but the value does change."
# ╔═╡ 68c4ead2-edef-11ea-124a-03c2d7dd6a1b
md"A nice way to create `Vector`s following a certain pattern is to use an **array comprehension**:"
# ╔═╡ 84129294-edef-11ea-0c77-ffa2b9592a26
v2 = [i^2 for i in 1:10]
# ╔═╡ d364fa16-edee-11ea-2050-0f6cb70e1bcf
md"## 2D arrays (matrices)"
# ╔═╡ db99ae9a-edee-11ea-393e-9de420a545a1
md"We can make small matrices (2D arrays) with square brackets too:"
# ╔═╡ 04f175f2-edef-11ea-0882-712548ebb7a3
M = [1 2
3 4]
# ╔═╡ 0a8ac112-edef-11ea-1e99-cf7c7808c4f5
typeof(M)
# ╔═╡ 1295f48a-edef-11ea-22a5-61e8a2e1d005
md"The `2` in the type confirms that this is a 2D array."
# ╔═╡ 3e1fdaa8-edef-11ea-2f03-eb41b2b9ea0f
md"This won't work for larger matrices, though. For that we can use e.g."
# ╔═╡ 48f3deca-edef-11ea-2c18-e7419c9030a0
zeros(5, 5)
# ╔═╡ a8f26af8-edef-11ea-2fc7-2b776f515aea
md"Note that `zeros` gives `Float64`s by default. We can also specify a type for the elements:"
# ╔═╡ b595373e-edef-11ea-03e2-6599ef14af20
zeros(Int, 4, 5)
# ╔═╡ 4cb33c04-edef-11ea-2b35-1139c246c331
md"We can then fill in the values we want by manipulating the elements, e.g. with a `for` loop."
# ╔═╡ 54e47e9e-edef-11ea-2d75-b5f550902528
md"A nice alternative syntax to create matrices following a certain pattern is an array comprehension with a *double* `for` loop:"
# ╔═╡ 6348edce-edef-11ea-1ab4-019514eb414f
[i + j for i in 1:5, j in 1:6]
# ╔═╡ Cell order:
# ╟─0d3aec92-edeb-11ea-3adb-cd0dc17cbdab
# ╟─3b038ee0-edeb-11ea-0977-97cc30d1c6ff
# ╠═3e8e0ea0-edeb-11ea-22e0-c58f7c2168ce
# ╠═59b66862-edeb-11ea-2d62-71dcc79dbfab
# ╟─5e062a24-edeb-11ea-256a-d938f77d7815
# ╟─7e46f0e8-edeb-11ea-1092-4b5e8acd9ee0
# ╠═8a695b86-edeb-11ea-08cc-17263bec09df
# ╟─8e2dd3be-edeb-11ea-0703-354fb31c12f5
# ╟─96b5a28c-edeb-11ea-11c0-597615962f54
# ╠═a7453572-edeb-11ea-1e27-9f710fd856a6
# ╟─b341db4e-edeb-11ea-078b-b71ac00089d7
# ╠═23f9afd4-eded-11ea-202a-9f0f1f91e5ad
# ╠═cc1f6872-edeb-11ea-33e9-6976fd9b107a
# ╟─ce9667c2-edeb-11ea-2665-d789032abd11
# ╠═d73d3400-edeb-11ea-2dea-95e8c4a6563b
# ╠═e04ccf10-edeb-11ea-36d1-d11969e4b2f2
# ╟─e297c5cc-edeb-11ea-3bdd-090f415685ab
# ╟─ec751446-edeb-11ea-31ba-2372e7c71b42
# ╠═fe3fa290-edeb-11ea-121e-7114e5c573c1
# ╟─394b0ec8-eded-11ea-31fb-27392068ef8f
# ╠═4dc00908-eded-11ea-25c5-0f7b2b7e18f9
# ╟─6c44abb4-edec-11ea-16bd-557800b5f9d2
# ╠═683af3e2-eded-11ea-25a5-0d90bf099d98
# ╠═76764ea2-eded-11ea-1aa6-296f3421de1c
# ╟─93a231f4-edec-11ea-3b39-299b3be2da78
# ╟─82e63a24-eded-11ea-3887-15d6bfabea4b
# ╠═9b339b2a-eded-11ea-10d7-8fc9a907c892
# ╠═9535eb40-eded-11ea-1651-e33c9c23dbfb
# ╟─a16299a2-eded-11ea-2b56-93eb7a1010a7
# ╠═bc6b124e-eded-11ea-0290-b3760cb81024
# ╟─cfb21014-eded-11ea-1261-3bc30952a88e
# ╟─ffee7d80-eded-11ea-26b1-1331df204c67
# ╟─cae4137e-edee-11ea-14af-59a32227de1b
# ╟─714f4fca-edee-11ea-3410-c9ab8825d836
# ╠═82cc2a0e-edee-11ea-11b7-fbaa5ad7b556
# ╠═85916c18-edee-11ea-0738-5f5d78875b86
# ╟─881b7d0c-edee-11ea-0b4a-4bd7d5be2c77
# ╠═a298e8ae-edee-11ea-3613-0dd4bae70c26
# ╠═a5ebddd6-edee-11ea-2234-55453ea59c5a
# ╟─a9b48e54-edee-11ea-1333-a96181de0185
# ╟─68c4ead2-edef-11ea-124a-03c2d7dd6a1b
# ╠═84129294-edef-11ea-0c77-ffa2b9592a26
# ╟─d364fa16-edee-11ea-2050-0f6cb70e1bcf
# ╟─db99ae9a-edee-11ea-393e-9de420a545a1
# ╠═04f175f2-edef-11ea-0882-712548ebb7a3
# ╠═0a8ac112-edef-11ea-1e99-cf7c7808c4f5
# ╟─1295f48a-edef-11ea-22a5-61e8a2e1d005
# ╟─3e1fdaa8-edef-11ea-2f03-eb41b2b9ea0f
# ╠═48f3deca-edef-11ea-2c18-e7419c9030a0
# ╟─a8f26af8-edef-11ea-2fc7-2b776f515aea
# ╠═b595373e-edef-11ea-03e2-6599ef14af20
# ╟─4cb33c04-edef-11ea-2b35-1139c246c331
# ╟─54e47e9e-edef-11ea-2d75-b5f550902528
# ╠═6348edce-edef-11ea-1ab4-019514eb414f

@ -0,0 +1,807 @@
### A Pluto.jl notebook ###
# v0.11.12
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
# ╔═╡ 5e688928-e939-11ea-0e16-fbc80af390ab
using LinearAlgebra
# ╔═╡ a50b5f48-e8d5-11ea-1f05-a3741b5d15ba
html"<button onclick=present()>Present</button>"
# ╔═╡ 8a6fed4c-e94b-11ea-1113-d56f56fb293b
br = HTML("<br>")
# ╔═╡ dc53f316-e8c8-11ea-150f-1374dbce114a
md"""# Welcome to 18.S191 -- Fall 2020!
### Introduction to Computational Thinking for Real-World Problems"""
# ╔═╡ c3f43d66-e94b-11ea-02bd-23cfeb878ff1
br
# ╔═╡ c6c77738-e94b-11ea-22f5-1dce3dbcc3ca
md"### <https://github.com/mitmath/18S191>"
# ╔═╡ cf80793a-e94b-11ea-0120-f7913ae06f22
br
# ╔═╡ d1638d96-e94b-11ea-2ff4-910e399f864d
md"##### Alan Edelman, David P. Sanders, Grant Sanderson, James Schloss"
# ╔═╡ 0117246a-e94c-11ea-1a76-c981ce8e725d
md"##### & Philip the Corgi"
# ╔═╡ 27060098-e8c9-11ea-2fe0-03b39b1ddc32
md"""# Class outline
### Data and computation
- Module 1: Analyzing images
- Module 2: Particles and ray tracing
- Module 3: Epidemic spread
- Module 4: Climate change
"""
# ╔═╡ 4fc58814-e94b-11ea-339b-cb714a63f9b6
md"## Tools
- Julia programming language: <http://www.julialang.org/learning>
- Pluto notebook environment
"
# ╔═╡ f067d3b8-e8c8-11ea-20cb-474709ffa99a
md"""# Module 1: Images"""
# ╔═╡ 37c1d012-ebc9-11ea-2dfe-8b86bb78f283
4 + 4
# ╔═╡ a0a97214-e8d2-11ea-0f46-0bfaf016ab6d
md"""## Data takes many forms
- Time series:
- Number of infections per day
- Stock price each minute
- A piece for violin broadcast over the radio
$(HTML("<br>"))
- Video:
- The view from a window of a self-driving car
- A hurricane monitoring station
$(HTML("<br>"))
- Images:
- Diseased versus healthy tissue in a scan
- Deep space via the Hubble telescope
- Can your social media account recognise your friends?
"""
# ╔═╡ 1697a756-e93d-11ea-0b6e-c9c78d527993
md"## Capture your own image!"
# ╔═╡ af28faca-ebb7-11ea-130d-0f94bf9bd836
# ╔═╡ ee1d1596-e94a-11ea-0fb4-cd05f62471d3
md"##"
# ╔═╡ 8ab9a978-e8c9-11ea-2476-f1ef4ba1b619
md"""## What is an image?"""
# ╔═╡ 38c54bfc-e8cb-11ea-3d52-0f02452f8ba1
md"Albrecht Dürer:"
# ╔═╡ 983f8270-e8c9-11ea-29d2-adeccb5a7ffc
# md"# begin
# using Images
# download("https://i.stack.imgur.com/QQL8X.jpg", "durer.jpg")
# load("durer.jpg")
# end
md"![](https://i.stack.imgur.com/QQL8X.jpg)"
# ╔═╡ 2fcaef88-e8ca-11ea-23f7-29c48580f43c
md"""##
An image is:
- A 2D representation of a 3D world
- An approximation
"""
# ╔═╡ 7636c4b0-e8d1-11ea-2051-757a850a9d30
begin
image_text =
md"""
## What *is* an image, though?
- A grid of coloured squares called **pixels**
- A colour for each pair $(i, j)$ of indices
- A **discretization**
"""
image_text
end
# ╔═╡ bca22176-e8ca-11ea-2004-ebeb103116b5
md"""
## How can we store an image in the computer?
- Is it a 1D array (`Vector`)?
- A 2D array (`Matrix`)?
- A 3D array (`tensor`)?
"""
# ╔═╡ 0ad91f1e-e8d2-11ea-2c18-93f66c906a8b
md"""## If in doubt: Ask Julia!
- Let's use the `Images.jl` package to load an image and see what we get
"""
# ╔═╡ de373816-ec79-11ea-2772-ebdca52246ac
begin
import Pkg
Pkg.activate(mktempdir())
end
# ╔═╡ 552129ae-ebca-11ea-1fa1-3f9fa00a2601
begin
Pkg.add(["Images", "ImageIO", "ImageMagick"])
using Images
end
# ╔═╡ fbe11200-e938-11ea-12e9-6125c1b56b25
begin
Pkg.add("PlutoUI")
using PlutoUI
end
# ╔═╡ 54c1ba3c-e8d2-11ea-3564-bdaca8563738
# defines a variable called `url`
# whose value is a string (written inside `"`):
url = "https://i.imgur.com/VGPeJ6s.jpg"
# ╔═╡ 6e0fefb6-e8d4-11ea-1f9b-e7a3db40df39
philip_file = download(url, "philip.jpg") # download to a local file
# ╔═╡ 9c359212-ec79-11ea-2d7e-0124dad5f127
philip = load(philip_file)
# ╔═╡ 7703b032-ebca-11ea-3074-0b80a077078e
philip
# ╔═╡ 7eff3522-ebca-11ea-1a65-59e66a4e72ab
typeof(philip)
# ╔═╡ c9cd6c04-ebca-11ea-0990-5fa19ff7ed97
RGBX(0.9, 0.1, 0.1)
# ╔═╡ 0d873d9c-e93b-11ea-2425-1bd79677fb97
md"##"
# ╔═╡ 6b09354a-ebb9-11ea-2d5a-3b75c5ae7aa9
# ╔═╡ 2d6c434e-e93b-11ea-2678-3b9db4975089
md"##"
# ╔═╡ 2b14e93e-e93b-11ea-25f1-5f565f80e778
typeof(philip)
# ╔═╡ 0bdc6058-e8d5-11ea-1889-3f706cea7a1f
md"""##
- According to Julia / Pluto, the variable `philip` *is* an image
- Julia always returns output
- The output can be displayed in a "rich" way
$(HTML("<br>"))
- Arthur C. Clarke:
> Any sufficiently advanced technology is indistinguishable from magic.
"""
# ╔═╡ e61db924-ebca-11ea-2f79-f9f1c121b7f5
size(philip)
# ╔═╡ ef60fcc4-ebca-11ea-3f69-155afffe8ea8
philip
# ╔═╡ fac550ec-ebca-11ea-337a-dbc16848c617
philip[1:1000, 1:400]
# ╔═╡ 42aa8cfe-e8d5-11ea-3cb9-c365b98e7a8c
md"
## How big is Philip?
- He's pretty big:
"
# ╔═╡ 4eea5710-e8d5-11ea-3978-af66ee2a137e
size(philip)
# ╔═╡ 57b3a0c2-e8d5-11ea-15aa-8da4549f849b
md"- Which number is which?"
# ╔═╡ 03a7c0fc-ebba-11ea-1c71-79d750c97b16
philip
# ╔═╡ e6fd68fa-e8d8-11ea-3dc4-274caceda222
md"# So, what *is* an image?"
# ╔═╡ 63a1d282-e8d5-11ea-0bba-b9cdd32a218b
typeof(philip)
# ╔═╡ fc5e1af0-e8d8-11ea-1077-07216ff96d29
md"""
- It's an `Array`
- The `2` means that it has **2 dimensions** (a **matrix**)
$(HTML("<br>"))
- `RGBX{Normed{UInt8,8}}` is the type of object stored in the array
- A Julia object representing a colour
- RGB = Red, Green, Blue
"""
# ╔═╡ c79dd836-e8e8-11ea-029d-57be9899979a
md"## Getting pieces of an image"
# ╔═╡ ae260168-e932-11ea-38fd-4f2c6f43e21c
begin
(h, w) = size(philip)
head = philip[(h ÷ 2):h, (w ÷ 10): (9w ÷ 10)]
# `÷` is typed as \div <TAB> -- integer division
end
# ╔═╡ 47d1bc04-ebcb-11ea-3643-d1ba8dea57c8
size(head)
# ╔═╡ 72400458-ebcb-11ea-26b6-678ae1de8e23
size(philip)
# ╔═╡ f57ea7c2-e932-11ea-0d52-4112187bcb38
md"## Manipulating matrices
- An image is just a matrix, so we can manipulate *matrices* to manipulate the *image*
"
# ╔═╡ 740ed2e2-e933-11ea-236c-f3c3f09d0f8b
[head head]
# ╔═╡ 6128a5ba-e93b-11ea-03f5-f170c7b90b25
md"##"
# ╔═╡ 78eafe4e-e933-11ea-3539-c13feb894ef6
[
head reverse(head, dims=2)
reverse(head, dims=1) reverse(reverse(head, dims=1), dims=2)
]
# ╔═╡ bf3f9050-e933-11ea-0df7-e5dcff6bb3ee
md"## Manipulating an image
- How can we get inside the image and change it?
- There are two possibilities:
- **Modify** (**mutate**) numbers inside the array -- useful to change a small piece
- Create a new **copy** of the array -- useful to alter everything together
"
# ╔═╡ 212e1f12-e934-11ea-2f35-51c7a6c8dff1
md"## Painting a piece of an image
- Let's paint a corner red
- We'll copy the image first so we don't destroy the original
"
# ╔═╡ 117a98c0-e936-11ea-3aac-8f66337cea68
new_phil = copy(head)
# ╔═╡ 8004d076-e93b-11ea-29cc-a1bfcc75e87f
md"##"
# ╔═╡ 3ac63296-e936-11ea-2144-f94bdbd60eaf
red = RGB(1, 0, 0)
# ╔═╡ 3e3f841a-e936-11ea-0a81-1b95fe0faa83
for i in 1:100
for j in 1:300
new_phil[i, j] = red
end
end
# ╔═╡ 5978db50-e936-11ea-3145-059a51be2281
md"Note that `for` loops *do not return anything* (or, rather, they return `nothing`)"
# ╔═╡ 21638b14-ebcc-11ea-1761-bbd2f4306a96
new_phil
# ╔═╡ 70cb0e36-e936-11ea-3ade-49fde77cb696
md"""## Element-wise operations: "Broadcasting"
- Julia provides powerful technology for operating element by element: **broadcasting**
- Adding "`.`" applies an operation element by element
"""
# ╔═╡ b3ea975e-e936-11ea-067d-81339575a3cb
begin
new_phil2 = copy(new_phil)
new_phil2[100:200, 1:100] .= RGB(0, 1, 0)
new_phil2
end
# ╔═╡ 918a0762-e93b-11ea-1115-71dbfdb03f27
md"##"
# ╔═╡ daabe66c-e937-11ea-3bc3-d77f2bce406c
new_phil2
# ╔═╡ 095ced62-e938-11ea-1169-939dc7136fd0
md"## Modifying the whole image at once
- We can use the same trick to modify the whole image at once
- Let's **redify** the image
- We define a **function** that turns a colour into just its red component
"
# ╔═╡ 31f3605a-e938-11ea-3a6d-29a185bbee31
function redify(c)
return RGB(c.r, 0, 0)
end
# ╔═╡ 2744a556-e94f-11ea-2434-d53c24e59285
begin
color = RGB(0.9, 0.7, 0.2)
[color, redify(color)]
end
# ╔═╡ 98412a36-e93b-11ea-1954-f1c105c6ed4a
md"##"
# ╔═╡ 3c32efde-e938-11ea-1ae4-5d88290f5311
redify.(philip)
# ╔═╡ 4b26e4e6-e938-11ea-2635-6d4fc15e13b7
md"## Transforming an image
- The main goal of this week will be to transfrom images in more interesting ways
- First let's **decimate** poor Phil
"
# ╔═╡ c12e0928-e93b-11ea-0922-2b590a99ee89
md"##"
# ╔═╡ ff5dc538-e938-11ea-058f-693d6b016640
md"## Experiments come alive with interaction
- We start to get a feel for things when we can **experiment**!
"
# ╔═╡ fa24f4a8-e93b-11ea-06bd-25c9672166d6
md"##"
# ╔═╡ 15ce202e-e939-11ea-2387-93be0ec4cf1f
@bind repeat_count Slider(1:10, show_value=true)
# ╔═╡ bf2167a4-e93d-11ea-03b2-cdd24b459ba9
md"## Summary
- Images are readily-accessible data about the world
- We want to process them to extract information
- Relatively simple mathematical operations can transform images in useful ways
"
# ╔═╡ 58184d88-e939-11ea-2fc8-73b3476ebe92
expand(image, ratio=5) = kron(image, ones(ratio, ratio))
# ╔═╡ 2dd09f16-e93a-11ea-2cdc-13f558e3391d
extract_red(c) = c.r
# ╔═╡ df1b7996-e93b-11ea-1a3a-81b4ec520679
decimate(image, ratio=5) = image[1:ratio:end, 1:ratio:end]
# ╔═╡ 41fa85c0-e939-11ea-1ad8-79805a2083bb
poor_phil = decimate(head, 5)
# ╔═╡ cd5721d0-ede6-11ea-0918-1992c69bccc6
repeat(poor_phil, repeat_count, repeat_count)
# ╔═╡ b8daeea0-ec79-11ea-34b5-3f13e8a56a42
md"# Appendix"
# ╔═╡ bf1bb2c8-ec79-11ea-0671-3ffb34828f3c
md"## Package environment"
# ╔═╡ 69e3aa82-e93c-11ea-23fe-c1103d989cba
md"## Camera input"
# ╔═╡ 739c3bb6-e93c-11ea-127b-efb6a8ab9379
function camera_input(;max_size=200, default_url="https://i.imgur.com/SUmi94P.png")
"""
<span class="pl-image waiting-for-permission">
<style>
.pl-image.popped-out {
position: fixed;
top: 0;
right: 0;
z-index: 5;
}
.pl-image #video-container {
width: 250px;
}
.pl-image video {
border-radius: 1rem 1rem 0 0;
}
.pl-image.waiting-for-permission #video-container {
display: none;
}
.pl-image #prompt {
display: none;
}
.pl-image.waiting-for-permission #prompt {
width: 250px;
height: 200px;
display: grid;
place-items: center;
font-family: monospace;
font-weight: bold;
text-decoration: underline;
cursor: pointer;
border: 5px dashed rgba(0,0,0,.5);
}
.pl-image video {
display: block;
}
.pl-image .bar {
width: inherit;
display: flex;
z-index: 6;
}
.pl-image .bar#top {
position: absolute;
flex-direction: column;
}
.pl-image .bar#bottom {
background: black;
border-radius: 0 0 1rem 1rem;
}
.pl-image .bar button {
flex: 0 0 auto;
background: rgba(255,255,255,.8);
border: none;
width: 2rem;
height: 2rem;
border-radius: 100%;
cursor: pointer;
z-index: 7;
}
.pl-image .bar button#shutter {
width: 3rem;
height: 3rem;
margin: -1.5rem auto .2rem auto;
}
.pl-image video.takepicture {
animation: pictureflash 200ms linear;
}
@keyframes pictureflash {
0% {
filter: grayscale(1.0) contrast(2.0);
}
100% {
filter: grayscale(0.0) contrast(1.0);
}
}
</style>
<div id="video-container">
<div id="top" class="bar">
<button id="stop" title="Stop video"></button>
<button id="pop-out" title="Pop out/pop in"></button>
</div>
<video playsinline autoplay></video>
<div id="bottom" class="bar">
<button id="shutter" title="Click to take a picture">📷</button>
</div>
</div>
<div id="prompt">
<span>
Enable webcam
</span>
</div>
<script>
// based on https://github.com/fonsp/printi-static (by the same author)
const span = this.currentScript.parentElement
const video = span.querySelector("video")
const popout = span.querySelector("button#pop-out")
const stop = span.querySelector("button#stop")
const shutter = span.querySelector("button#shutter")
const prompt = span.querySelector(".pl-image #prompt")
const maxsize = $(max_size)
const send_source = (source, src_width, src_height) => {
const scale = Math.min(1.0, maxsize / src_width, maxsize / src_height)
const width = Math.floor(src_width * scale)
const height = Math.floor(src_height * scale)
const canvas = html`<canvas width=\${width} height=\${height}>`
const ctx = canvas.getContext("2d")
ctx.drawImage(source, 0, 0, width, height)
span.value = {
width: width,
height: height,
data: ctx.getImageData(0, 0, width, height).data,
}
span.dispatchEvent(new CustomEvent("input"))
}
const clear_camera = () => {
window.stream.getTracks().forEach(s => s.stop());
video.srcObject = null;
span.classList.add("waiting-for-permission");
}
prompt.onclick = () => {
navigator.mediaDevices.getUserMedia({
audio: false,
video: {
facingMode: "environment",
},
}).then(function(stream) {
stream.onend = console.log
window.stream = stream
video.srcObject = stream
window.cameraConnected = true
video.controls = false
video.play()
video.controls = false
span.classList.remove("waiting-for-permission");
}).catch(function(error) {
console.log(error)
});
}
stop.onclick = () => {
clear_camera()
}
popout.onclick = () => {
span.classList.toggle("popped-out")
}
shutter.onclick = () => {
const cl = video.classList
cl.remove("takepicture")
void video.offsetHeight
cl.add("takepicture")
video.play()
video.controls = false
console.log(video)
send_source(video, video.videoWidth, video.videoHeight)
}
document.addEventListener("visibilitychange", () => {
if (document.visibilityState != "visible") {
clear_camera()
}
})
// Set a default image
const img = html`<img crossOrigin="anonymous">`
img.onload = () => {
console.log("helloo")
send_source(img, img.width, img.height)
}
img.src = "$(default_url)"
console.log(img)
</script>
</span>
""" |> HTML
end
# ╔═╡ 9529bc40-e93c-11ea-2587-3186e0978476
@bind raw_camera_data camera_input(;max_size=2000)
# ╔═╡ 832ebd1a-e93c-11ea-1d18-d784f3184ebe
function process_raw_camera_data(raw_camera_data)
# the raw image data is a long byte array, we need to transform it into something
# more "Julian" - something with more _structure_.
# The encoding of the raw byte stream is:
# every 4 bytes is a single pixel
# every pixel has 4 values: Red, Green, Blue, Alpha
# (we ignore alpha for this notebook)
# So to get the red values for each pixel, we take every 4th value, starting at
# the 1st:
reds_flat = UInt8.(raw_camera_data["data"][1:4:end])
greens_flat = UInt8.(raw_camera_data["data"][2:4:end])
blues_flat = UInt8.(raw_camera_data["data"][3:4:end])
# but these are still 1-dimensional arrays, nicknamed 'flat' arrays
# We will 'reshape' this into 2D arrays:
width = raw_camera_data["width"]
height = raw_camera_data["height"]
# shuffle and flip to get it in the right shape
reds = reshape(reds_flat, (width, height))' / 255.0
greens = reshape(greens_flat, (width, height))' / 255.0
blues = reshape(blues_flat, (width, height))' / 255.0
# we have our 2D array for each color
# Let's create a single 2D array, where each value contains the R, G and B value of
# that pixel
RGB.(reds, greens, blues)
end
# ╔═╡ 9a843af8-e93c-11ea-311b-1bc6d5b58492
grant = decimate(process_raw_camera_data(raw_camera_data), 2)
# ╔═╡ 6aa73286-ede7-11ea-232b-63e052222ecd
[
grant grant[:,end:-1:1]
grant[end:-1:1,:] grant[end:-1:1,end:-1:1]
]
# ╔═╡ Cell order:
# ╟─a50b5f48-e8d5-11ea-1f05-a3741b5d15ba
# ╟─8a6fed4c-e94b-11ea-1113-d56f56fb293b
# ╟─dc53f316-e8c8-11ea-150f-1374dbce114a
# ╟─c3f43d66-e94b-11ea-02bd-23cfeb878ff1
# ╟─c6c77738-e94b-11ea-22f5-1dce3dbcc3ca
# ╟─cf80793a-e94b-11ea-0120-f7913ae06f22
# ╟─d1638d96-e94b-11ea-2ff4-910e399f864d
# ╟─0117246a-e94c-11ea-1a76-c981ce8e725d
# ╟─27060098-e8c9-11ea-2fe0-03b39b1ddc32
# ╟─4fc58814-e94b-11ea-339b-cb714a63f9b6
# ╟─f067d3b8-e8c8-11ea-20cb-474709ffa99a
# ╠═37c1d012-ebc9-11ea-2dfe-8b86bb78f283
# ╟─a0a97214-e8d2-11ea-0f46-0bfaf016ab6d
# ╟─1697a756-e93d-11ea-0b6e-c9c78d527993
# ╟─af28faca-ebb7-11ea-130d-0f94bf9bd836
# ╠═9529bc40-e93c-11ea-2587-3186e0978476
# ╟─ee1d1596-e94a-11ea-0fb4-cd05f62471d3
# ╠═6aa73286-ede7-11ea-232b-63e052222ecd
# ╠═9a843af8-e93c-11ea-311b-1bc6d5b58492
# ╟─8ab9a978-e8c9-11ea-2476-f1ef4ba1b619
# ╟─38c54bfc-e8cb-11ea-3d52-0f02452f8ba1
# ╟─983f8270-e8c9-11ea-29d2-adeccb5a7ffc
# ╟─2fcaef88-e8ca-11ea-23f7-29c48580f43c
# ╟─7636c4b0-e8d1-11ea-2051-757a850a9d30
# ╟─bca22176-e8ca-11ea-2004-ebeb103116b5
# ╟─0ad91f1e-e8d2-11ea-2c18-93f66c906a8b
# ╠═de373816-ec79-11ea-2772-ebdca52246ac
# ╠═552129ae-ebca-11ea-1fa1-3f9fa00a2601
# ╠═54c1ba3c-e8d2-11ea-3564-bdaca8563738
# ╠═6e0fefb6-e8d4-11ea-1f9b-e7a3db40df39
# ╠═9c359212-ec79-11ea-2d7e-0124dad5f127
# ╠═7703b032-ebca-11ea-3074-0b80a077078e
# ╠═7eff3522-ebca-11ea-1a65-59e66a4e72ab
# ╠═c9cd6c04-ebca-11ea-0990-5fa19ff7ed97
# ╟─0d873d9c-e93b-11ea-2425-1bd79677fb97
# ╠═6b09354a-ebb9-11ea-2d5a-3b75c5ae7aa9
# ╟─2d6c434e-e93b-11ea-2678-3b9db4975089
# ╠═2b14e93e-e93b-11ea-25f1-5f565f80e778
# ╟─0bdc6058-e8d5-11ea-1889-3f706cea7a1f
# ╠═e61db924-ebca-11ea-2f79-f9f1c121b7f5
# ╠═ef60fcc4-ebca-11ea-3f69-155afffe8ea8
# ╠═fac550ec-ebca-11ea-337a-dbc16848c617
# ╟─42aa8cfe-e8d5-11ea-3cb9-c365b98e7a8c
# ╠═4eea5710-e8d5-11ea-3978-af66ee2a137e
# ╟─57b3a0c2-e8d5-11ea-15aa-8da4549f849b
# ╠═03a7c0fc-ebba-11ea-1c71-79d750c97b16
# ╟─e6fd68fa-e8d8-11ea-3dc4-274caceda222
# ╠═63a1d282-e8d5-11ea-0bba-b9cdd32a218b
# ╟─fc5e1af0-e8d8-11ea-1077-07216ff96d29
# ╟─c79dd836-e8e8-11ea-029d-57be9899979a
# ╠═ae260168-e932-11ea-38fd-4f2c6f43e21c
# ╠═47d1bc04-ebcb-11ea-3643-d1ba8dea57c8
# ╠═72400458-ebcb-11ea-26b6-678ae1de8e23
# ╟─f57ea7c2-e932-11ea-0d52-4112187bcb38
# ╠═740ed2e2-e933-11ea-236c-f3c3f09d0f8b
# ╟─6128a5ba-e93b-11ea-03f5-f170c7b90b25
# ╠═78eafe4e-e933-11ea-3539-c13feb894ef6
# ╟─bf3f9050-e933-11ea-0df7-e5dcff6bb3ee
# ╟─212e1f12-e934-11ea-2f35-51c7a6c8dff1
# ╠═117a98c0-e936-11ea-3aac-8f66337cea68
# ╟─8004d076-e93b-11ea-29cc-a1bfcc75e87f
# ╠═3ac63296-e936-11ea-2144-f94bdbd60eaf
# ╠═3e3f841a-e936-11ea-0a81-1b95fe0faa83
# ╟─5978db50-e936-11ea-3145-059a51be2281
# ╠═21638b14-ebcc-11ea-1761-bbd2f4306a96
# ╟─70cb0e36-e936-11ea-3ade-49fde77cb696
# ╠═b3ea975e-e936-11ea-067d-81339575a3cb
# ╟─918a0762-e93b-11ea-1115-71dbfdb03f27
# ╠═daabe66c-e937-11ea-3bc3-d77f2bce406c
# ╟─095ced62-e938-11ea-1169-939dc7136fd0
# ╠═31f3605a-e938-11ea-3a6d-29a185bbee31
# ╠═2744a556-e94f-11ea-2434-d53c24e59285
# ╟─98412a36-e93b-11ea-1954-f1c105c6ed4a
# ╠═3c32efde-e938-11ea-1ae4-5d88290f5311
# ╟─4b26e4e6-e938-11ea-2635-6d4fc15e13b7
# ╠═41fa85c0-e939-11ea-1ad8-79805a2083bb
# ╟─c12e0928-e93b-11ea-0922-2b590a99ee89
# ╟─ff5dc538-e938-11ea-058f-693d6b016640
# ╠═fbe11200-e938-11ea-12e9-6125c1b56b25
# ╟─fa24f4a8-e93b-11ea-06bd-25c9672166d6
# ╠═15ce202e-e939-11ea-2387-93be0ec4cf1f
# ╠═cd5721d0-ede6-11ea-0918-1992c69bccc6
# ╟─bf2167a4-e93d-11ea-03b2-cdd24b459ba9
# ╟─5e688928-e939-11ea-0e16-fbc80af390ab
# ╟─58184d88-e939-11ea-2fc8-73b3476ebe92
# ╟─2dd09f16-e93a-11ea-2cdc-13f558e3391d
# ╟─df1b7996-e93b-11ea-1a3a-81b4ec520679
# ╟─b8daeea0-ec79-11ea-34b5-3f13e8a56a42
# ╟─bf1bb2c8-ec79-11ea-0671-3ffb34828f3c
# ╟─69e3aa82-e93c-11ea-23fe-c1103d989cba
# ╟─739c3bb6-e93c-11ea-127b-efb6a8ab9379
# ╟─832ebd1a-e93c-11ea-1d18-d784f3184ebe

@ -0,0 +1,256 @@
### A Pluto.jl notebook ###
# v0.12.11
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
# ╔═╡ 15a4ba3e-f0d1-11ea-2ef1-5ff1dee8795f
using Pkg
# ╔═╡ 21e744b8-f0d1-11ea-2e09-7ffbcdf43c37
begin
Pkg.add("Gadfly")
Pkg.add("Compose")
Pkg.add("Statistics")
Pkg.add("Hyperscript")
Pkg.add("Colors")
Pkg.add("Images")
Pkg.add("ImageMagick")
Pkg.add("ImageFiltering")
using Gadfly
using Images
using Compose
using Hyperscript
using Colors
using Statistics
using PlutoUI
using ImageMagick
using ImageFiltering
end
# ╔═╡ 1ab1c808-f0d1-11ea-03a7-e9854427d45f
Pkg.activate(mktempdir())
# ╔═╡ 10f850fc-f0d1-11ea-2a58-2326a9ea1e2a
set_default_plot_size(12cm, 12cm)
# ╔═╡ 7b4d5270-f0d3-11ea-0b48-79005f20602c
function convolve(M, kernel)
height, width = size(kernel)
half_height = height ÷ 2
half_width = width ÷ 2
new_image = similar(M)
# (i, j) loop over the original image
m, n = size(M)
@inbounds for i in 1:m
for j in 1:n
# (k, l) loop over the neighbouring pixels
accumulator = 0 * M[1, 1]
for k in -half_height:-half_height + height - 1
for l in -half_width:-half_width + width - 1
Mi = i - k
Mj = j - l
# First index into M
if Mi < 1
Mi = 1
elseif Mi > m
Mi = m
end
# Second index into M
if Mj < 1
Mj = 1
elseif Mj > n
Mj = n
end
accumulator += kernel[k, l] * M[Mi, Mj]
end
end
new_image[i, j] = accumulator
end
end
return new_image
end
# ╔═╡ 6fd3b7a4-f0d3-11ea-1f26-fb9740cd16e0
function disc(n, r1=0.8, r2=0.8)
white = RGB{Float64}(1,1,1)
blue = RGB{Float64}(colorant"#4EC0E3")
convolve(
[(i-n/2)^2 + (j-n/2)^2 <= (n/2-5)^2 ? white : blue for i=1:n, j=1:n],
Kernel.gaussian((1,1))
)
end
# ╔═╡ fe3559e0-f13b-11ea-06c8-a314e44c20d6
brightness(c) = 0.3 * c.r + 0.59 * c.g + 0.11 * c.b
# ╔═╡ 0ccf76e4-f0d9-11ea-07c9-0159e3d4d733
@bind img_select Radio(["disc", "mario"], default="disc")
# ╔═╡ 236dab08-f13d-11ea-1922-a3b82cfc7f51
begin
url = "http://files.softicons.com/download/game-icons/super-mario-icons-by-sandro-pereira/png/32/Retro%20Mario.png"
img = Dict(
"disc" => disc(25),
"mario" => load(download(url))
)[img_select]
end
# ╔═╡ 03434682-f13b-11ea-2b6e-11ad781e9a51
md"""Show $G_x$ $(@bind Gx CheckBox())
Show $G_y$ $(@bind Gy CheckBox())"""
# ╔═╡ ca13597a-f168-11ea-1a2c-ff7b98b7b2c7
function partial_derivatives(img)
Sy,Sx = Kernel.sobel()
∇x, ∇y = zeros(size(img)), zeros(size(img))
if Gx
∇x = convolve(brightness.(img), Sx)
end
if Gy
∇y = convolve(brightness.(img), Sy)
end
return ∇x, ∇y
end
# ╔═╡ b369584c-f183-11ea-260a-35dc797e63ad
# ╔═╡ b2cbe058-f183-11ea-39dc-23d4a5b92796
# ╔═╡ 9d9cccb2-f118-11ea-1638-c76682e636b2
function arrowhead(θ)
eq_triangle = [(0, 1/sqrt(3)),
(-1/3, -2/(2 * sqrt(3))),
(1/3, -2/(2 * sqrt(3)))]
compose(context(units=UnitBox(-1,-1,2,2), rotation=Rotation(θ, 0, 0)),
polygon(eq_triangle))
end
# ╔═╡ b7ea8a28-f0d7-11ea-3e98-7b19a1f58304
function quiver(points, vecs)
xmin = minimum(first.(points))
ymin = minimum(last.(points))
xmax = maximum(first.(points))
ymax = maximum(last.(points))
hs = map(x->hypot(x...), vecs)
hs = hs / maximum(hs)
vector(p, v, h) = all(iszero, v) ? context() :
(context(),
(context((p.+v.*6 .- .2)..., .4,.4),
arrowhead(atan(v[2], v[1]) - pi/2)),
stroke(RGBA(90/255,39/255,41/255,h)),
fill(RGBA(90/255,39/255,41/255,h)),
line([p, p.+v.*8]))
compose(context(units=UnitBox(xmin,ymin,xmax,ymax)),
vector.(points, vecs, hs)...)
end
# ╔═╡ c821b906-f0d8-11ea-2df0-8f2d06964aa2
function sobel_quiver(img, ∇x, ∇y)
quiver([(j-1,i-1) for i=1:size(img,1), j=1:size(img,2)],
[(∇x[i,j], ∇y[i,j]) for i=1:size(img,1), j=1:size(img,2)])
end
# ╔═╡ 6da3fdfe-f0dd-11ea-2407-7b85217b35cc
# render an Image using squares in Compose
function compimg(img)
xmax, ymax = size(img)
xmin, ymin = 0, 0
arr = [(j-1, i-1) for i=1:ymax, j=1:xmax]
compose(context(units=UnitBox(xmin, ymin, xmax, ymax)),
fill(vec(img)),
rectangle(
first.(arr),
last.(arr),
fill(1.0, length(arr)),
fill(1.0, length(arr))))
end
# ╔═╡ f22aa34e-f0df-11ea-3053-3dcdc070ec2f
let
∇x, ∇y = partial_derivatives(img)
compose(context(),
sobel_quiver(img, ∇x, ∇y),
compimg(img))
end
# ╔═╡ 885ec336-f146-11ea-00c4-c1d1ab4c0001
function show_colored_array(array)
pos_color = RGB(0.36, 0.82, 0.8)
neg_color = RGB(0.99, 0.18, 0.13)
to_rgb(x) = max(x, 0) * pos_color + max(-x, 0) * neg_color
to_rgb.(array) / maximum(abs.(array))
end
# ╔═╡ 9232dcc8-f188-11ea-08fe-b787ea93c598
begin
Sy, Sx = Kernel.sobel()
show_colored_array(Sx)
Sx
end
# ╔═╡ 7864bd00-f146-11ea-0020-7fccb3913d8b
let
∇x, ∇y = partial_derivatives(img)
to_show = (x -> RGB(0, 0, 0)).(zeros(size(img)))
if Gx && Gy
edged = sqrt.(∇x.^2 + ∇y.^2)
to_show = Gray.(edged) / maximum(edged)
elseif Gx
to_show = show_colored_array(∇x)
elseif Gy
to_show = show_colored_array(∇y)
end
compose(
context(),
compimg(to_show)
)
end
# ╔═╡ Cell order:
# ╠═15a4ba3e-f0d1-11ea-2ef1-5ff1dee8795f
# ╠═1ab1c808-f0d1-11ea-03a7-e9854427d45f
# ╠═21e744b8-f0d1-11ea-2e09-7ffbcdf43c37
# ╠═10f850fc-f0d1-11ea-2a58-2326a9ea1e2a
# ╟─7b4d5270-f0d3-11ea-0b48-79005f20602c
# ╠═6fd3b7a4-f0d3-11ea-1f26-fb9740cd16e0
# ╠═fe3559e0-f13b-11ea-06c8-a314e44c20d6
# ╟─b7ea8a28-f0d7-11ea-3e98-7b19a1f58304
# ╟─0ccf76e4-f0d9-11ea-07c9-0159e3d4d733
# ╟─236dab08-f13d-11ea-1922-a3b82cfc7f51
# ╟─03434682-f13b-11ea-2b6e-11ad781e9a51
# ╠═ca13597a-f168-11ea-1a2c-ff7b98b7b2c7
# ╟─f22aa34e-f0df-11ea-3053-3dcdc070ec2f
# ╟─9232dcc8-f188-11ea-08fe-b787ea93c598
# ╠═7864bd00-f146-11ea-0020-7fccb3913d8b
# ╠═b369584c-f183-11ea-260a-35dc797e63ad
# ╠═b2cbe058-f183-11ea-39dc-23d4a5b92796
# ╟─9d9cccb2-f118-11ea-1638-c76682e636b2
# ╟─c821b906-f0d8-11ea-2df0-8f2d06964aa2
# ╟─6da3fdfe-f0dd-11ea-2407-7b85217b35cc
# ╠═885ec336-f146-11ea-00c4-c1d1ab4c0001

@ -0,0 +1,256 @@
### A Pluto.jl notebook ###
# v0.11.10
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
# ╔═╡ 15a4ba3e-f0d1-11ea-2ef1-5ff1dee8795f
using Pkg
# ╔═╡ 21e744b8-f0d1-11ea-2e09-7ffbcdf43c37
begin
Pkg.add("Gadfly")
Pkg.add("Compose")
Pkg.add("Statistics")
Pkg.add("Hyperscript")
Pkg.add("Colors")
Pkg.add("Images")
Pkg.add("ImageMagick")
Pkg.add("ImageFiltering")
using Gadfly
using Images
using Compose
using Hyperscript
using Colors
using Statistics
using PlutoUI
using ImageMagick
using ImageFiltering
end
# ╔═╡ 1ab1c808-f0d1-11ea-03a7-e9854427d45f
Pkg.activate(mktempdir())
# ╔═╡ 10f850fc-f0d1-11ea-2a58-2326a9ea1e2a
set_default_plot_size(12cm, 12cm)
# ╔═╡ 7b4d5270-f0d3-11ea-0b48-79005f20602c
function convolve(M, kernel)
height, width = size(kernel)
half_height = height ÷ 2
half_width = width ÷ 2
new_image = similar(M)
# (i, j) loop over the original image
m, n = size(M)
@inbounds for i in 1:m
for j in 1:n
# (k, l) loop over the neighbouring pixels
accumulator = 0 * M[1, 1]
for k in -half_height:-half_height + height - 1
for l in -half_width:-half_width + width - 1
Mi = i - k
Mj = j - l
# First index into M
if Mi < 1
Mi = 1
elseif Mi > m
Mi = m
end
# Second index into M
if Mj < 1
Mj = 1
elseif Mj > n
Mj = n
end
accumulator += kernel[k, l] * M[Mi, Mj]
end
end
new_image[i, j] = accumulator
end
end
return new_image
end
# ╔═╡ 6fd3b7a4-f0d3-11ea-1f26-fb9740cd16e0
function disc(n, r1=0.8, r2=0.8)
white = RGB{Float64}(1,1,1)
blue = RGB{Float64}(colorant"#4EC0E3")
convolve(
[(i-n/2)^2 + (j-n/2)^2 <= (n/2-5)^2 ? white : blue for i=1:n, j=1:n],
Kernel.gaussian((1,1))
)
end
# ╔═╡ fe3559e0-f13b-11ea-06c8-a314e44c20d6
brightness(c) = 0.3 * c.r + 0.59 * c.g + 0.11 * c.b
# ╔═╡ 0ccf76e4-f0d9-11ea-07c9-0159e3d4d733
@bind img_select Radio(["disc", "mario"], default="disc")
# ╔═╡ 236dab08-f13d-11ea-1922-a3b82cfc7f51
begin
url = "http://files.softicons.com/download/game-icons/super-mario-icons-by-sandro-pereira/png/32/Retro%20Mario.png"
img = Dict(
"disc" => disc(25),
"mario" => load(download(url))
)[img_select]
end
# ╔═╡ 03434682-f13b-11ea-2b6e-11ad781e9a51
md"""Show $G_x$ $(@bind Gx CheckBox())
Show $G_y$ $(@bind Gy CheckBox())"""
# ╔═╡ ca13597a-f168-11ea-1a2c-ff7b98b7b2c7
function partial_derivatives(img)
Sy,Sx = Kernel.sobel()
∇x, ∇y = zeros(size(img)), zeros(size(img))
if Gx
∇x = convolve(brightness.(img), Sx)
end
if Gy
∇y = convolve(brightness.(img), Sy)
end
return ∇x, ∇y
end
# ╔═╡ b369584c-f183-11ea-260a-35dc797e63ad
# ╔═╡ b2cbe058-f183-11ea-39dc-23d4a5b92796
# ╔═╡ 9d9cccb2-f118-11ea-1638-c76682e636b2
function arrowhead(θ)
eq_triangle = [(0, 1/sqrt(3)),
(-1/3, -2/(2 * sqrt(3))),
(1/3, -2/(2 * sqrt(3)))]
compose(context(units=UnitBox(-1,-1,2,2), rotation=Rotation(θ, 0, 0)),
polygon(eq_triangle))
end
# ╔═╡ b7ea8a28-f0d7-11ea-3e98-7b19a1f58304
function quiver(points, vecs)
xmin = minimum(first.(points))
ymin = minimum(last.(points))
xmax = maximum(first.(points))
ymax = maximum(last.(points))
hs = map(x->hypot(x...), vecs)
hs = hs / maximum(hs)
vector(p, v, h) = all(iszero, v) ? context() :
(context(),
(context((p.+v.*6 .- .2)..., .4,.4),
arrowhead(atan(v[2], v[1]) - pi/2)),
stroke(RGBA(90/255,39/255,41/255,h)),
fill(RGBA(90/255,39/255,41/255,h)),
line([p, p.+v.*8]))
compose(context(units=UnitBox(xmin,ymin,xmax,ymax)),
vector.(points, vecs, hs)...)
end
# ╔═╡ c821b906-f0d8-11ea-2df0-8f2d06964aa2
function sobel_quiver(img, ∇x, ∇y)
quiver([(j-1,i-1) for i=1:size(img,1), j=1:size(img,2)],
[(∇x[i,j], ∇y[i,j]) for i=1:size(img,1), j=1:size(img,2)])
end
# ╔═╡ 6da3fdfe-f0dd-11ea-2407-7b85217b35cc
# render an Image using squares in Compose
function compimg(img)
xmax, ymax = size(img)
xmin, ymin = 0, 0
arr = [(j-1, i-1) for i=1:ymax, j=1:xmax]
compose(context(units=UnitBox(xmin, ymin, xmax, ymax)),
fill(vec(img)),
rectangle(
first.(arr),
last.(arr),
fill(1.0, length(arr)),
fill(1.0, length(arr))))
end
# ╔═╡ f22aa34e-f0df-11ea-3053-3dcdc070ec2f
let
∇x, ∇y = partial_derivatives(img)
compose(context(),
sobel_quiver(img, ∇x, ∇y),
compimg(img))
end
# ╔═╡ 885ec336-f146-11ea-00c4-c1d1ab4c0001
function show_colored_array(array)
pos_color = RGB(0.36, 0.82, 0.8)
neg_color = RGB(0.99, 0.18, 0.13)
to_rgb(x) = max(x, 0) * pos_color + max(-x, 0) * neg_color
to_rgb.(array) / maximum(abs.(array))
end
# ╔═╡ 9232dcc8-f188-11ea-08fe-b787ea93c598
begin
Sy, Sx = Kernel.sobel()
show_colored_array(Sx)
Sx
end
# ╔═╡ 7864bd00-f146-11ea-0020-7fccb3913d8b
let
∇x, ∇y = partial_derivatives(img)
to_show = (x -> RGB(0, 0, 0)).(zeros(size(img)))
if Gx && Gy
edged = sqrt.(∇x.^2 + ∇y.^2)
to_show = Gray.(edged) / maximum(edged)
elseif Gx
to_show = show_colored_array(∇x)
elseif Gy
to_show = show_colored_array(∇y)
end
compose(
context(),
compimg(to_show)
)
end
# ╔═╡ Cell order:
# ╠═15a4ba3e-f0d1-11ea-2ef1-5ff1dee8795f
# ╠═1ab1c808-f0d1-11ea-03a7-e9854427d45f
# ╟─21e744b8-f0d1-11ea-2e09-7ffbcdf43c37
# ╠═10f850fc-f0d1-11ea-2a58-2326a9ea1e2a
# ╟─7b4d5270-f0d3-11ea-0b48-79005f20602c
# ╠═6fd3b7a4-f0d3-11ea-1f26-fb9740cd16e0
# ╟─fe3559e0-f13b-11ea-06c8-a314e44c20d6
# ╟─b7ea8a28-f0d7-11ea-3e98-7b19a1f58304
# ╟─0ccf76e4-f0d9-11ea-07c9-0159e3d4d733
# ╟─236dab08-f13d-11ea-1922-a3b82cfc7f51
# ╟─03434682-f13b-11ea-2b6e-11ad781e9a51
# ╟─ca13597a-f168-11ea-1a2c-ff7b98b7b2c7
# ╟─f22aa34e-f0df-11ea-3053-3dcdc070ec2f
# ╟─9232dcc8-f188-11ea-08fe-b787ea93c598
# ╠═7864bd00-f146-11ea-0020-7fccb3913d8b
# ╠═b369584c-f183-11ea-260a-35dc797e63ad
# ╠═b2cbe058-f183-11ea-39dc-23d4a5b92796
# ╟─9d9cccb2-f118-11ea-1638-c76682e636b2
# ╟─c821b906-f0d8-11ea-2df0-8f2d06964aa2
# ╟─6da3fdfe-f0dd-11ea-2407-7b85217b35cc
# ╠═885ec336-f146-11ea-00c4-c1d1ab4c0001

359
hw0.jl

@ -0,0 +1,359 @@
### A Pluto.jl notebook ###
# v0.12.11
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
# ╔═╡ fafae38e-e852-11ea-1208-732b4744e4c2
md"_homework 0, version 2_"
# ╔═╡ 7308bc54-e6cd-11ea-0eab-83f7535edf25
# edit the code below to set your name and kerberos ID (i.e. email without @mit.edu)
student = (name = "Jazzy Doe", kerberos_id = "jazz")
# press the ▶ button in the bottom right of this cell to run your edits
# or use Shift+Enter
# you might need to wait until all other cells in this notebook have completed running.
# scroll down the page to see what's up
# ╔═╡ cdff6730-e785-11ea-2546-4969521b33a7
md"""
Submission by: **_$(student.name)_** ($(student.kerberos_id)@mit.edu)
"""
# ╔═╡ a2181260-e6cd-11ea-2a69-8d9d31d1ef0e
md"""
# Homework 0: Getting up and running
First of all, **_welcome to the course!_** We are excited to teach you about real world applications of scientific computing, using the same tools that we work with ourselves.
Before we start next week, we'd like everyone to **submit this zeroth homework assignment**. It will not affect your grade, but it will help us get everything running smoothly when the course starts. If you're stuck or don't have much time, just fill in your name and ID and submit 🙂
"""
# ╔═╡ 094e39c8-e6ce-11ea-131b-07c4a1199edf
# ╔═╡ 31a8fbf8-e6ce-11ea-2c66-4b4d02b41995
# ╔═╡ 339c2d5c-e6ce-11ea-32f9-714b3628909c
md"## Exercise 1 - _Square root by Newton's method_
Computing the square of a number is easy -- you just multiply it with itself.
But how does one compute the square root of a number?
##### Algorithm:
Given: $x$
Output: $\sqrt{x}$
1. Take a guess `a`
1. Divide `x` by `a`
1. Set a = the average of `x/a` and `a`. (The square root must be between these two numbers. Why?)
1. Repeat until `x/a` is roughly equal to `a`. Return `a` as the square root.
In general, you will never get to the point where `x/a` is _exactly_ equal to `a`. So if our algorithm keeps going until `x/a == a`, then it will get stuck.
So instead, the algorithm takes a parameter `error_margin`, which is used to decide when `x/a` and `a` are close enough to halt.
"
# ╔═╡ 56866718-e6ce-11ea-0804-d108af4e5653
md"### Exercise 1.1
Step 3 in the algorithm sets the new guess to be the average of `x/a` and the old guess `a`.
This is because the square root must be between the numbers `x/a` and `a`. Why?
"
# ╔═╡ bccf0e88-e754-11ea-3ab8-0170c2d44628
ex_1_1 = md"""
your answer here
"""
# you might need to wait until all other cells in this notebook have completed running.
# scroll down the page to see what's up
# ╔═╡ e7abd366-e7a6-11ea-30d7-1b6194614d0a
if !(@isdefined ex_1_1)
md"""Do not change the name of the variable - write you answer as `ex_1_1 = "..."`"""
end
# ╔═╡ d62f223c-e754-11ea-2470-e72a605a9d7e
md"### Exercise 1.2
Write a function newton_sqrt(x) which implements the above algorithm."
# ╔═╡ 4896bf0c-e754-11ea-19dc-1380bb356ab6
function newton_sqrt(x, error_margin=0.01, a=x / 2) # a=x/2 is the default value of `a`
while true
xa = x / a
if (abs(a - xa) < error_margin)
return a
end
a = (xa + a) / 2
end
end
# ╔═╡ 7a01a508-e78a-11ea-11da-999d38785348
newton_sqrt(2)
# ╔═╡ 682db9f8-e7b1-11ea-3949-6b683ca8b47b
let
result = newton_sqrt(2, 0.01)
if !(result isa Number)
md"""
!!! warning "Not a number"
`newton_sqrt` did not return a number. Did you forget to write `return`?
"""
elseif abs(result - sqrt(2)) < 0.01
md"""
!!! correct
Well done!
"""
else
md"""
!!! warning "Incorrect"
Keep working on it!
"""
end
end
# ╔═╡ 088cc652-e7a8-11ea-0ca7-f744f6f3afdd
md"""
!!! hint
`abs(r - s)` is the distance between `r` and `s`
"""
# ╔═╡ c18dce7a-e7a7-11ea-0a1a-f944d46754e5
md"""
!!! hint
If you're stuck, feel free to cheat, this is homework 0 after all 🙃
Julia has a function called `sqrt`
"""
# ╔═╡ 5e24d95c-e6ce-11ea-24be-bb19e1e14657
md"## Exercise 2 - _Sierpinksi's triangle_
Sierpinski's triangle is defined _recursively_:
- Sierpinski's triangle of complexity N is a figure in the form of a triangle which is made of 3 triangular figures which are themselves Sierpinski's triangles of complexity N-1.
- A Sierpinski's triangle of complexity 0 is a simple solid equilateral triangle
"
# ╔═╡ 6b8883f6-e7b3-11ea-155e-6f62117e123b
md"To draw Sierpinski's triangle, we are going to use an external package, [_Compose.jl_](https://giovineitalia.github.io/Compose.jl/latest/tutorial). Let's set up a package environment and add the package.
A package contains a coherent set of functionality that you can often use a black box according to its specification. There are [lots of Julia packages](https://juliahub.com/ui/Home).
"
# ╔═╡ 851c03a4-e7a4-11ea-1652-d59b7a6599f0
# setting up an empty package environment
begin
import Pkg
Pkg.activate(mktempdir())
Pkg.Registry.update()
end
# ╔═╡ d6ee91ea-e750-11ea-1260-31ebf3ec6a9b
# add (ie install) a package to our environment
begin
Pkg.add("Compose")
# call `using` so that we can use it in our code
using Compose
end
# ╔═╡ 5acd58e0-e856-11ea-2d3d-8329889fe16f
begin
Pkg.add("PlutoUI")
using PlutoUI
end
# ╔═╡ dbc4da6a-e7b4-11ea-3b70-6f2abfcab992
md"Just like the definition above, our `sierpinksi` function is _recursive_: it calls itself."
# ╔═╡ 02b9c9d6-e752-11ea-0f32-91b7b6481684
complexity = 5
# ╔═╡ 1eb79812-e7b5-11ea-1c10-63b24803dd8a
if complexity == 3
md"""
Try changing the value of **`complexity` to `5`** in the cell above.
Hit `Shift+Enter` to affect the change.
"""
else
md"""
**Great!** As you can see, all the cells in this notebook are linked together by the variables they define and use. Just like a spreadsheet!
"""
end
# ╔═╡ d7e8202c-e7b5-11ea-30d3-adcd6867d5f5
md"### Exercise 2.1
As you can see, the total area covered by triangles is lower when the complexity is higher."
# ╔═╡ f22222b4-e7b5-11ea-0ea0-8fa368d2a014
md"""
Can you write a function that computes the _area of `sierpinski(n)`_, as a fraction of the area of `sierpinski(0)`?
So:
```
area_sierpinski(0) = 1.0
area_sierpinski(1) = 0.??
...
```
"""
# ╔═╡ ca8d2f72-e7b6-11ea-1893-f1e6d0a20dc7
function area_sierpinski(n)
if (n == 0)
return 1.0
end
return 0.75 * area_sierpinski(n-1)
end
# ╔═╡ 71c78614-e7bc-11ea-0959-c7a91a10d481
if area_sierpinski(0) == 1.0 && area_sierpinski(1) == 3 / 4
md"""
!!! correct
Well done!
"""
else
md"""
!!! warning "Incorrect"
Keep working on it!
"""
end
# ╔═╡ c21096c0-e856-11ea-3dc5-a5b0cbf29335
md"**Let's try it out below:**"
# ╔═╡ 52533e00-e856-11ea-08a7-25e556fb1127
md"Complexity = $(@bind n Slider(0:6, show_value=true))"
# ╔═╡ c1ecad86-e7bc-11ea-1201-23ee380181a1
md"""
!!! hint
Can you write `area_sierpinksi(n)` as a function of `area_sierpinski(n-1)`?
"""
# ╔═╡ c9bf4288-e6ce-11ea-0e13-a36b5e685998
# ╔═╡ a60a492a-e7bc-11ea-0f0b-75d81ce46a01
md"That's it for now, see you next week!"
# ╔═╡ b3c7a050-e855-11ea-3a22-3f514da746a4
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
# ╔═╡ d3625d20-e6ce-11ea-394a-53208540d626
# ╔═╡ dfdeab34-e751-11ea-0f90-2fa9bbdccb1e
triangle() = compose(context(), polygon([(1, 1), (0, 1), (1 / 2, 0)]))
# ╔═╡ b923d394-e750-11ea-1971-595e09ab35b5
# It does not matter which order you define the building blocks (functions) of the
# program in. The best way to organize code is the one that promotes understanding.
function place_in_3_corners(t)
# Uses the Compose library to place 3 copies of t
# in the 3 corners of a triangle.
# treat this function as a black box,
# or learn how it works from the Compose documentation here https://giovineitalia.github.io/Compose.jl/latest/tutorial/#Compose-is-declarative-1
compose(context(),
(context(1 / 4, 0, 1 / 2, 1 / 2), t),
(context(0, 1 / 2, 1 / 2, 1 / 2), t),
(context(1 / 2, 1 / 2, 1 / 2, 1 / 2), t))
end
# ╔═╡ e2848b9a-e703-11ea-24f9-b9131434a84b
function sierpinski(n)
if n == 0
triangle()
else
t = sierpinski(n - 1) # recursively construct a smaller sierpinski's triangle
place_in_3_corners(t) # place it in the 3 corners of a triangle
end
end
# ╔═╡ 9664ac52-e750-11ea-171c-e7d57741a68c
sierpinski(complexity)
# ╔═╡ df0a4068-e7b2-11ea-2475-81b237d492b3
sierpinski.(0:6)
# ╔═╡ 147ed7b0-e856-11ea-0d0e-7ff0d527e352
md"""
Sierpinski's triangle of complexity $(n)
$(sierpinski(n))
has area **$(area_sierpinski(n))**
"""
# ╔═╡ Cell order:
# ╟─fafae38e-e852-11ea-1208-732b4744e4c2
# ╟─cdff6730-e785-11ea-2546-4969521b33a7
# ╠═7308bc54-e6cd-11ea-0eab-83f7535edf25
# ╟─a2181260-e6cd-11ea-2a69-8d9d31d1ef0e
# ╟─094e39c8-e6ce-11ea-131b-07c4a1199edf
# ╟─31a8fbf8-e6ce-11ea-2c66-4b4d02b41995
# ╟─339c2d5c-e6ce-11ea-32f9-714b3628909c
# ╟─56866718-e6ce-11ea-0804-d108af4e5653
# ╠═bccf0e88-e754-11ea-3ab8-0170c2d44628
# ╟─e7abd366-e7a6-11ea-30d7-1b6194614d0a
# ╟─d62f223c-e754-11ea-2470-e72a605a9d7e
# ╠═4896bf0c-e754-11ea-19dc-1380bb356ab6
# ╠═7a01a508-e78a-11ea-11da-999d38785348
# ╟─682db9f8-e7b1-11ea-3949-6b683ca8b47b
# ╟─088cc652-e7a8-11ea-0ca7-f744f6f3afdd
# ╟─c18dce7a-e7a7-11ea-0a1a-f944d46754e5
# ╟─5e24d95c-e6ce-11ea-24be-bb19e1e14657
# ╟─6b8883f6-e7b3-11ea-155e-6f62117e123b
# ╠═851c03a4-e7a4-11ea-1652-d59b7a6599f0
# ╠═d6ee91ea-e750-11ea-1260-31ebf3ec6a9b
# ╠═5acd58e0-e856-11ea-2d3d-8329889fe16f
# ╟─dbc4da6a-e7b4-11ea-3b70-6f2abfcab992
# ╠═e2848b9a-e703-11ea-24f9-b9131434a84b
# ╠═9664ac52-e750-11ea-171c-e7d57741a68c
# ╠═02b9c9d6-e752-11ea-0f32-91b7b6481684
# ╟─1eb79812-e7b5-11ea-1c10-63b24803dd8a
# ╟─d7e8202c-e7b5-11ea-30d3-adcd6867d5f5
# ╠═df0a4068-e7b2-11ea-2475-81b237d492b3
# ╟─f22222b4-e7b5-11ea-0ea0-8fa368d2a014
# ╠═ca8d2f72-e7b6-11ea-1893-f1e6d0a20dc7
# ╠═71c78614-e7bc-11ea-0959-c7a91a10d481
# ╟─c21096c0-e856-11ea-3dc5-a5b0cbf29335
# ╟─52533e00-e856-11ea-08a7-25e556fb1127
# ╟─147ed7b0-e856-11ea-0d0e-7ff0d527e352
# ╟─c1ecad86-e7bc-11ea-1201-23ee380181a1
# ╟─c9bf4288-e6ce-11ea-0e13-a36b5e685998
# ╟─a60a492a-e7bc-11ea-0f0b-75d81ce46a01
# ╟─b3c7a050-e855-11ea-3a22-3f514da746a4
# ╟─d3625d20-e6ce-11ea-394a-53208540d626
# ╠═dfdeab34-e751-11ea-0f90-2fa9bbdccb1e
# ╠═b923d394-e750-11ea-1971-595e09ab35b5

1640
hw1.jl

File diff suppressed because it is too large Load Diff

1035
hw2.jl

File diff suppressed because it is too large Load Diff

1318
hw3.jl

File diff suppressed because it is too large Load Diff

@ -0,0 +1,553 @@
### 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
# ╔═╡ 877df834-f078-11ea-303b-e98273ef98a4
begin
using Pkg
Pkg.activate(tempname())
end
# ╔═╡ 0316b94c-eef6-11ea-19bc-dbc959901bb5
begin
using Images
using ImageMagick
using Statistics
using LinearAlgebra
using ImageFiltering
end
# ╔═╡ fe19ad0a-ef04-11ea-1e5f-1bfcbbb51302
using PlutoUI
# ╔═╡ e196fa66-eef5-11ea-2afe-c9fcb6c48937
# Poor man's Project.toml
Pkg.add(["Images",
"ImageMagick",
"PlutoUI",
"Hyperscript",
"ImageFiltering"])
# ╔═╡ cb335074-eef7-11ea-24e8-c39a325166a1
md"""
# Seam Carving
1. We use convolution with Sobel filters for "edge detection".
2. We use that to write an algorithm that removes "uninteresting"
bits of an image in order to shrink it.
"""
# ╔═╡ d2ae6dd2-eef9-11ea-02df-255ec3b46a36
begin
example_urls = [
"https://cdn.shortpixel.ai/spai/w_1086+q_lossy+ret_img+to_webp/https://wisetoast.com/wp-content/uploads/2015/10/The-Persistence-of-Memory-salvador-deli-painting.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Gustave_Caillebotte_-_Paris_Street%3B_Rainy_Day_-_Google_Art_Project.jpg/1014px-Gustave_Caillebotte_-_Paris_Street%3B_Rainy_Day_-_Google_Art_Project.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Gustave_Caillebotte_-_Paris_Street%3B_Rainy_Day_-_Google_Art_Project.jpg/1014px-Gustave_Caillebotte_-_Paris_Street%3B_Rainy_Day_-_Google_Art_Project.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Grant_Wood_-_American_Gothic_-_Google_Art_Project.jpg/480px-Grant_Wood_-_American_Gothic_-_Google_Art_Project.jpg",
"https://cdn.shortpixel.ai/spai/w_1086+q_lossy+ret_img+to_webp/https://wisetoast.com/wp-content/uploads/2015/10/The-Persistence-of-Memory-salvador-deli-painting.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/7/7d/A_Sunday_on_La_Grande_Jatte%2C_Georges_Seurat%2C_1884.jpg/640px-A_Sunday_on_La_Grande_Jatte%2C_Georges_Seurat%2C_1884.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/758px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg",
"https://web.mit.edu/facilities/photos/construction/Projects/stata/1_large.jpg",
]
img = load(download(example_urls[2]))
end
# ╔═╡ 8340cf20-f079-11ea-1665-f5864bc49cb9
# ╔═╡ 0b6010a8-eef6-11ea-3ad6-c1f10e30a413
# arbitrarily choose the brightness of a pixel as mean of rgb
# brightness(c::AbstractRGB) = mean((c.r, c.g, c.b))
# Use a weighted sum of rgb giving more weight to colors we perceive as 'brighter'
# Based on https://www.tutorialspoint.com/dip/grayscale_to_rgb_conversion.htm
brightness(c::AbstractRGB) = 0.3 * c.r + 0.59 * c.g + 0.11 * c.b
# ╔═╡ fc1c43cc-eef6-11ea-0fc4-a90ac4336964
Gray.(brightness.(img))
# ╔═╡ 82c0d0c8-efec-11ea-1bb9-83134ecb877e
md"""
# Edge detection filter
(Spoiler alert!) Here, we use the Sobel edge detection filter we created in Homework 1.
```math
\begin{align}
G_x &= \begin{bmatrix}
1 & 0 & -1 \\
2 & 0 & -2 \\
1 & 0 & -1 \\
\end{bmatrix}*A\\
G_y &= \begin{bmatrix}
1 & 2 & 1 \\
0 & 0 & 0 \\
-1 & -2 & -1 \\
\end{bmatrix}*A
\end{align}
```
Here $A$ is the array corresponding to your image.
We can think of these as derivatives in the $x$ and $y$ directions.
Then we combine them by finding the magnitude of the **gradient** (in the sense of multivariate calculus) by defining
$$G_\text{total} = \sqrt{G_x^2 + G_y^2}.$$
"""
# ╔═╡ da726954-eff0-11ea-21d4-a7f4ae4a6b09
Sy, Sx = Kernel.sobel()
# ╔═╡ abf6944e-f066-11ea-18e2-0b92606dab85
(collect(Int.(8 .* Sy)), collect(Int.(8 .* Sx)))
# ╔═╡ ac8d6902-f069-11ea-0f1d-9b0fa706d769
md"""
- blue shows positive values
- red shows negative values
$G_x \hspace{180pt} G_y$
"""
# ╔═╡ 172c7612-efee-11ea-077a-5d5c6e2505a4
function shrink_image(image, ratio=5)
(height, width) = size(image)
new_height = height ÷ ratio - 1
new_width = width ÷ ratio - 1
list = [
mean(image[
ratio * i:ratio * (i + 1),
ratio * j:ratio * (j + 1),
])
for j in 1:new_width
for i in 1:new_height
]
reshape(list, new_height, new_width)
end
# ╔═╡ fcf46120-efec-11ea-06b9-45f470899cb2
function convolve(M, kernel)
height, width = size(kernel)
half_height = height ÷ 2
half_width = width ÷ 2
new_image = similar(M)
# (i, j) loop over the original image
m, n = size(M)
@inbounds for i in 1:m
for j in 1:n
# (k, l) loop over the neighbouring pixels
accumulator = 0 * M[1, 1]
for k in -half_height:-half_height + height - 1
for l in -half_width:-half_width + width - 1
Mi = i - k
Mj = j - l
# First index into M
if Mi < 1
Mi = 1
elseif Mi > m
Mi = m
end
# Second index into M
if Mj < 1
Mj = 1
elseif Mj > n
Mj = n
end
accumulator += kernel[k, l] * M[Mi, Mj]
end
end
new_image[i, j] = accumulator
end
end
return new_image
end
# ╔═╡ 6f7bd064-eff4-11ea-0260-f71aa7f4f0e5
function edgeness(img)
Sy, Sx = Kernel.sobel()
b = brightness.(img)
∇y = convolve(b, Sy)
∇x = convolve(b, Sx)
sqrt.(∇x.^2 + ∇y.^2)
end
# ╔═╡ dec62538-efee-11ea-1e03-0b801e61e91c
function show_colored_array(array)
pos_color = RGB(0.36, 0.82, 0.8)
neg_color = RGB(0.99, 0.18, 0.13)
to_rgb(x) = max(x, 0) * pos_color + max(-x, 0) * neg_color
to_rgb.(array) / maximum(abs.(array))
end
# ╔═╡ da39c824-eff0-11ea-375b-1b6c6e186182
# Sx
# collect(Int.(8 .* Sx))
show_colored_array(Sx)
# ╔═╡ 074a58be-f146-11ea-382c-b7ae6c44bf75
# Sy
# collect(Int.(8 .* Sy))
show_colored_array(Sy)
# ╔═╡ f8283a0e-eff4-11ea-23d3-9f1ced1bafb4
md"""
## Seam carving idea
The idea of seam carving is to find a path from the top of the image to the bottom of the image where the path minimizes the edgness.
In other words, this path **minimizes the number of edges it crosses**
"""
# ╔═╡ 025e2c94-eefb-11ea-12cb-f56f34886334
md"""
At every step in going down, the path is allowed to go south west, south or south east. We want to find a seam with the minimum possible sum of energies.
We start by writing a `least_edgy` function which given a matrix of energies, returns
a matrix of minimum possible energy starting from that pixel going up to a pixel in the bottom most row.
"""
# ╔═╡ e5a7e426-2d0c-11eb-07fd-cd587148bd38
small_img = shrink_image(img, 10)
# ╔═╡ eff57fca-2d06-11eb-0ef2-f94561e916eb
function get_seam_map(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
# ╔═╡ 1c01264c-2d08-11eb-0549-076c31bb8fa6
sm, sd = get_seam_map(edgeness(img))
# ╔═╡ acc1ee8c-eef9-11ea-01ac-9b9e9c4167b3
# e[x,y]
# ↙ ↓ ↘ <--pick the next path which gives the least overall energy
# e[x-1,y+1] e[x,y+1] e[x+1,y+1]
#
# Basic Comp: e[x,y] += min( e[x-1,y+1],e[x,y],e[x+1,y])
# dirs records which one from (-1==SW,0==S,1==SE)
function least_edgy(E)
least_E = zeros(size(E))
dirs = zeros(Int, size(E))
least_E[end, :] .= E[end, :] # the minimum energy on the last row is the energy
# itself
m, n = size(E)
# Go from the last row up, finding the minimum energy
for i in m-1:-1:1
for j in 1:n
j1, j2 = max(1, j-1), min(j+1, n)
e, dir = findmin(least_E[i+1, j1:j2])
least_E[i,j] += e
least_E[i,j] += E[i,j]
dirs[i, j] = (-1,0,1)[dir + (j==1)]
end
end
least_E, dirs
end
# ╔═╡ 8b204a2a-eff6-11ea-25b0-13f230037ee1
# The bright areas are screaming "AVOID ME!!!"
least_e, dirs = least_edgy(edgeness(img))
# ╔═╡ 84d3afe4-eefe-11ea-1e31-bf3b2af4aecd
show_colored_array(least_e)
# ╔═╡ 50a49b56-2d09-11eb-2d33-8d4cd5896e38
show_colored_array(sm)
# ╔═╡ b507480a-ef01-11ea-21c4-63d19fac19ab
# direction the path should take at every pixel.
reduce((x,y)->x*y*"\n",
reduce(*, getindex.(([" ", "", "", ""],), dirs[1:25, 1:76].+3), dims=2, init=""), init="") |> Text
# ╔═╡ 696cf94e-2d09-11eb-136a-8f360d3a7507
reduce((x,y)->x*y*"\n",
reduce(*, getindex.(([" ", "", "", ""],), sd[1:25, 1:76].+3), dims=2, init=""), init="") |> Text
# ╔═╡ 7d8b20a2-ef03-11ea-1c9e-fdf49a397619
md"## Remove seams"
# ╔═╡ f690b06a-ef31-11ea-003b-4f2b2f82a9c3
md"""
Compressing an image horizontally involves a number of seams of lowest energy successively.
"""
# ╔═╡ 0adf2e18-2d10-11eb-2a27-6de83b5aad3a
function get_seam_at2(sd, j)
h = size(sd, 1)
js = Array{Int}(undef, h)
js[1] = j
for i=2:h
js[i] = js[i-1] + sd[i-1,js[i-1]]
end
tuple.(1:h, js)
end
# ╔═╡ 977b6b98-ef03-11ea-0176-551fc29729ab
function get_seam_at(dirs, j)
m = size(dirs, 1)
js = fill(0, m)
js[1] = j
for i=2:m
js[i] = js[i-1] + dirs[i-1, js[i-1]]
end
tuple.(1:m, js)
end
# ╔═╡ 9abbb158-ef03-11ea-39df-a3e8aa792c50
get_seam_at(dirs, 2)
# ╔═╡ fe709508-2d10-11eb-02a9-29080b78d44a
get_seam_at2(dirs, 2)
# ╔═╡ 14f72976-ef05-11ea-2ad5-9f0914f9cf58
function mark_path(img, path)
img = copy(img)
m = size(img, 2)
for (i, j) in 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
# ╔═╡ cf9a9124-ef04-11ea-14a4-abf930edc7cc
@bind start_column Slider(1:size(img, 2))
# ╔═╡ 772a4d68-ef04-11ea-366a-f7ae9e1634f6
path = get_seam_at2(dirs, start_column)
# ╔═╡ 081a98cc-f06e-11ea-3664-7ba51d4fd153
function pencil(X)
f(x) = RGB(1-x,1-x,1-x)
map(f, X ./ maximum(X))
end
# ╔═╡ 237647e8-f06d-11ea-3c7e-2da57e08bebc
e = edgeness(img);
# ╔═╡ c7a7386e-2d11-11eb-3885-056f6d7fb840
# ╔═╡ 4f23bc54-ef0f-11ea-06a9-35ca3ece421e
function rm_path(img, path)
img = img[:, 1:end-1] # one less column
for (i, j) in path
img[i, 1:j-1] .= img[i, 1:j-1]
img[i, j:end] .= img[i, j+1:end]
end
img
end
# ╔═╡ b401f398-ef0f-11ea-38fe-012b7bc8a4fa
function shrink_n(img, n)
imgs = []
marked_imgs = []
e = edgeness(img)
for i=1:n
least_E, dirs = get_seam_map(e)
_, min_j = findmin(@view least_E[1, :])
seam = get_seam_at2(dirs, min_j)
img = rm_path(img, seam)
# Recompute the energy for the new image
# Note, this currently involves rerunning the convolution
# on the whole image, but in principle the only values that
# need recomputation are those adjacent to the seam, so there
# is room for a meanintful speedup here.
e = edgeness(img)
e = rm_path(e, seam)
push!(imgs, img)
push!(marked_imgs, mark_path(img, seam))
end
imgs, marked_imgs
end
# ╔═╡ b1b6b7fc-f153-11ea-224a-2578e8298775
n_examples = min(200, size(img, 2))
# ╔═╡ 2eb459d4-ef36-11ea-1f74-b53ffec7a1ed
# returns two vectors of n successively smaller images
# The second images have markings where the seam is cut out
carved, marked_carved = shrink_n(img, n_examples);
# ╔═╡ 7038abe4-ef36-11ea-11a5-75e57ab51032
@bind n Slider(1:length(carved))
# ╔═╡ 2d6c6820-ef2d-11ea-1704-49bb5188cfcc
md"shrunk by $n:"
# ╔═╡ 1fd26a60-f089-11ea-1f56-bb6eba7d9651
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
# ╔═╡ 44192a40-eff2-11ea-0ec7-05cdadb0c29a
begin
img_brightness = brightness.(img)
∇x = convolve(img_brightness, Sx)
∇y = convolve(img_brightness, Sy)
hbox(show_colored_array(∇x), show_colored_array(∇y))
end
# ╔═╡ d6a268c0-eff4-11ea-2c9e-bfef19c7f540
begin
edged = edgeness(img)
# hbox(img, pencil(edged))
hbox(img, Gray.(edgeness(img)) / maximum(abs.(edged)))
end
# ╔═╡ 552fb92e-ef05-11ea-0a79-dd7a6760089a
hbox(mark_path(img, path), mark_path(show_colored_array(least_e), path))
# ╔═╡ dfd03c4e-f06c-11ea-1e2a-89233a675138
let
hbox(mark_path(img, path), mark_path(pencil(e), path));
end
# ╔═╡ ca4a87e8-eff8-11ea-3d57-01dfa34ff723
let
# least energy path of them all:
_, k = findmin(least_e[1, :])
path = get_seam_at(dirs, k)
hbox(
mark_path(img, path),
mark_path(show_colored_array(least_e), path)
)
end
# ╔═╡ fa6a2152-ef0f-11ea-0e67-0d1a6599e779
hbox(img, marked_carved[n], sy=size(img))
# ╔═╡ 71b16dbe-f08b-11ea-2343-5f1583074029
vbox(x,y, gap=16) = hbox(x', y')'
# ╔═╡ ddac52ea-f148-11ea-2860-21cff4c867e6
let
∇y = convolve(brightness.(img), Sy)
∇x = convolve(brightness.(img), Sx)
# zoom in on the clock
vbox(
hbox(img[300:end, 1:300], img[300:end, 1:300]),
hbox(show_colored_array.((∇x[300:end, 1:300], ∇y[300:end, 1:300]))...)
)
end
# ╔═╡ 15d1e5dc-ef2f-11ea-093a-417108bcd495
[size(img) size(carved[n])]
# ╔═╡ Cell order:
# ╠═877df834-f078-11ea-303b-e98273ef98a4
# ╠═e196fa66-eef5-11ea-2afe-c9fcb6c48937
# ╠═0316b94c-eef6-11ea-19bc-dbc959901bb5
# ╟─cb335074-eef7-11ea-24e8-c39a325166a1
# ╠═d2ae6dd2-eef9-11ea-02df-255ec3b46a36
# ╠═8340cf20-f079-11ea-1665-f5864bc49cb9
# ╠═0b6010a8-eef6-11ea-3ad6-c1f10e30a413
# ╠═fc1c43cc-eef6-11ea-0fc4-a90ac4336964
# ╟─82c0d0c8-efec-11ea-1bb9-83134ecb877e
# ╠═da726954-eff0-11ea-21d4-a7f4ae4a6b09
# ╠═da39c824-eff0-11ea-375b-1b6c6e186182
# ╠═074a58be-f146-11ea-382c-b7ae6c44bf75
# ╠═abf6944e-f066-11ea-18e2-0b92606dab85
# ╠═44192a40-eff2-11ea-0ec7-05cdadb0c29a
# ╟─ac8d6902-f069-11ea-0f1d-9b0fa706d769
# ╠═ddac52ea-f148-11ea-2860-21cff4c867e6
# ╠═6f7bd064-eff4-11ea-0260-f71aa7f4f0e5
# ╟─d6a268c0-eff4-11ea-2c9e-bfef19c7f540
# ╠═172c7612-efee-11ea-077a-5d5c6e2505a4
# ╟─fcf46120-efec-11ea-06b9-45f470899cb2
# ╟─dec62538-efee-11ea-1e03-0b801e61e91c
# ╟─f8283a0e-eff4-11ea-23d3-9f1ced1bafb4
# ╟─025e2c94-eefb-11ea-12cb-f56f34886334
# ╠═e5a7e426-2d0c-11eb-07fd-cd587148bd38
# ╠═eff57fca-2d06-11eb-0ef2-f94561e916eb
# ╠═1c01264c-2d08-11eb-0549-076c31bb8fa6
# ╠═acc1ee8c-eef9-11ea-01ac-9b9e9c4167b3
# ╠═8b204a2a-eff6-11ea-25b0-13f230037ee1
# ╠═84d3afe4-eefe-11ea-1e31-bf3b2af4aecd
# ╠═50a49b56-2d09-11eb-2d33-8d4cd5896e38
# ╠═b507480a-ef01-11ea-21c4-63d19fac19ab
# ╠═696cf94e-2d09-11eb-136a-8f360d3a7507
# ╟─7d8b20a2-ef03-11ea-1c9e-fdf49a397619
# ╠═f690b06a-ef31-11ea-003b-4f2b2f82a9c3
# ╠═fe19ad0a-ef04-11ea-1e5f-1bfcbbb51302
# ╠═0adf2e18-2d10-11eb-2a27-6de83b5aad3a
# ╠═977b6b98-ef03-11ea-0176-551fc29729ab
# ╠═9abbb158-ef03-11ea-39df-a3e8aa792c50
# ╠═fe709508-2d10-11eb-02a9-29080b78d44a
# ╠═772a4d68-ef04-11ea-366a-f7ae9e1634f6
# ╟─14f72976-ef05-11ea-2ad5-9f0914f9cf58
# ╠═cf9a9124-ef04-11ea-14a4-abf930edc7cc
# ╠═552fb92e-ef05-11ea-0a79-dd7a6760089a
# ╠═081a98cc-f06e-11ea-3664-7ba51d4fd153
# ╠═237647e8-f06d-11ea-3c7e-2da57e08bebc
# ╠═dfd03c4e-f06c-11ea-1e2a-89233a675138
# ╠═ca4a87e8-eff8-11ea-3d57-01dfa34ff723
# ╠═c7a7386e-2d11-11eb-3885-056f6d7fb840
# ╠═4f23bc54-ef0f-11ea-06a9-35ca3ece421e
# ╠═b401f398-ef0f-11ea-38fe-012b7bc8a4fa
# ╠═b1b6b7fc-f153-11ea-224a-2578e8298775
# ╠═2eb459d4-ef36-11ea-1f74-b53ffec7a1ed
# ╠═7038abe4-ef36-11ea-11a5-75e57ab51032
# ╟─2d6c6820-ef2d-11ea-1704-49bb5188cfcc
# ╠═fa6a2152-ef0f-11ea-0e67-0d1a6599e779
# ╟─71b16dbe-f08b-11ea-2343-5f1583074029
# ╟─1fd26a60-f089-11ea-1f56-bb6eba7d9651
# ╟─15d1e5dc-ef2f-11ea-093a-417108bcd495
Loading…
Cancel
Save