diff --git a/src/computational-thinking/homework1.jl b/src/computational-thinking/homework1.jl
new file mode 100644
index 0000000..fac6d2e
--- /dev/null
+++ b/src/computational-thinking/homework1.jl
@@ -0,0 +1,2251 @@
+### A Pluto.jl notebook ###
+# v0.19.41
+
+#> [frontmatter]
+#> chapter = 1
+#> license_url = "https://github.com/mitmath/computational-thinking/blob/Fall23/LICENSE.md"
+#> text_license = "CC-BY-SA-4.0"
+#> code_license = "MIT"
+#> section = 2.5
+#> order = 2.5
+#> homework_number = 1
+#> title = "Images and Arrays"
+#> layout = "layout.jlhtml"
+#> tags = ["homework", "module1", "image", "track_julia", "track_math", "track_climate", "track_data", "programming", "interactive", "type", "matrix"]
+#> description = "Practice Julia basics by working with arrays of colors. At the end of this homework, you can see all of your filters applied to your webcam image!"
+
+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 iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
+ local el = $(esc(element))
+ global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
+ el
+ end
+end
+
+# ╔═╡ 65780f00-ed6b-11ea-1ecf-8b35523a7ac0
+begin
+ using Images, ImageIO
+ using PlutoUI
+ using HypertextLiteral
+end
+
+# ╔═╡ 7c798410-ffd2-4873-bff8-d3802fd20ee8
+using PlutoTeachingTools
+
+# ╔═╡ b59ecffd-d201-4292-b61d-d18c88d4a15b
+html"""
+
+
+
+
+
+
+
+
This homework is part of Computational Thinking, a live online Julia/Pluto textbook. Go to computationalthinking.mit.edu to read all 50 lectures for free.
+
+
+"""
+
+# ╔═╡ ac8ff080-ed61-11ea-3650-d9df06123e1f
+md"""
+
+# **Homework 1** - _images and arrays_
+`18.S191`, Fall 2023
+
+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.
+
+Feel free to ask questions!
+"""
+
+# ╔═╡ 5f95e01a-ee0a-11ea-030c-9dba276aba92
+md"""
+#### Initializing packages
+
+_When running this notebook for the first time, this could take up to 15 minutes. Hang in there!_
+"""
+
+# ╔═╡ 540ccfcc-ee0a-11ea-15dc-4f8120063397
+md"""
+## **Exercise 1** - _Manipulating vectors (1D images)_
+
+A `Vector` is a 1D array. We can think of that as a 1D image.
+
+"""
+
+# ╔═╡ 467856dc-eded-11ea-0f83-13d939021ef3
+example_vector = [0.5, 0.4, 0.3, 0.2, 0.1, 0.0, 0.7, 0.0, 0.7, 0.9]
+
+# ╔═╡ ad6a33b0-eded-11ea-324c-cfabfd658b56
+md"""
+$(html" ")
+#### Exercise 1.1
+👉 Make a random vector `random_vect` of length 10 using the `rand` function.
+"""
+
+# ╔═╡ f51333a6-eded-11ea-34e6-bfbb3a69bcb0
+random_vect = missing # replace `missing` with your code!
+
+# ╔═╡ 397941fc-edee-11ea-33f2-5d46c759fbf7
+if !@isdefined(random_vect)
+ not_defined(:random_vect)
+elseif ismissing(random_vect)
+ still_missing()
+elseif !(random_vect isa Vector)
+ keep_working(md"`random_vect` should be a `Vector`.")
+elseif eltype(random_vect) != Float64
+ almost(md"""
+ You generated a vector of random integers. For the remaining exercises, we want a vector of `Float64` numbers.
+
+ The (optional) first argument to `rand` specifies the **type** of elements to generate. For example: `rand(Bool, 10)` generates 10 values that are either `true` or `false`. (Try it!)
+ """)
+elseif length(random_vect) != 10
+ keep_working(md"`random_vect` does not have the correct size.")
+elseif length(Set(random_vect)) != 10
+ keep_working(md"`random_vect` is not 'random enough'")
+else
+ correct(md"Well done! You can run your code again to generate a new vector!")
+end
+
+# ╔═╡ b1d5ca28-edf6-11ea-269e-75a9fb549f1d
+md"""
+You can find out more about any function (like `rand`) by clicking on the Live Docs in the bottom right of this Pluto window, and typing a function name in the top.
+
+![image](https://user-images.githubusercontent.com/6933510/107848812-c934df80-6df6-11eb-8a32-663d802f5d11.png)
+
+
+![image](https://user-images.githubusercontent.com/6933510/107848846-0f8a3e80-6df7-11eb-818a-7271ecb9e127.png)
+
+We recommend that you leave the window open while you work on Julia code. It will continually look up documentation for anything you type!
+
+#### Help, I don't see the Live Docs!
+
+Try the following:
+
+🙋 **Are you viewing a static preview?** The Live Docs only work if you _run_ the notebook. If you are reading this on our course website, then click the button in the top right to run the notebook.
+
+🙋 **Is your screen too small?** Try resizing your window or zooming out.
+""" |> hint
+
+# ╔═╡ 5da8cbe8-eded-11ea-2e43-c5b7cc71e133
+begin
+ colored_line(x::Vector) = hcat(Gray.(Float64.(x)))'
+ colored_line(x::Any) = nothing
+end
+
+# ╔═╡ 56ced344-eded-11ea-3e81-3936e9ad5777
+colored_line(example_vector)
+
+# ╔═╡ b18e2c54-edf1-11ea-0cbf-85946d64b6a2
+colored_line(random_vect)
+
+# ╔═╡ 77adb065-bfd4-4680-9c2a-ad4d92689dbf
+md"#### Exercise 1.2
+👉 Make a function `my_sum` using a `for` loop, which computes the total of a vector of numbers."
+
+# ╔═╡ bd907ee1-5253-4cae-b5a5-267dac24362a
+function my_sum(xs)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 6640110a-d171-4b32-8d12-26979a36b718
+my_sum([1,2,3])
+
+# ╔═╡ e0bfc973-2808-4f84-b065-fb3d05401e30
+if !@isdefined(my_sum)
+ not_defined(:my_sum)
+else
+ let
+ result = my_sum([1,2,3])
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif result != 6
+ keep_working()
+ else
+ correct()
+ end
+ end
+end
+
+# ╔═╡ 24090306-7395-4f2f-af31-34f7486f3945
+hint(md"""Check out this page for a refresher on basic Julia syntax:
+
+ [Basic Julia Syntax](https://computationalthinking.mit.edu/Spring21/basic_syntax/)""")
+
+# ╔═╡ cf738088-eded-11ea-2915-61735c2aa990
+md"#### Exercise 1.3
+👉 Use your `my_sum` function to write a function `mean`, which computes the mean/average of a vector of numbers."
+
+# ╔═╡ 0ffa8354-edee-11ea-2883-9d5bfea4a236
+function mean(xs)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 1f104ce4-ee0e-11ea-2029-1d9c817175af
+mean([1, 2, 3])
+
+# ╔═╡ 38dc80a0-edef-11ea-10e9-615255a4588c
+if !@isdefined(mean)
+ not_defined(:mean)
+else
+ let
+ result = mean([1,2,3])
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif result != 2
+ keep_working()
+ else
+ correct()
+ end
+ end
+end
+
+# ╔═╡ 1f229ca4-edee-11ea-2c56-bb00cc6ea53c
+md"👉 Define `m` to be the mean of `random_vect`."
+
+# ╔═╡ 2a391708-edee-11ea-124e-d14698171b68
+m = missing # replace `missing` with your code!
+
+# ╔═╡ 2b1ccaca-edee-11ea-34b0-c51659f844d0
+if !@isdefined(m)
+ not_defined(:m)
+elseif ismissing(m)
+ still_missing()
+elseif !(m isa Number)
+ keep_working(md"`m` should be a number.")
+elseif m != mean(random_vect)
+ keep_working()
+else
+ correct()
+end
+
+# ╔═╡ e2863d4c-edef-11ea-1d67-332ddca03cc4
+md"""#### Exercise 1.4
+👉 Write a function `demean`, which takes a vector `xs` and subtracts the mean from each value in `xs`. Use your `mean` function!"""
+
+# ╔═╡ ea8d92f8-159c-4161-8c54-bab7bc00f290
+md"""
+> ### Note about _mutation_
+> There are two ways to think about this exercise, you could _modify_ the original vector, or you can _create a new vector_. We often prefer the second version, so that the original data is preserved. We generally only use code of the first variant in the most performance-sensitive parts of a program, as it requires more care to write and use correctly. _**Be careful not to get carried away in optimizing code**, especially when learning a new language!_
+>
+> There is a convention among Julians that functions that modify their argument have a `!` in their name. For example, `sort(x)` returns a sorted _copy_ of `x`, while `sort!(x)` _modifies_ `x` to be sorted.
+>
+> #### Tips for writing non-mutating code
+> 1. _Rewriting_ an existing mutating function to be non-mutating can feel like a 'tedious' and 'inefficient' process. Often, instead of trying to **rewrite** a mutating function, it's best to take a step back and try to think of your problem as _constructing something new_. Instead of a `for` loop, it might make more sense to use **descriptive** primitives like [broadcasting with the dot syntax](https://docs.julialang.org/en/v1/manual/functions/#man-vectorized) (also for [math operators](https://docs.julialang.org/en/v1/manual/mathematical-operations/#man-dot-operators)), and [map and filter](https://www.youtube.com/watch?v=_O-HBDZMLrM).
+>
+>
+> 2. If a mutating algorithm makes the most sense for your problem, then you can first use `copy` to create a copy of an array, and then modify that copy.
+>
+> We will cover this topic more in the later exercises!
+
+"""
+
+# ╔═╡ ec5efe8c-edef-11ea-2c6f-afaaeb5bc50c
+function demean(xs)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ d6ddafdd-1a44-48c7-b49a-554073cdf331
+test_vect = let
+
+ # feel free to change your test case here!
+ to_create = [-1.0, -1.5, 8.5]
+
+
+ ####
+ # this cell is a bit funky to deal with a common pitfall from last year
+ # it regenerates the vector if you accidentally wrote a mutating function
+
+ # don't worry about how it works for this exercise!
+
+ demean
+ to_create
+end
+
+# ╔═╡ 29e10640-edf0-11ea-0398-17dbf4242de3
+md"To verify our function, let's check that the mean of the `demean(test_vect)` is 0: (_Due to floating-point round-off error it may *not* be *exactly* 0._)"
+
+# ╔═╡ 38155b5a-edf0-11ea-3e3f-7163da7433fb
+demeaned_test_vect = demean(test_vect)
+
+# ╔═╡ 1267e961-5b75-4b55-8080-d45316a03b9b
+mean(demeaned_test_vect)
+
+# ╔═╡ adf476d8-a334-4b35-81e8-cc3b37de1f28
+if !@isdefined(mean)
+ not_defined(:mean)
+else
+ let
+ input = Float64[1,2,3]
+ result = demean(input)
+
+ if input === result
+ almost(md"""
+ It looks like you **modified** `xs` inside the function.
+
+ It is preferable to avoid mutation inside functions, because you might want to use the original data again. For example, applying `demean` to a dataset of sensor readings would **modify** the original data, and the rest of your analysis would be erroneous.
+
+ """)
+ elseif ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif !(result isa AbstractVector) || length(result) != 3
+ keep_working(md"Return a vector of the same size as `xs`.")
+ elseif abs(sum(result) / 3) < 1e-10
+ correct()
+ else
+ keep_working()
+ end
+ end
+end
+
+# ╔═╡ a5f8bafe-edf0-11ea-0da3-3330861ae43a
+md"""
+#### Exercise 1.5
+
+👉 Generate a vector of 100 elements. Where:
+- the center 20 elements are set to `1`, and
+- all other elements are `0`.
+"""
+
+# ╔═╡ b6b65b94-edf0-11ea-3686-fbff0ff53d08
+function create_bar()
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 4a5e9d2c-dd90-4bb0-9e31-3f5c834406b4
+create_bar()
+
+# ╔═╡ d862fb16-edf1-11ea-36ec-615d521e6bc0
+colored_line(create_bar())
+
+# ╔═╡ aa1ff74a-4e78-4ef1-8b8d-3a60a168cf6d
+hint(md"""
+In [Section 1.1](https://computationalthinking.mit.edu/Spring21/week1/), we drew a red square on top of the image Philip with a simple command...
+""")
+
+# ╔═╡ e3394c8a-edf0-11ea-1bb8-619f7abb6881
+if !@isdefined(create_bar)
+ not_defined(:create_bar)
+else
+ let
+ result = create_bar()
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif !(result isa Vector) || length(result) != 100
+ keep_working(md"The result should be a `Vector` with 100 elements.")
+ elseif result[[1,50,100]] != [0,1,0]
+ keep_working()
+ else
+ correct()
+ end
+ end
+end
+
+# ╔═╡ 59414833-a108-4b1e-9a34-0f31dc907c6e
+url = "https://user-images.githubusercontent.com/6933510/107239146-dcc3fd00-6a28-11eb-8c7b-41aaf6618935.png"
+
+# ╔═╡ c5484572-ee05-11ea-0424-f37295c3072d
+philip_filename = download(url) # download to a local file. The filename is returned
+
+# ╔═╡ c8ecfe5c-ee05-11ea-322b-4b2714898831
+philip = load(philip_filename)
+
+# ╔═╡ e86ed944-ee05-11ea-3e0f-d70fc73b789c
+md"_Hi there Philip_"
+
+# ╔═╡ 6ccd8902-0dd9-4335-a11a-ee7f9a1a6c09
+philip_head = philip[470:800, 140:410]
+
+# ╔═╡ 15088baa-c337-405d-8885-19a6e2bfd6aa
+md"""
+Recall from [Section 1.1](https://computationalthinking.mit.edu/Spring21/week1/) that in Julia, an _image_ is just a 2D array of color objects:
+"""
+
+# ╔═╡ 7ad4f6bc-588e-44ab-8ca9-c87cd1048442
+typeof(philip)
+
+# ╔═╡ a55bb5ca-600b-4aa0-b95f-7ece20845c9b
+md"""
+Every pixel (i.e. _element of the 2D array_) is of the `RGB` type:
+"""
+
+# ╔═╡ c5dc0cc8-9305-47e6-8b20-a9f8ef867799
+philip_pixel = philip[100,100]
+
+# ╔═╡ de772e21-0bea-4fd2-868a-9a7d32550bc9
+typeof(philip_pixel)
+
+# ╔═╡ 21bdc692-91ee-474d-ae98-455913a2342e
+md"""
+To get the values of its individual color channels, use the `r`, `g` and `b` _attributes_:
+"""
+
+# ╔═╡ 2ae3f379-96ce-435d-b863-deba4586ec71
+philip_pixel.r, philip_pixel.g, philip_pixel.b
+
+# ╔═╡ c49ba901-d798-489a-963c-4cc113c7abfd
+md"""
+And to create an `RGB` object yourself:
+"""
+
+# ╔═╡ 93451c37-77e1-4d4f-9788-c2a3da1401ee
+RGB(0.1, 0.4, 0.7)
+
+# ╔═╡ f52e4914-2926-4a42-9e45-9caaace9a7db
+md"""
+#### Exercise 2.1
+👉 Write a function **`get_red`** that takes a single pixel, and returns the value of its red channel.
+"""
+
+# ╔═╡ a8b2270a-600c-4f83-939e-dc5ab35f4735
+function get_red(pixel::AbstractRGB)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ c320b39d-4cea-4fa1-b1ce-053c898a67a6
+get_red(RGB(0.8, 0.1, 0.0))
+
+# ╔═╡ 09102183-f9fb-4d89-b4f9-5d76af7b8e90
+let
+ result = get_red(RGB(0.2, 0.3, 0.4))
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif result == 0.2
+ correct()
+ else
+ keep_working()
+ end
+end
+
+# ╔═╡ d8cf9bd5-dbf7-4841-acf9-eef7e7cabab3
+md"""
+#### Exercise 2.2
+👉 Write a function **`get_reds`** (note the extra `s`) that accepts a 2D color array called `image`, and returns a 2D array with the red channel value of each pixel. (The result should be a 2D array of _numbers_.) Use your function `get_red` from the previous exercise.
+"""
+
+# ╔═╡ ebe1d05c-f6aa-437d-83cb-df0ba30f20bf
+function get_reds(image::AbstractMatrix)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ c427554a-6f6a-43f1-b03b-f83239887cee
+get_reds(philip_head)
+
+# ╔═╡ 63ac142e-6d9d-4109-9286-030a02c900b4
+let
+ test = [RGB(0.2, 0, 0) RGB(0.6, 0, 0)]
+ result = get_reds(test)
+
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif result == [ 0.2 0.6 ]
+ correct()
+ else
+ keep_working()
+ end
+end
+
+# ╔═╡ 50e2b0fb-b06d-4ac1-bdfb-eab833466736
+md"""
+This exercise can be quite difficult if you use a `for` loop or list comprehension.
+
+Instead, you should use the [dot syntax](https://docs.julialang.org/en/v1/manual/functions/#man-vectorized) to apply a function _element-wise_ to an array. For example, this is how you get the square root of `3`:
+
+```
+sqrt(3)
+```
+
+and this is how you get the square roots of 1, 2 and 3:
+
+```
+sqrt.([1, 2, 3])
+```
+
+""" |> hint
+
+# ╔═╡ 4fd07e01-2f8b-4ec9-9f4f-8a9e5ff56fb6
+md"""
+
+Great! By extracting the red channel value of each pixel, we get a 2D array of numbers. We went from an image (2D array of RGB colors) to a matrix (2D array of numbers).
+
+#### Exercise 2.3
+Let's try to visualize this matrix. Right now, it is displayed in text form, but because the image is quite large, most rows and columns don't fit on the screen. Instead, a better way to visualize it is to **view a number matrix as an image**.
+
+This is easier than you might think! We just want to map each number to an `RGB` object, and the result will be a 2D array of `RGB` objects, which Julia will display as an image.
+
+First, let's define a function that turns a _number_ into a _color_.
+"""
+
+# ╔═╡ 97c15896-6d99-4292-b7d7-4fcd2353656f
+function value_as_color(x)
+
+ return RGB(x, 0, 0)
+end
+
+# ╔═╡ cbb9bf41-4c21-42c7-b0e0-fc1ce29e0eb1
+value_as_color(0.0), value_as_color(0.6), value_as_color(1.0)
+
+# ╔═╡ 3f1a670b-44c2-4cab-909c-65f4ae9ed14b
+md"""
+👉 Use the functions `get_reds` and `value_as_color` to visualize the red channel values of `philip_head`. Tip: Like in previous exercises, use broadcasting ('dot syntax') to apply a function _element-wise_.
+
+Use the ➕ button at the bottom left of this cell to add more cells.
+"""
+
+# ╔═╡ 21ba6e75-55a2-4614-9b5d-ea6378bf1d98
+
+
+# ╔═╡ f7825c18-ff28-4e23-bf26-cc64f2f5049a
+md"""
+
+#### Exercise 2.4
+👉 Write four more functions, `get_green`, `get_greens`, `get_blue` and `get_blues`, to be the equivalents of `get_red` and `get_reds`. Use the ➕ button at the bottom left of this cell to add new cells.
+"""
+
+# ╔═╡ d994e178-78fd-46ab-a1bc-a31485423cad
+
+
+# ╔═╡ c54ccdea-ee05-11ea-0365-23aaf053b7d7
+md"""
+#### Exercise 2.5
+👉 Write a function **`mean_color`** that accepts an object called `image`. It should calculate the mean amounts of red, green and blue in the image and return the average color. Be sure to use functions from previous exercises!
+"""
+
+# ╔═╡ f6898df6-ee07-11ea-2838-fde9bc739c11
+function mean_color(image)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 5be9b144-ee0d-11ea-2a8d-8775de265a1d
+mean_color(philip)
+
+# ╔═╡ 4d0158d0-ee0d-11ea-17c3-c169d4284acb
+if !@isdefined(mean_color)
+ not_defined(:mean_color)
+else
+ let
+ input = reshape([RGB(1.0, 1.0, 1.0), RGB(1.0, 1.0, 0.0)], (2, 1))
+
+ result = mean_color(input)
+ shouldbe = RGB(1.0, 1.0, 0.5)
+
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif !(result isa AbstractRGB)
+ keep_working(md"You need to return a _color_, i.e. an object of type `RGB`. Use `RGB(r, g, b)` to create a color with channel values `r`, `g` and `b`.")
+ elseif !(result == shouldbe)
+ keep_working()
+ else
+ correct()
+ end
+ end
+end
+
+# ╔═╡ 5f6635b4-63ed-4a62-969c-bd4084a8202f
+md"""
+_At the end of this homework, you can see all of your filters applied to your webcam image!_
+"""
+
+# ╔═╡ 63e8d636-ee0b-11ea-173d-bd3327347d55
+function invert(color::AbstractRGB)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 80a4cb23-49c9-4446-a3ec-b2203128dc27
+let
+ result = invert(RGB(1.0, 0.5, 0.25)) # I chose these values because they can be represented exactly by Float64
+ shouldbe = RGB(0.0, 0.5, 0.75)
+
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif !(result isa AbstractRGB)
+ keep_working(md"You need to return a _color_, i.e. an object of type `RGB`. Use `RGB(r, g, b)` to create a color with channel values `r`, `g` and `b`.")
+ elseif !(result == shouldbe)
+ keep_working()
+ else
+ correct()
+ end
+end
+
+# ╔═╡ 2cc2f84e-ee0d-11ea-373b-e7ad3204bb00
+md"Let's invert some colors:"
+
+# ╔═╡ b8f26960-ee0a-11ea-05b9-3f4bc1099050
+color_black = RGB(0.0, 0.0, 0.0)
+
+# ╔═╡ 5de3a22e-ee0b-11ea-230f-35df4ca3c96d
+invert(color_black)
+
+# ╔═╡ 4e21e0c4-ee0b-11ea-3d65-b311ae3f98e9
+color_red = RGB(0.8, 0.1, 0.1)
+
+# ╔═╡ 6dbf67ce-ee0b-11ea-3b71-abc05a64dc43
+invert(color_red)
+
+# ╔═╡ 846b1330-ee0b-11ea-3579-7d90fafd7290
+md"👉 Can you invert the picture of Philip?"
+
+# ╔═╡ 943103e2-ee0b-11ea-33aa-75a8a1529931
+philip_inverted = missing # replace `missing` with your code!
+
+# ╔═╡ 55b138b7-19fb-4da1-9eb1-1e8304528251
+md"""
+_At the end of this homework, you can see all of your filters applied to your webcam image!_
+"""
+
+# ╔═╡ f68d4a36-ee07-11ea-0832-0360530f102e
+md"""
+#### Exercise 3.2
+👉 Look up the documentation on the `floor` function. Use it to write a function `quantize(x::Number)` that takes in a value $x$ (which you can assume is between 0 and 1) and "quantizes" it into bins of width 0.1. For example, check that 0.267 gets mapped to 0.2.
+"""
+
+# ╔═╡ fbd1638d-8d7a-4d12-aff9-9c160cc3fd74
+function quantize(x::Number)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 7720740e-2d2b-47f7-98fd-500ed3eee479
+md"""
+#### Intermezzo: _multiple methods_
+
+In Julia, we often write multiple methods for the same function. When a function is called, the method is chosen based on the input arguments. Let's look at an example:
+
+These are two _methods_ to the same function, because they have
+
+> **the same name, but different input types**
+"""
+
+# ╔═╡ 90421bca-0804-4d6b-8bd0-3ddbd54cc5fe
+function double(x::Number)
+
+ return x * 2
+end
+
+# ╔═╡ b2329e4c-6204-453e-8998-2414b869b808
+function double(x::Vector)
+
+ return [x..., x...]
+end
+
+# ╔═╡ 23fcd65f-0182-41f3-80ec-d85b05136c47
+md"""
+When we call the function `double`, Julia will decide which method to call based on the given input argument!
+"""
+
+# ╔═╡ 5055b74c-b98d-41fa-a0d8-cb36200d82cc
+double(24)
+
+# ╔═╡ 8db17b2b-0cf9-40ba-8f6f-2e53be7b6355
+double([1,2,37])
+
+# ╔═╡ a8a597e0-a01c-40cd-9902-d56430afd938
+md"""
+We call this **multiple dispatch**, and it is one of Julia's key features. Throughout this course, you will see lots of real-world application, and learn to use multiple dispatch to create flexible and readable abstractions!
+"""
+
+# ╔═╡ f6b218c0-ee07-11ea-2adb-1968c4fd473a
+md"""
+#### Exercise 3.3
+👉 Write the second **method** of the function `quantize`, i.e. a new *version* of the function with the *same* name. This method will accept a color object called `color`, of the type `AbstractRGB`.
+
+Here, `::AbstractRGB` is a **type annotation**. This ensures that this version of the function will be chosen when passing in an object whose type is a **subtype** of the `AbstractRGB` abstract type. For example, both the `RGB` and `RGBX` types satisfy this.
+
+The method you write should return a new `RGB` object, in which each component ($r$, $g$ and $b$) are quantized. Use your previous method for `quantize`!
+"""
+
+# ╔═╡ 04e6b486-ceb7-45fe-a6ca-733703f16357
+function quantize(color::AbstractRGB)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ f6bf64da-ee07-11ea-3efb-05af01b14f67
+md"""
+#### Exercise 3.4
+👉 Write a method `quantize(image::AbstractMatrix)` that quantizes an image by quantizing each pixel in the image. (You may assume that the matrix is a matrix of color objects.)
+"""
+
+# ╔═╡ 13e9ec8d-f615-4833-b1cf-0153010ccb65
+function quantize(image::AbstractMatrix)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ f6a655f8-ee07-11ea-13b6-43ca404ddfc7
+quantize(0.267), quantize(0.91)
+
+# ╔═╡ c905b73e-ee1a-11ea-2e36-23b8e73bfdb6
+if !@isdefined(quantize)
+ not_defined(:quantize)
+else
+ let
+ result = quantize(.3)
+
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif result != .3
+ if quantize(0.35) == .3
+ almost(md"What should quantize(`0.2`) be?")
+ else
+ keep_working()
+ end
+ else
+ correct()
+ end
+ end
+end
+
+# ╔═╡ a6d9635b-85ed-4590-ad09-ca2903ea8f1d
+let
+ result = quantize(RGB(.297, .1, .0))
+
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif !(result isa AbstractRGB)
+ keep_working(md"You need to return a _color_, i.e. an object of type `RGB`. Use `RGB(r, g, b)` to create a color with channel values `r`, `g` and `b`.")
+ elseif result != RGB(0.2, .1, .0)
+ keep_working()
+ else
+ correct()
+ end
+end
+
+# ╔═╡ 25dad7ce-ee0b-11ea-3e20-5f3019dd7fa3
+md"Let's apply your method!"
+
+# ╔═╡ 9751586e-ee0c-11ea-0cbb-b7eda92977c9
+quantize(philip)
+
+# ╔═╡ f6d6c71a-ee07-11ea-2b63-d759af80707b
+md"""
+#### Exercise 3.5
+👉 Write a function `noisify(x::Number, s)` to add randomness of intensity $s$ to a value $x$, i.e. to add a random value between $-s$ and $+s$ to $x$. If the result falls outside the range $[0, 1]$ you should "clamp" it to that range. (Julia has a built-in `clamp` function, or you can write your own function.)
+"""
+
+# ╔═╡ f38b198d-39cf-456f-a841-1ba08f206010
+function noisify(x::Number, s)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ f6ef2c2e-ee07-11ea-13a8-2512e7d94426
+hint(md"`rand()` generates a (uniformly) random floating-point number between $0$ and $1$.")
+
+# ╔═╡ f6fc1312-ee07-11ea-39a0-299b67aee3d8
+md"""
+👉 Write the second method `noisify(c::AbstractRGB, s)` to add random noise of intensity $s$ to each of the $(r, g, b)$ values in a colour.
+
+Use your previous method for `noisify`. _(Remember that Julia chooses which method to use based on the input arguments. So to call the method from the previous exercise, the first argument should be a number.)_
+"""
+
+# ╔═╡ db4bad9f-df1c-4640-bb34-dd2fe9bdce18
+function noisify(color::AbstractRGB, s)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 0000b7f8-4c43-4dd8-8665-0dfe59e74c0a
+md"""
+Noise strength:
+"""
+
+# ╔═╡ 774b4ce6-ee1b-11ea-2b48-e38ee25fc89b
+@bind color_noise Slider(0:0.01:1, show_value=true)
+
+# ╔═╡ 48de5bc2-72d3-11eb-3fd9-eff2b686cb75
+md"""
+> ### Note about _array comprehension_
+> At this point, you already know of a few ways to make a new array based on one that already exists.
+> 1. you can use a for loop to go through a array
+> 1. you can use function broadcasting over a array
+> 1. you can use _**array comprehension**_!
+>
+> The third option you are about to see demonstrated below and following the following syntax:
+>
+> ```[function_to_apply(args) for args in some_iterable_of_your_choice]```
+>
+> This creates a new iterable that matches what you iterate through in the second part of the comprehension. Below is an example with `for` loops through two iterables that creates a 2-dimensional `Array`.
+"""
+
+# ╔═╡ f70823d2-ee07-11ea-2bb3-01425212aaf9
+md"""
+👉 Write the third method `noisify(image::AbstractMatrix, s)` to noisify each pixel of an image. This function should be a single line!
+"""
+
+# ╔═╡ 21a5885d-00ab-428b-96c3-c28c98c4ca6d
+function noisify(image::AbstractMatrix, s)
+ # your code here!
+ return missing
+end
+
+# ╔═╡ 1ea53f41-b791-40e2-a0f8-04e13d856829
+noisify(0.5, 0.1) # edit this test case!
+
+# ╔═╡ 31ef3710-e4c9-4aa7-bd8f-c69cc9a977ee
+let
+ result = noisify(0.5, 0)
+
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif result == 0.5
+
+ results = [noisify(0.9, 0.1) for _ in 1:1000]
+
+ if 0.8 ≤ minimum(results) < 0.81 && 0.99 ≤ maximum(results) ≤ 1
+ result = noisify(5, 3)
+
+ if result == 1
+ correct()
+ else
+ keep_working(md"The result should be restricted to the range ``[0,1]``.")
+ end
+ else
+ keep_working()
+ end
+ else
+ keep_working(md"What should `noisify(0.5, 0)` be?")
+ correct()
+ end
+end
+
+# ╔═╡ 7e4aeb70-ee1b-11ea-100f-1952ba66f80f
+(original=color_red, with_noise=noisify(color_red, color_noise))
+
+# ╔═╡ 8e848279-1b3e-4f32-8c0c-45693d12de96
+[
+ noisify(color_red, strength)
+ for
+ strength in 0 : 0.05 : 1,
+ row in 1:10
+]'
+
+# ╔═╡ d896b7fd-20db-4aa9-bbcf-81b1cd44ec46
+md"""
+
+#### Exercise 3.6
+Move the slider below to set the amount of noise applied to the image of Philip.
+"""
+
+# ╔═╡ e70a84d4-ee0c-11ea-0640-bf78653ba102
+@bind philip_noise Slider(0:0.01:1, show_value=true)
+
+# ╔═╡ ac15e0d0-ee0c-11ea-1eaf-d7f88b5df1d7
+noisify(philip_head, philip_noise)
+
+# ╔═╡ 9604bc44-ee1b-11ea-28f8-7f7af8d0cbb2
+if philip_noise == 1
+ md"""
+ > #### What's this?
+ >
+ > The noise intensity is `1.0`, but we can still recognise Philip in the picture...
+ >
+ > 👉 Modify the definition of the slider to go further than `1.0`.
+ """
+end
+
+# ╔═╡ f714699e-ee07-11ea-08b6-5f5169861b57
+md"""
+👉 For which noise intensity does it become unrecognisable?
+
+You may need noise intensities larger than 1. Why?
+
+"""
+
+# ╔═╡ bdc2df7c-ee0c-11ea-2e9f-7d2c085617c1
+answer_about_noise_intensity = md"""
+The image is unrecognisable with intensity ...
+"""
+
+# ╔═╡ 20402780-426b-4caa-af8f-ff1e7787b7f9
+@bind cam_image PlutoUI.WebcamInput(help=false, max_size=200)
+
+# ╔═╡ e87e0d14-43a5-490d-84d9-b14ece472061
+md"""
+### Results
+"""
+
+# ╔═╡ 9dafc1bb-afe1-4de6-abef-05b8c9ab8b1d
+cam_image
+
+# ╔═╡ d38c6958-9300-4f7a-89cf-95ca9e899c13
+mean_color(cam_image)
+
+# ╔═╡ 82f1e006-60fe-4ad1-b9cb-180fafdeb4da
+invert.(cam_image)
+
+# ╔═╡ 54c83589-b8c6-422a-b5e9-d8e0ee72a224
+quantize(cam_image)
+
+# ╔═╡ 18e781f8-66f3-4216-bc84-076a08f9f3fb
+noisify(cam_image, .5)
+
+# ╔═╡ ee5f21fb-1076-42b6-8926-8bbb6ed0ad67
+function custom_filter(pixel::AbstractRGB)
+
+ # your code here!
+
+ return pixel
+end
+
+# ╔═╡ 9e5a08dd-332a-486b-94ab-15c49e72e522
+function custom_filter(image::AbstractMatrix)
+
+ return custom_filter.(image)
+end
+
+# ╔═╡ ebf3193d-8c8d-4425-b252-45067a5851d9
+[
+ invert.(cam_image) quantize(cam_image)
+ noisify(cam_image, .5) custom_filter(cam_image)
+]
+
+# ╔═╡ 8917529e-fa7a-412b-8aea-54f92f6270fa
+custom_filter(cam_image)
+
+# ╔═╡ 115ded8c-ee0a-11ea-3493-89487315feb7
+begin
+ bigbreak = html"
This lecture is part of Computational Thinking, a live online Julia/Pluto textbook. Go to computationalthinking.mit.edu to read all 50 lectures for free.
+
+
+
+
Lecture Video
+
+
+
+
+
+
+
+
+
+"""
+
+# ╔═╡ d07fcdb0-7afc-4a25-b68a-49fd1e3405e7
+PlutoUI.TableOfContents(aside=true)
+
+# ╔═╡ 9b49500c-0164-4556-a17b-7595e35c5ede
+md"""
+#### Initializing packages
+
+_When running this notebook for the first time, this could take up to 15 minutes. Hang in there!_
+"""
+
+# ╔═╡ ca1b507e-6017-11eb-34e6-6b85cd189002
+md"""
+# Images as examples of data all around us
+Welcome to the Computational Thinking using Julia for Real-World Problems at MIT!
+
+The aim of this course is to bring together concepts from computer science and applied math with coding in the modern **Julia language**, and to see how to apply these techniques to study interesting applications (and of course to have fun).
+
+We would be pleased if students who have been interested in computer science now become interested in computational science and those interested in scientific applications learn computer science they may not see elsewhere.
+... and for all students, we wish to share the value of
+the Julia language as the best of both worlds.
+"""
+
+# ╔═╡ e9ff96d8-6bc1-11eb-0f6a-234b9fae047e
+md"""
+
+## Alan's Essay: Are all programming languages the same?
+
+>Superficially, many programming languages are very similar. "Showoffs" will compare functional programming vs imperative programming. Others will compare compiled languages vs dynamic languages. I will avoid such fancy terms in this little essay, preferring to provide this course's pedagogical viewpoint.
+>
+>Generally speaking beginning programmers should learn to create "arrays" write "for loops", "conditionals", "comparisons", express mathematical formulas, etc. So why Julia at a time when Python seems to be the language of teaching, and Java and C++ so prominent in the corporate world?
+>
+>As you might imagine, we believe Julia is special. Oh you will still have the nitty gritty of when to use a bracket and a comma. You might have strong opinions as to whether arrays should begin with 0 or 1 (joke: some say it's time to compromise and use ½.) Getting past these irrelevant issues, you will begin to experience one by one what makes Julia so very special. For starters, a language that runs fast is more fun. We can have you try things that would just be so slow in other languages it would be boring. We also think you will start to notice how natural Julia is, how it feels like the mathematics, and how flexible it can be.
+>
+>Getting to see the true value of fancy terms like multiple dispatch, strong typing, generic programming, and composable software will take a little longer, but stick with us, and you too will see why Julia is so very special.
+"""
+
+# ╔═╡ 9111db10-6bc3-11eb-38e5-cf3f58536914
+md"""
+## Computer Science and Computational Science Working Together
+"""
+
+# ╔═╡ fb8a99ac-6bc1-11eb-0835-3146734a1c99
+md"""
+Applications of computer science in the real world use **data**, i.e. information that we can **measure** in some way. Data take many different forms, for example:
+
+- Numbers that change over time (**time series**):
+ - Stock price each second / minute / day
+ - Weekly number of infections
+ - Earth's global average temperature
+
+- Video:
+ - The view from a window of a self-driving car
+ - A hurricane monitoring station
+ - Ultrasound e.g. prenatal
+
+- Images:
+ - Diseased versus healthy tissue in a medical scan
+ - Pictures of your favourite dog
+"""
+
+# ╔═╡ b795dcb4-6bc3-11eb-20ec-db2cc4b89bfb
+md"""
+#### Exercise:
+> Think of another two examples in each category. Can you think of other categories of data?
+"""
+
+# ╔═╡ 8691e434-6bc4-11eb-07d1-8169158484e6
+md"""
+Computational science can be summed up by a simplified workflow:
+"""
+
+# ╔═╡ 546db74c-6d4e-11eb-2e27-f5bed9dbd9ba
+md"""
+## data ⟶ input ⟶ process ⟶ model ⟶ visualize ⟶ output
+"""
+
+# ╔═╡ 6385d174-6d4e-11eb-093b-6f6fafb79f84
+md"""
+$(html" ")
+To use any data source, we need to **input** the data of interest, for example by downloading it, reading in the resulting file, and converting it into a form that we can use in the computer. Then we need to **process** it in some way to extract information of interest. We usually want to **visualize** the results, and we may want to **output** them, for example by saving to disc or putting them on a website.
+
+We often want to make a mathematical or computational **model** that can help us to understand and predict the behavior of the system of interest.
+
+> In this course we aim to show how programming, computer science and applied math combine to help us with these goals.
+"""
+
+# ╔═╡ 132f6596-6bc6-11eb-29f1-1b2478c929af
+md"""
+# Data: Images (as an example of data)
+Let's start off by looking at **images** and how we can process them.
+Our goal is to process the data contained in an image in some way, which we will do by developing and coding certain **algorithms**.
+
+Here is the the Fall 2020 version of this lecture (small variations) by 3-Blue-1-Brown (Grant Sanderson) for your reference.
+"""
+
+# ╔═╡ e1bd938e-d067-4854-b5da-9aa71023d8a1
+html"""
+
+
+
+
+"""
+
+# ╔═╡ 9eb6efd2-6018-11eb-2db8-c3ce41d9e337
+md"""
+
+
+If we open an image on our computer or the web and zoom in enough, we will see that it consists of many tiny squares, or **pixels** ("picture elements"). Each pixel is a block of one single colour, and the pixels are arranged in a two-dimensional square grid.
+
+You probably already know that these pixels are stored in a computer numerically
+perhaps in some form of RGB (red,green,blue) format. This is the computer's representation of the data.
+
+Note that an image is already an **approximation** of the real world -- it is a two-dimensional, discrete representation of a three-dimensional reality.
+
+"""
+
+# ╔═╡ e37e4d40-6018-11eb-3e1d-093266c98507
+md"""
+# Input and Visualize: loading and viewing an Image (in Julia)
+"""
+
+# ╔═╡ e1c9742a-6018-11eb-23ba-d974e57f78f9
+md"""
+Let's use Julia to load actual images and play around with them. We can download images from the internet, your own file, or your own webcam.
+"""
+
+# ╔═╡ 9b004f70-6bc9-11eb-128c-914eadfc9a0e
+md"""
+## Downloading an image from the internet or a local file
+We can use the `Images.jl` package to load an image file in three steps.
+"""
+
+# ╔═╡ 62fa19da-64c6-11eb-0038-5d40a6890cf5
+md"""
+Step 1: (from internet) we specify the URL (web address) to download from:
+$(html" ")
+(note that Pluto places results before commands because some people believe
+output is more interesting than code. This takes some getting used to.)
+"""
+
+# ╔═╡ 34ee0954-601e-11eb-1912-97dc2937fd52
+url = "https://user-images.githubusercontent.com/6933510/107239146-dcc3fd00-6a28-11eb-8c7b-41aaf6618935.png"
+
+# ╔═╡ 9180fbcc-601e-11eb-0c22-c920dc7ee9a9
+md"""
+Step 2: Now we use the aptly-named `download` function to download the image file to our own computer. (Philip is Prof. Edelman's corgi.)
+"""
+
+# ╔═╡ 34ffc3d8-601e-11eb-161c-6f9a07c5fd78
+philip_filename = download(url) # download to a local file. The filename is returned
+
+# ╔═╡ abaaa980-601e-11eb-0f71-8ff02269b775
+md"""
+Step 3:
+Using the `Images.jl` package (loaded at the start of this notebook; scroll up and take a look.) we can **load** the file, which automatically converts it into usable data. We'll store the result in a variable. (Remember the code is after the output.)
+"""
+
+# ╔═╡ aafe76a6-601e-11eb-1ff5-01885c5238da
+philip = load(philip_filename)
+
+# ╔═╡ e86ed944-ee05-11ea-3e0f-d70fc73b789c
+md"_Hi there Philip_"
+
+# ╔═╡ c99d2aa8-601e-11eb-3469-497a246db17c
+md"""
+We see that the Pluto notebook has recognised that we created an object representing an image, and automatically displayed the resulting image of Philip, the cute Welsh Pembroke corgi and co-professor of this course.
+Poor Philip will undergo quite a few transformations as we go along!
+"""
+
+# ╔═╡ 11dff4ce-6bca-11eb-1056-c1345c796ed4
+md"""
+👉 **Exercise:** change the url to use another image from the web.
+"""
+
+# ╔═╡ efef3a32-6bc9-11eb-17e9-dd2171be9c21
+md"""
+## Capturing an Image from your own camera
+"""
+
+# ╔═╡ e94dcc62-6d4e-11eb-3d53-ff9878f0091e
+md"""
+Even more fun is to use your own webcam. Try pressing the enable button below. Then
+press the camera to capture an image. Kind of fun to keep pressing the button as you move your hand etc.
+"""
+
+# ╔═╡ bd0e4cfc-72bb-43c1-8178-63872f859fab
+@bind myface1 PlutoUI.WebcamInput(help=false, max_size=150)
+
+# ╔═╡ 6224c74b-8915-4983-abf0-30e6ba04a46d
+[
+ myface1 myface1[ : , end:-1:1]
+ myface1[end:-1:1, :] myface1[end:-1:1, end:-1:1]
+]
+
+# ╔═╡ cef1a95a-64c6-11eb-15e7-636a3621d727
+md"""
+## Inspecting your data
+"""
+
+# ╔═╡ f26d9326-64c6-11eb-1166-5d82586422ed
+md"""
+### Image size
+"""
+
+# ╔═╡ 6f928b30-602c-11eb-1033-71d442feff93
+md"""
+The first thing we might want to know is the size of the image:
+"""
+
+# ╔═╡ 75c5c85a-602c-11eb-2fb1-f7e7f2c5d04b
+philip_size = size(philip)
+
+# ╔═╡ 77f93eb8-602c-11eb-1f38-efa56cc93ca5
+md"""
+Julia returns a pair of two numbers. Comparing these with the picture of the image, we see that the first number is the height, i.e. the vertical number of pixels, and the second is the width.
+"""
+
+# ╔═╡ 96b7d801-c427-4e27-ab1f-e2fd18fc24d0
+philip_height = philip_size[1]
+
+# ╔═╡ f08d02af-6e38-4ace-8b11-7af4930b64ea
+philip_width = philip_size[2]
+
+# ╔═╡ f9244264-64c6-11eb-23a6-cfa76f8aff6d
+md"""
+### Locations in an image: Indexing
+
+Now suppose that we want to examine a piece of the image in more detail. We need some way of specifying which piece of the image we want.
+
+Thinking of the image as a grid of pixels, we need a way to tell the computer which pixel or group of pixels we want to refer to.
+Since the image is a two-dimensional grid, we can use two integers (whole numbers) to give the coordinates of a single pixel. Specifying coordinates like this is called **indexing**: think of the index of a book, which tells you *on which page* an idea is discussed.
+
+In Julia we use (square) brackets, `[` and `]` for indexing:
+"""
+
+# ╔═╡ bd22d09a-64c7-11eb-146f-67733b8be241
+a_pixel = philip[200, 100]
+
+# ╔═╡ 28860d48-64c8-11eb-240f-e1232b3638df
+md"""
+We see that Julia knows to draw our pixel object for us a block of the relevant color.
+
+When we index into an image like this, the first number indicates the *row* in the image, starting from the top, and the second the *column*, starting from the left. In Julia, the first row and column are numbered starting from 1, not from 0 as in some other programming languages.
+"""
+
+# ╔═╡ 4ef99715-4d8d-4f9d-bf0b-8df9907a14cf
+
+
+# ╔═╡ a510fc33-406e-4fb5-be83-9e4b5578717c
+md"""
+We can also use variables as indices...
+"""
+
+# ╔═╡ 13844ebf-52c4-47e9-bda4-106a02fad9d7
+md"""
+...and these variables can be controlled by sliders!
+"""
+
+# ╔═╡ 08d61afb-c641-4aa9-b995-2552af89f3b8
+@bind row_i Slider(1:size(philip)[1], show_value=true)
+
+# ╔═╡ 6511a498-7ac9-445b-9c15-ec02d09783fe
+@bind col_i Slider(1:size(philip)[2], show_value=true)
+
+# ╔═╡ 94b77934-713e-11eb-18cf-c5dc5e7afc5b
+row_i,col_i
+
+# ╔═╡ ff762861-b186-4eb0-9582-0ce66ca10f60
+philip[row_i, col_i]
+
+# ╔═╡ c9ed950c-dcd9-4296-a431-ee0f36d5b557
+md"""
+### Locations in an image: Range indexing
+
+We saw that we can use the **row number** and **column number** to index a _single pixel_ of our image. Next, we will use a **range of numbers** to index _multiple rows or columns_ at once, returning a subarray:
+"""
+
+# ╔═╡ f0796032-8105-4f6d-b5ee-3647b052f2f6
+philip[550:650, 1:philip_width]
+
+# ╔═╡ b9be8761-a9c9-49eb-ba1b-527d12097362
+md"""
+Here, we use `a:b` to mean "_all numbers between `a` and `b`_". For example:
+
+"""
+
+# ╔═╡ d515286b-4ad4-449b-8967-06b9b4c87684
+collect(1:10)
+
+# ╔═╡ eef8fbc8-8887-4628-8ba8-114575d6b91f
+md"""
+
+You can also use a `:` without start and end to mean "_every index_"
+"""
+
+# ╔═╡ 4e6a31d6-1ef8-4a69-b346-ad58cfc4d8a5
+philip[550:650, :]
+
+# ╔═╡ e11f0e47-02d9-48a6-9b1a-e313c18db129
+md"""
+Let's get a single row of pixels:
+"""
+
+# ╔═╡ 9e447eab-14b6-45d8-83ab-1f7f1f1c70d2
+philip[550, :]
+
+# ╔═╡ c926435c-c648-419c-9951-ac8a1d4f3b92
+philip_head = philip[470:800, 140:410]
+
+# ╔═╡ 32e7e51c-dd0d-483d-95cb-e6043f2b2975
+md"""
+#### Scroll in on Philip's nose!
+
+Use the widgets below (slide left and right sides).
+"""
+
+# ╔═╡ 4b64e1f2-d0ca-4e22-a89d-1d9a16bd6788
+@bind range_rows RangeSlider(1:size(philip_head)[1])
+
+# ╔═╡ 85919db9-1444-4904-930f-ba572cff9460
+@bind range_cols RangeSlider(1:size(philip_head)[2])
+
+# ╔═╡ 2ac47b91-bbc3-49ae-9bf5-4def30ff46f4
+nose = philip_head[range_rows, range_cols]
+
+# ╔═╡ 5a0cc342-64c9-11eb-1211-f1b06d652497
+md"""
+# Process: Modifying an image
+
+Now that we have access to image data, we can start to **process** that data to extract information and/or modify it in some way.
+
+We might want to detect what type of objects are in the image, say to detect whether a patient has a certain disease. To achieve a high-level goal like this, we will need to perform mid-level operations, such as detecting edges that separate different objects based on their color. And, in turn, to carry that out we will need to do low-level operations like comparing colors of neighboring pixels and somehow deciding if they are "different".
+
+"""
+
+# ╔═╡ 4504577c-64c8-11eb-343b-3369b6d10d8b
+md"""
+## Representing colors
+
+We can use indexing to *modify* a pixel's color. To do so, we need a way to specify a new color.
+
+Color turns out to be a complicated concept, having to do with the interaction of the physical properties of light with the physiological mechanisms and mental processes by which we detect it!
+
+We will ignore this complexity by using a standard method of representing colours in the computer as an **RGB triple**, i.e. a triple of three numbers $(r, g, b)$, giving the amount of red, of green and of blue in a colour, respectively. These are numbers between 0 (none) and 1 (full). The final colour that we perceive is the result of "adding" the corresponding amount of light of each colour; the details are fascinating, but beyond the scope of this course!
+"""
+
+# ╔═╡ 40886d36-64c9-11eb-3c69-4b68673a6dde
+md"""
+We can create a new color in Julia as follows:
+"""
+
+# ╔═╡ 552235ec-64c9-11eb-1f7f-f76da2818cb3
+RGB(1.0, 0.0, 0.0)
+
+# ╔═╡ c2907d1a-47b1-4634-8669-a68022706861
+begin
+ md"""
+ A pixel with $(@bind test_r Scrubbable(0:0.1:1; default=0.1)) red, $(@bind test_g Scrubbable(0:0.1:1; default=0.5)) green and $(@bind test_b Scrubbable(0:0.1:1; default=1.0)) blue looks like:
+ """
+end
+
+
+# ╔═╡ ff9eea3f-cab0-4030-8337-f519b94316c5
+RGB(test_r, test_g, test_b)
+
+# ╔═╡ f6cc03a0-ee07-11ea-17d8-013991514d42
+md"""
+#### Exercise 2.5
+👉 Write a function `invert` that inverts a color, i.e. sends $(r, g, b)$ to $(1 - r, 1-g, 1-b)$.
+"""
+
+# ╔═╡ 63e8d636-ee0b-11ea-173d-bd3327347d55
+function invert(color::AbstractRGB)
+
+ return missing
+end
+
+# ╔═╡ 2cc2f84e-ee0d-11ea-373b-e7ad3204bb00
+md"Let's invert some colors:"
+
+# ╔═╡ b8f26960-ee0a-11ea-05b9-3f4bc1099050
+color_black = RGB(0.0, 0.0, 0.0)
+
+# ╔═╡ 5de3a22e-ee0b-11ea-230f-35df4ca3c96d
+invert(color_black)
+
+# ╔═╡ 4e21e0c4-ee0b-11ea-3d65-b311ae3f98e9
+color_red = RGB(0.8, 0.1, 0.1)
+
+# ╔═╡ 6dbf67ce-ee0b-11ea-3b71-abc05a64dc43
+invert(color_red)
+
+# ╔═╡ 846b1330-ee0b-11ea-3579-7d90fafd7290
+md"Can you invert the picture of Philip?"
+
+# ╔═╡ 943103e2-ee0b-11ea-33aa-75a8a1529931
+philip_inverted = missing
+
+# ╔═╡ 2ee543b2-64d6-11eb-3c39-c5660141787e
+md"""
+
+## Modifying a pixel
+
+Let's start by seeing how to modify an image, e.g. in order to hide sensitive information.
+
+We do this by assigning a new value to the color of a pixel:
+"""
+
+# ╔═╡ 53bad296-4c7b-471f-b481-0e9423a9288a
+let
+ temp = copy(philip_head)
+ temp[100, 200] = RGB(1.0, 0.0, 0.0)
+ temp
+end
+
+# ╔═╡ ab9af0f6-64c9-11eb-13d3-5dbdb75a69a7
+md"""
+## Groups of pixels
+
+We probably want to examine and modify several pixels at once.
+For example, we can extract a horizontal strip 1 pixel tall:
+"""
+
+# ╔═╡ e29b7954-64cb-11eb-2768-47de07766055
+philip_head[50, 50:100]
+
+# ╔═╡ 8e7c4866-64cc-11eb-0457-85be566a8966
+md"""
+Here, Julia is showing the strip as a collection of rectangles in a row.
+
+
+"""
+
+# ╔═╡ f2ad501a-64cb-11eb-1707-3365d05b300a
+md"""
+And then modify it:
+"""
+
+# ╔═╡ 4f03f651-56ed-4361-b954-e6848ac56089
+let
+ temp = copy(philip_head)
+ temp[50, 50:100] .= RGB(1.0, 0.0, 0.0)
+ temp
+end
+
+# ╔═╡ 2808339c-64cc-11eb-21d1-c76a9854aa5b
+md"""
+Similarly we can modify a whole rectangular block of pixels:
+"""
+
+# ╔═╡ 1bd53326-d705-4d1a-bf8f-5d7f2a4e696f
+let
+ temp = copy(philip_head)
+ temp[50:100, 50:100] .= RGB(1.0, 0.0, 0.0)
+ temp
+end
+
+# ╔═╡ a5f8bafe-edf0-11ea-0da3-3330861ae43a
+md"""
+#### Exercise 1.2
+
+👉 Generate a vector of 100 zeros. Change the center 20 elements to 1.
+"""
+
+# ╔═╡ b6b65b94-edf0-11ea-3686-fbff0ff53d08
+function create_bar()
+
+ return missing
+end
+
+# ╔═╡ e3394c8a-edf0-11ea-1bb8-619f7abb6881
+if !@isdefined(create_bar)
+ not_defined(:create_bar)
+else
+ let
+ result = create_bar()
+ if ismissing(result)
+ still_missing()
+ elseif isnothing(result)
+ keep_working(md"Did you forget to write `return`?")
+ elseif !(result isa Vector) || length(result) != 100
+ keep_working(md"The result should be a `Vector` with 100 elements.")
+ elseif result[[1,50,100]] != [0,1,0]
+ keep_working()
+ else
+ correct()
+ end
+ end
+end
+
+# ╔═╡ 693af19c-64cc-11eb-31f3-57ab2fbae597
+md"""
+## Reducing the size of an image
+"""
+
+# ╔═╡ 6361d102-64cc-11eb-31b7-fb631b632040
+md"""
+Maybe we would also like to reduce the size of this image, since it's rather large. For example, we could take every 10th row and every 10th column and make a new image from the result:
+"""
+
+# ╔═╡ ae542fe4-64cc-11eb-29fc-73b7a66314a9
+reduced_image = philip[1:10:end, 1:10:end]
+
+# ╔═╡ c29292b8-64cc-11eb-28db-b52c46e865e6
+md"""
+Note that the resulting image doesn't look very good, since we seem to have lost too much detail.
+
+#### Exercise
+
+> Think about what we might do to reduce the size of an image without losing so much detail.
+"""
+
+# ╔═╡ 7b04331a-6bcb-11eb-34fa-1f5b151e5510
+md"""
+# Model: Creating synthetic images
+
+Think about your favorite Pixar movie (e.g. Monsters Inc.) Movie frames are images that are generated from complicated mathematical models. Ray tracing (which may be covered in this class)
+is a method for making images feel realistic.
+"""
+
+# ╔═╡ 5319c03c-64cc-11eb-0743-a1612476e2d3
+md"""
+# Output: Saving an image to a file
+
+Finally, we want to be able to save our new creation to a file. To do so, you can **right click** on a displayed image, or you can write it to a file. Fill in a path below:
+"""
+
+# ╔═╡ 3db09d92-64cc-11eb-0333-45193c0fd1fe
+save("reduced_phil.png", reduced_image)
+
+# ╔═╡ 61606acc-6bcc-11eb-2c80-69ceec9f9702
+md"""
+# $(html" ")
+"""
+
+# ╔═╡ dd183eca-6018-11eb-2a83-2fcaeea62942
+md"""
+# Computer science: Arrays
+
+An image is a concrete example of a fundamental concept in computer science, namely an **array**.
+
+Just as an image is a rectangular grid, where each grid cell contains a single color,
+an array is a rectangular grid for storing data. Data is stored and retrieved using indexing, just as in the image examples: each cell in the grid can store a single "piece of data" of a given type.
+
+
+## Dimension of an array
+
+An array can be one-dimensional, like the strip of pixels above, two-dimensional, three-dimensional, and so on. The dimension tells us the number of indices that we need to specify a unique location in the grid.
+The array object also needs to know the length of the data in each dimension.
+
+## Names for different types of array
+
+One-dimensional arrays are often called **vectors** (or, in some other languages, "lists") and two-dimensional arrays are **matrices**. Higher-dimensional arrays are **tensors**.
+
+
+## Arrays as data structures
+
+An array is an example of a **data structure**, i.e. a way of arranging data such that we can access it. A key theme in computer science is that of designing different data structures that represent data in different ways.
+
+Conceptually, we can think of an array as a block of data that has a position or location in space. This can be a useful way to arrange data if, for example, we want to represent the fact that values in nearby locations in array are somehow near to one another.
+
+Images are a good example of this: neighbouring pixels often represent different pieces of the same object, for example the rug or floor, or Philip himself, in the photo. We thus expect neighbouring pixels to be of a similar color. On the other hand, if they are not, this is also useful information, since that may correspond to the edge of an object.
+
+"""
+
+# ╔═╡ 8ddcb286-602a-11eb-3ae0-07d3c77a0f8c
+md"""
+# Julia: constructing arrays
+
+## Creating vectors and matrices
+Julia has strong support for arrays of any dimension.
+
+Vectors, or one-dimensional arrays, are written using square brackets and commas:
+"""
+
+# ╔═╡ f4b0aa23-2d76-4d88-b2a4-3807e88d27ce
+[1, 20, "hello"]
+
+# ╔═╡ 1b2b2b18-64d4-11eb-2d43-e31cb8bc25d1
+[RGB(1, 0, 0), RGB(0, 1, 0), RGB(0, 0, 1)]
+
+# ╔═╡ 2b0e6450-64d4-11eb-182b-ff1bd515b56f
+md"""
+Matrices, or two-dimensional arrays, also use square brackets, but with spaces and new lines instead of commas:
+"""
+
+# ╔═╡ 3b2b041a-64d4-11eb-31dd-47d7321ee909
+[RGB(1, 0, 0) RGB(0, 1, 0)
+ RGB(0, 0, 1) RGB(0.5, 0.5, 0.5)]
+
+# ╔═╡ 0f35603a-64d4-11eb-3baf-4fef06d82daa
+md"""
+
+## Array comprehensions
+
+It's clear that if we want to create an array with more than a few elements, it will be *very* tedious to do so by hand like this.
+Rather, we want to *automate* the process of creating an array by following some pattern, for example to create a whole palette of colors!
+
+Let's start with all the possible colors interpolating between black, `RGB(0, 0, 0)`, and red, `RGB(1, 0, 0)`. Since only one of the values is changing, we can represent this as a vector, i.e. a one-dimensional array.
+
+A neat method to do this is an **array comprehension**. Again we use square brackets to create an array, but now we use a **variable** that varies over a given **range** values:
+"""
+
+# ╔═╡ e69b02c6-64d6-11eb-02f1-21c4fb5d1043
+[RGB(x, 0, 0) for x in 0:0.1:1]
+
+# ╔═╡ fce76132-64d6-11eb-259d-b130038bbae6
+md"""
+Here, `0:0.1:1` is a **range**; the first and last numbers are the start and end values, and the middle number is the size of the step.
+"""
+
+# ╔═╡ 17a69736-64d7-11eb-2c6c-eb5ebf51b285
+md"""
+In a similar way we can create two-dimensional matrices, by separating the two variables for each dimension with a comma (`,`):
+"""
+
+# ╔═╡ 291b04de-64d7-11eb-1ee0-d998dccb998c
+[RGB(i, j, 0) for i in 0:0.1:1, j in 0:0.1:1]
+
+# ╔═╡ 647fddf2-60ee-11eb-124d-5356c7014c3b
+md"""
+## Joining matrices
+
+We often want to join vectors and matrices together. We can do so using an extension of the array creation syntax:
+"""
+
+# ╔═╡ 7d9ad134-60ee-11eb-1b2a-a7d63f3a7a2d
+[philip_head philip_head]
+
+# ╔═╡ 8433b862-60ee-11eb-0cfc-add2b72997dc
+[philip_head reverse(philip_head, dims=2)
+ reverse(philip_head, dims=1) rot180(philip_head)]
+
+# ╔═╡ 5e52d12e-64d7-11eb-0905-c9038a404e24
+md"""
+# Pluto: Interactivity using sliders
+"""
+
+# ╔═╡ 6aba7e62-64d7-11eb-2c49-7944e9e2b94b
+md"""
+Suppose we want to see the effect of changing the number of colors in our vector or matrix. We could, of course, do so by manually fiddling with the range.
+
+It would be nice if we could do so using a **user interface**, for example with a **slider**. Fortunately, the Pluto notebook allows us to do so!
+"""
+
+# ╔═╡ afc66dac-64d7-11eb-1ad0-7f62c20ffefb
+md"""
+We can define a slider using
+"""
+
+# ╔═╡ b37c9868-64d7-11eb-3033-a7b5d3065f7f
+@bind number_reds Slider(1:100, show_value=true)
+
+# ╔═╡ b1dfe122-64dc-11eb-1104-1b8852b2c4c5
+md"""
+[The `Slider` type is defined in the `PlutoUI.jl` package.]
+"""
+
+# ╔═╡ cfc55140-64d7-11eb-0ff6-e59c70d01d67
+md"""
+This creates a new variable called `number_reds`, whose value is the value shown by the slider. When we move the slider, the value of the variable gets updated. Since Pluto is a **reactive** notebook, other cells which use the value of this variable will *automatically be updated too*!
+"""
+
+# ╔═╡ fca72490-64d7-11eb-1464-f5e0582c4d18
+md"""
+Let's use this to make a slider for our one-dimensional collection of reds:
+"""
+
+# ╔═╡ 88933746-6028-11eb-32de-13eb6ff43e29
+[RGB(red_value / number_reds, 0, 0) for red_value in 0:number_reds]
+
+# ╔═╡ 1c539b02-64d8-11eb-3505-c9288357d139
+md"""
+When you move the slider, you should see the number of red color patches change!
+"""
+
+# ╔═╡ 10f6e6da-64d8-11eb-366f-11f16e73043b
+md"""
+What is going on here is that we are creating a vector in which `red_value` takes each value in turn from the range from `0` up to the current value of `number_reds`. If we change `number_reds`, then we create a new vector with that new number of red patches.
+"""
+
+# ╔═╡ 82a8314c-64d8-11eb-1acb-e33625381178
+md"""
+#### Exercise
+
+> Make three sliders with variables `r`, `g` and `b`. Then make a single color patch with the RGB color given by those values.
+"""
+
+# ╔═╡ 576d5e3a-64d8-11eb-10c9-876be31f7830
+md"""
+We can do the same to create different size matrices, by creating two sliders, one for reds and one for greens. Try it out!
+"""
+
+# ╔═╡ ace86c8a-60ee-11eb-34ef-93c54abc7b1a
+md"""
+# Summary
+"""
+
+# ╔═╡ b08e57e4-60ee-11eb-0e1a-2f49c496668b
+md"""
+Let's summarize the main ideas from this notebook:
+- Images are **arrays** of colors
+- We can inspect and modify arrays using **indexing**
+- We can create arrays directly or using **array comprehensions**
+"""
+
+# ╔═╡ 5da8cbe8-eded-11ea-2e43-c5b7cc71e133
+begin
+ colored_line(x::Vector{<:Real}) = Gray.(Float64.((hcat(x)')))
+ colored_line(x::Any) = nothing
+end
+
+# ╔═╡ d862fb16-edf1-11ea-36ec-615d521e6bc0
+colored_line(create_bar())
+
+# ╔═╡ e0a6031c-601b-11eb-27a5-65140dd92897
+bigbreak = html"
This lecture is part of Computational Thinking, a live online Julia/Pluto textbook. Go to computationalthinking.mit.edu to read all 50 lectures for free.