To be able to edit code and run cells, you need to run the notebook yourself. Where would you like to run the notebook?

This notebook takes about 30 seconds to run.

In the cloud (experimental)

Binder is a free, open source service that runs scientific notebooks in the cloud! It will take a while, usually 2-7 minutes to get a session.

On your computer

(Recommended if you want to store your changes.)

  1. Copy the notebook URL:
  2. Run Pluto

    (Also see: How to install Julia and Pluto)

  3. Paste URL in the Open box

Frontmatter

If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and social media.

Author 1
0.4
👀 Reading hidden code
base = .4
10.7 μs
false
👀 Reading hidden code
isapprox(1.619, 1.618, atol=1e-4)
9.9 ms
false
👀 Reading hidden code
filter(i -> n % i == 0, 1:n) == [1,n]
28.9 μs
1
👀 Reading hidden code
n = 1
11.3 μs
10
👀 Reading hidden code
N = 10
10.7 μs
👀 Reading hidden code
fib = let
fib = ones(N)
for i3:N
fib[i] = fib[i-1] + fib[i-2]
end
end
42.0 μs
👀 Reading hidden code
accumulate(1:10, init=(1, 1)) do pair, _
(pair[2], pair[1] + pair[2])
end
79.5 ms
plot(fib[2:end] ./ fib[1:end-1])
👀 Reading hidden code
3.2 s

👀 Reading hidden code
66.6 μs
fibonacci (generic function with 1 method)
fibonacci(n) = if n <= 2
1
else
fibonacci(n - 1) + fibonacci(n - 2)
end
👀 Reading hidden code
548 μs
1.6180339631667064
fibonacci(20) / fibonacci(19)
👀 Reading hidden code
50.8 μs
1.618033988749895
ϕ = 1 - (1 - sqrt(5))/2
👀 Reading hidden code
18.0 μs

👀 Reading hidden code
73.7 μs
0.05
h = .05
👀 Reading hidden code
11.4 μs
begin
points = [[1.0, 0.0]]
for i in 1:100
previous = last(points)
next = [previous[1] - previous[2] * h, previous[2] + previous[1] * h]
push!(points, next)
end
points
end
👀 Reading hidden code
56.2 μs
let
plot(first.(points))
plot!(last.(points))
end
👀 Reading hidden code
77.5 ms
Error message

UndefVarError: esult not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. esult
esult
👀 Reading hidden code
---
using Plots
👀 Reading hidden code
3.4 s
Error message

UndefVarError: esult not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. 	t = -0.9:0.1:0.9	plot(t, esult)	plot!(t, exp)
begin
t = -0.9:0.1:0.9
plot(t, esult)
plot!(t, exp)
end
👀 Reading hidden code
---
f (generic function with 1 method)
function f(x)
sqrt(X)
end
👀 Reading hidden code
350 μs

Singular Value Decomposition

Lorem ipsum SVD est.

👀 Reading hidden code
208 μs
0×0 Matrix{Float64}
zeros(Float64, 0, 0)
👀 Reading hidden code
8.7 ms

Step 1: upload your favorite image:

👀 Reading hidden code
183 μs
struct ImageInput
use_camera::Bool
default_url::AbstractString
maxsize::Integer
end
👀 Reading hidden code
1.4 ms
img = ImageInput(true, "asdf", 200)
👀 Reading hidden code
14.1 μs
ph = """
<span class="pl-image">
<style>

.pl-image video {
max-width: 250px;
}

.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" title="Click to take a picture" style="position: fixed; top: 0; right: 0; z-index: 100;">
<video playsinline autoplay></video>
</div>

<script>
// mostly taken from https://github.com/fonsp/printi-static
// (by the same author)

const span = this.currentScript.parentElement
const video = span.querySelector("video")
const img = html`<img crossOrigin="anonymous">`

const maxsize = $(img.maxsize)

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"))
}


navigator.mediaDevices.getUserMedia({
audio: false,
video: {
facingMode: "environment",
},
}).then(function(stream) {

window.stream = stream
video.srcObject = stream
window.cameraConnected = true
video.controls = false
video.play()
video.controls = false

}).catch(function(error) {
console.log(error)
});

var sending= false

span.querySelector("#video-container").onclick = function() {
sending = !sending
}

setInterval(() => {
if(sending){

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)
}
}, 50)

</script>
</span>
""" |> HTML;
👀 Reading hidden code
2.3 ms

The (black-and-white) image data is now available as a Julia 2D array:

👀 Reading hidden code
170 μs
extract_img_data(upload_data) = let
# every 4th byte is the Red pixel value
reds = UInt8.(upload_data["data"][1:4:end])
# shuffle and flip to get it in the right shape
( reshape(reds, (upload_data["width"], upload_data["height"]))') / 255.0
end;
👀 Reading hidden code
705 μs
Error message

MethodError: no method matching getindex(::Missing, ::String)

Stack trace

Here is what happened, the most recent locations are first:

  1. extract_img_data(upload_data::Missing)
    extract_img_data(upload_data) = let	# every 4th byte is the Red pixel value	reds = UInt8.(upload_data["data"][1:4:end])	# shuffle and flip to get it in the right shape	( reshape(reds, (upload_data["width"], upload_data["height"]))') / 255.0
  2. Show more...
BWImage(extract_img_data(upload_data))
👀 Reading hidden code
---

This notebook defines a type BWImage that can be used to turn 2D Float arrays into a picture!

👀 Reading hidden code
199 μs

You can use img_data like any other Julia 2D array! For example, here is the top left corner of your image:

👀 Reading hidden code
178 μs

Step 2: running the SVD

The Julia standard library package LinearAlgebra contains a method to compute the SVD.

👀 Reading hidden code
232 μs
using LinearAlgebra
👀 Reading hidden code
160 μs
#📚 = svd(img_data);
👀 Reading hidden code
8.1 μs

Let's look at the result.

👀 Reading hidden code
175 μs
#[📚.U, 📚.S, 📚.V]
👀 Reading hidden code
8.6 μs

Let's verify the identity

A=UΣV

👀 Reading hidden code
196 μs

Are they equal?

👀 Reading hidden code
167 μs

It looks like they are not equal - how come?

Since we are using a computer, the decomposition and multiplication both introduce some numerical errors. So instead of checking whether the reconstructed matrix is equal to the original, we can check how close they are to each other.

👀 Reading hidden code
372 μs

One way to quantify the distance between two matrices is to look at the point-wise difference. If the sum of all differences is close to 0, the matrices are almost equal.

👀 Reading hidden code
286 μs

There are other ways to compare two matrices, such methods are called matrix norms.

👀 Reading hidden code
259 μs

The 👀-norm

👀 Reading hidden code
162 μs

Another popular matrix norm is the 👀-norm: you turn both matrices into a picture, and use your 👀 to see how close they are:

👀 Reading hidden code
245 μs

How similar are these images?

👀 Reading hidden code
215 ms
missing
👀_dist
👀 Reading hidden code
10.6 μs

In some applications, like image compression, this is the most imporant norm.

👀 Reading hidden code
292 μs

Step 3: compression

👀 Reading hidden code
158 μs

Blabla

👀 Reading hidden code
164 μs
Error message

UndefVarError: img_data not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. BWImage(img_data)
BWImage(img_data)
👀 Reading hidden code
---
@bind upload_data ph
👀 Reading hidden code
589 μs
@bind keep HTML("<input type='range' max='50' value='10'>")
👀 Reading hidden code
647 μs

Showing the first missing singular pairs.

👀 Reading hidden code
1.6 ms
Error message

UndefVarError: 📚 not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. BWImage(	📚.U[:,1:keep] * 
BWImage(
📚.U[:,1:keep] *
Diagonal(📚.S[1:keep]) *
📚.V'[1:keep,:]
)
👀 Reading hidden code
---

Store fewer bytes

👀 Reading hidden code
160 μs
#compressed_size(keep), uncompressed_size()
👀 Reading hidden code
7.9 μs
#BWImage(Float16.(F.U)[:,1:keep] * Diagonal(Float16.(F.S[1:keep])) * Float16.(F.V)'[1:keep,:])
👀 Reading hidden code
8.7 μs

JPEG works in a similar way

👀 Reading hidden code
190 μs

Individual pairs

👀 Reading hidden code
167 μs

Going further

More stuff to learn about SVD

To keep things simple (and dependency-free), this notebook only works with downscaled black-and-white images that you pick using the button. For color, larger images, or images from your disk, you should look into the Images.jl package!

👀 Reading hidden code
411 μs
show (generic function with 381 methods)
begin
struct BWImage
data::Array{UInt8, 2}
end
function BWImage(data::Array{T, 2}) where T <: AbstractFloat
BWImage(floor.(UInt8, clamp.(data * 255, 0, 255)))
end
import Base: show
function show(io::IO, ::MIME"image/bmp", i::BWImage)

height, width = size(i.data)
datawidth = Integer(ceil(width / 4)) * 4

bmp_header_size = 14
dib_header_size = 40
palette_size = 256 * 4
data_size = datawidth * height * 1

# BMP header
write(io, 0x42, 0x4d)
write(io, UInt32(bmp_header_size + dib_header_size + palette_size + data_size))
write(io, 0x00, 0x00)
write(io, 0x00, 0x00)
write(io, UInt32(bmp_header_size + dib_header_size + palette_size))

# DIB header
write(io, UInt32(dib_header_size))
write(io, Int32(width))
write(io, Int32(-height))
write(io, UInt16(1))
write(io, UInt16(8))
write(io, UInt32(0))
write(io, UInt32(0))
write(io, 0x12, 0x0b, 0x00, 0x00)
write(io, 0x12, 0x0b, 0x00, 0x00)
write(io, UInt32(0))
write(io, UInt32(0))

# color palette
write(io, [[x, x, x, 0x00] for x in UInt8.(0:255)]...)

# data
padding = fill(0x00, datawidth - width)
for y in 1:height
write(io, i.data[y,:], padding)
end
end
end
👀 Reading hidden code
7.0 ms