TP-traitement-audio-visuel/julia/notebook.jl
2023-06-22 20:47:16 +02:00

3996 lines
127 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

### A Pluto.jl notebook ###
# v0.19.17
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
# ╔═╡ bbe20904-d70a-438d-b46a-889ddfb8bab3
begin
using Plots # pour le tracé de figures
using Statistics # pour mean et std (en autres)
using PlutoUI # pour les objets Pluto
using LinearAlgebra # pour les matrices identité
using StatsPlots
using Distributions
using MAT
using DSP
gr()
TableOfContents(depth=4)
end
# ╔═╡ e08d7569-68d5-4da6-b8fa-085ed6a312f6
html"""
<style>
img, .js-plotly-plot {
margin: auto;
display: block;
}
img[alt="50%"] {
width: 50%;
}
audio, video, td>img, .main-svg, .js-plotly-plot, .svg-container {
width: 100% !important;
}
main {
max-width: 58rem;
}
pluto-logs-container {
display: none
}
</style>
"""
# ╔═╡ 63ae9b00-344d-4726-8d21-a0bdc75ee8be
html"""
<center>
<strong style="font-size: 2rem;">
Traitement des données audio-visuelles <br/>
Laurent Fainsin <br/>
2021 - 2022
</strong>
</center>
"""
# ╔═╡ f7cdde7d-884d-4116-9d40-0b2f68e71e6a
md"""
# TP1/TP2 - Courbes de bézier
"""
# ╔═╡ 8cd04c88-4ea4-4594-b0ab-a145096b3122
"""
Renvoie un [polynome de Bernstein](https://en.wikipedia.org/wiki/Bernstein_polynomial).
###### Args:
- i : le degré selectionné
- d : le(s) degré(s) du polynome
- x : le(s) point(s) que l'on souhaite évaluer
"""
function bernstein(i::Int, d, x)
return binomial(d, i) .* x .^ i .* (1 .- x) .^ (d - i)
end
# ╔═╡ 3a57871a-8ccf-11ec-3c6c-d525f4e8b5c2
"""
Renvoie une [courbe de Bézier](https://en.wikipedia.org/wiki/Bézier_curve).
###### Args:
- β_0 : le premier point de controle de la courbe
- β : les points de contrôles intermédiaire de la courbe
- β_d : le dernier point de contrôle de la courbe
- x : le(s) point(s) que l'on souhaite évaluer
"""
function bezier(β_0::Number, β::AbstractArray, β_d::Number, x)
d = length(β) + 1
y = β_0 * (1 .- x) .^ d + β_d * x .^ d
for i = 1:d-1
y += β[i] * bernstein(i, d, x)
end
return y
end
# ╔═╡ 513cf0e8-b7db-447c-b04a-a2e2fa17fb32
"""
Renvoie une [courbe de Bézier](https://en.wikipedia.org/wiki/Bézier_curve) bruité selon une [distribution normale](https://en.wikipedia.org/wiki/Normal_distribution).
###### Args:
- β_0 : le premier point de controle de la courbe
- β : les points de contrôles intermédiaire de la courbe
- β_d : le dernier point de contrôle de la courbe
- x : le(s) point(s) que l'on souhaite évaluer
- σ : l'écart-type de la distribution normale
"""
function bezier_bruitee(β_0::Number, β::AbstractArray, β_d::Number, x, σ::Number)
return bezier(β_0, β, β_d, x) + σ * randn(size(x))
end
# ╔═╡ 97fba57b-8d2a-49c9-a18a-58eb2fe651eb
"""
Renvoie la régression d'une [courbe de Bézier](https://en.wikipedia.org/wiki/Bézier_curve) par optimisation du [problème des moindres carrés](https://en.wikipedia.org/wiki/Least_squares).
###### Args:
- D_app : matrice contenant les points à régresser
- β_0 : le premier point de controle de la courbe
- β_d : le dernier point de contrôle de la courbe
- d : le degré du la courbe de Bézier souhaité
"""
function moindres_carres(D_app::Vector, β_0::Number, β_d::Number, d::Int)::AbstractArray
x, y = D_app
B = y .- β_0 .* (1 .- x) .^ d .- β_d .* (x .^ d)
A = zeros(length(x), d - 1)
for i = 1:(d-1)
A[:, i] = bernstein(i, d, x)
end
return A \ B
end
# ╔═╡ 78784cae-f749-4a0f-89cc-46dfa4994d3a
"""
Renvoie la régression d'une [courbe de Bézier](https://en.wikipedia.org/wiki/Bézier_curve) par optimisation du [problème des moindres carrés](https://en.wikipedia.org/wiki/Least_squares) écrêtés.
###### Args:
- D_app : matrice contenant les points à régresser
- β_0 : le premier point de controle de la courbe
- β_d : le dernier point de contrôle de la courbe
- d : le degré du la courbe de Bézier souhaité
- λ : le terme de pénalisation, "hyper-paramètre"
"""
function moindres_carres_ecretes(D_app::Vector, β_0::Number, β_d::Number, d::Int, λ::Number)::AbstractArray
x, y = D_app
B = y .- β_0 .* (1 .- x) .^ d .- β_d .* (x .^ d)
A = zeros(length(x), d - 1)
for i = 1:(d-1)
A[:, i] = bernstein(i, d, x)
end
slope = (β_d - β_0) / (last(x) - first(x))
beta_barre = slope .* (1/d:1/d:1-1/d) .+ β_0
C = B .- A * beta_barre
delta_chapeau = (A' * A + λ .* I(d - 1)) \ (A' * C)
return beta_barre .+ delta_chapeau
end
# ╔═╡ f2f61669-6cbd-460d-9a48-454634f86579
Markdown.MD(Markdown.Admonition("warning", "Interactivité !", [md"Attention l'utilisation des sliders pour jouer avec les paramètres des figures (TP1/TP2) nécessite d'utiliser [Pluto.jl](https://github.com/fonsp/Pluto.jl). Pour ouvrir ce notebook, vous pouvez simplement cliquer en haut à droite de la page sur \"Edit or run this notebook\". Sinon vous pouvez toujours utiliser le service en ligne Binder, mais c'est très lent."]))
# ╔═╡ cf014e6b-9e74-4abf-9532-540f4bb6f43f
begin
σ_slider = @bind σ Slider(0:0.05:1, default=0.6, show_value=true)
# Points de contrôles, degré = 5
β_0 = 115
β_d = 123
β = [116, 120, 123.5, 114.5]
d_exact = length(β) + 1
md"""
## Génération de nos données de travail
σ: $(σ_slider)
Il nous faut générer des données avec lesquelles nous allons travailler par la suite. On construit alors des points de contrôle à partis desquels nous allons générer une courbe de bézier (de degré $(d_exact)) et des échantillons pour notre apprentissage et nos tests.
"""
end
# ╔═╡ 287bac5f-8932-4d8f-a114-39f4e071ba26
begin
gr()
# Calcul du modèle exact
x_exact = LinRange(0, 1, 50)
y_exact = bezier(β_0, β, β_d, x_exact)
# Calcul des données d'apprentissage
x_app = LinRange(0, 1, 100)
y_app = bezier_bruitee(β_0, β, β_d, x_app, σ)
D_app = [x_app, y_app]
# Calcul des données de test
x_test = LinRange(0, 1, 200)
y_test = bezier_bruitee(β_0, β, β_d, x_test, σ)
D_test = [x_test, y_test]
# Tracé des données générées
figure = plot(x_exact, y_exact, label="exact", linewidth=2, legend=:topleft)
scatter!(x_app, y_app, label="apprentissage", opacity=0.3, markershape=:xcross)
scatter!(x_test, y_test, label="test", markersize=2, opacity=0.2, markershape=:xcross)
scatter!(0:1/d_exact:1, [β_0; β; β_d], label="points de controles")
xlabel!("x")
xlims!(-0.01, 1.01)
xticks!(0:0.1:1)
ylabel!("y")
ylims!(114, 124)
title!("σ=" * string(σ))
end
# ╔═╡ e1b0bacc-a173-4084-bb5f-3fbd63527c23
begin
d_slider = @bind d Slider(1:1:20, default=5, show_value=true)
md"""
## Régression par les moindres carrés
d: $(d_slider)
On peut dans un premier temps utiliser les moindres carrés pour effectuer une régression de la courbe de bézier, et ainsi retrouver les points de contrôle définis précédemment.
On cherche à résoudre le problème suivant:
$A \beta^\top = B$
Au moindres carrés, on a:
$\beta^\top = A^\dagger B = (A^\top A)^{-1} A^\top B$
"""
end
# ╔═╡ 1ae5bc6d-16c4-40cb-846e-3b0e177fd0cf
begin
gr()
# Régression par les moindres carrés
β_estime = moindres_carres(D_test, β_0, β_d, d)
y_estime = bezier(β_0, β_estime, β_d, x_app)
# Tracé de la regression
plot(x_app, y_estime, linewidth=2, label="estimation", legend=:bottomright)
plot!(x_exact, y_exact, label="exact")
scatter!(x_app, y_app, label="apprentissage", opacity=0.2, markershape=:xcross)
xlabel!("x")
xlims!(-0.01, 1.01)
xticks!(0:0.1:1)
ylabel!("y")
ylims!(114, 124)
title!("d=" * lpad(d, 2, "0") * ", σ=" * string(σ))
end
# ╔═╡ b3634cc1-fb5f-4b7b-bfa3-08d1fb787c5f
md"""
On observe alors que plus $\sigma$ (le bruit) est élevé, plus la régression sera eloigné de la véritable courbe.
De même on remarque que plus le degré $d$ de la regression est élevé, plus la courbe estimée colle aux différents points de notre base d'apprentissage, mais on observera que celle-ci s'éloigne tout de même de la courbe originale.
"""
# ╔═╡ 73ed5f0e-4298-4308-bcb8-7c1245ebf0f5
md"""
### Calcul de l'erreur de l'estimation
Nous pouvons trouver l'erreur de notre approximation en calculant l'erreur quadratique moyenne sur une série de degrés. Nous disposons de plusieurs méthodes pour calculer cette erreur:
"""
# ╔═╡ c0615490-3d43-4c42-9ac6-c4fdd59aae7f
md"""
On observe alors que l'erreur d'apprentissage est décroissante, celle-ci tend vers 0, et atteint 0 pour $d=\text{nb}_{\text{app}}$.
Intuitivement la meilleure valeur pour $d$ semble être le minimum des erreurs de généralisation ou de leave-one-out.
"""
# ╔═╡ cc938f64-41f9-478b-a136-ae0cc55b5f48
function erreur_apprentissage(D_app, β_0, β_d, d)
x, y = D_app
β = moindres_carres(D_app, β_0, β_d, d)
estimation = bezier(β_0, β, β_d, x)
return mean((estimation - y) .^ 2)
end
# ╔═╡ aec0ff6e-ac6a-4104-83c1-f70e254602b9
function erreur_generalisation(D_test, D_app, β_0, β_d, d)
x, y = D_test
β = moindres_carres(D_app, β_0, β_d, d)
estimation = bezier(β_0, β, β_d, x)
return mean((estimation - y) .^ 2)
end
# ╔═╡ dbffcc35-ba67-4468-b1b5-1c38a0fd971e
function erreur_leave_one_out(D_app, β_0, β_d, d)
x, y = D_app
n = length(x)
VC = 0
for j = 1:n
D_loo = [
[x[1:j-1]; x[j+1:end]],
[y[1:j-1]; y[j+1:end]]
]
β = moindres_carres(D_loo, β_0, β_d, d)
estimation = bezier(β_0, β, β_d, x[j])
VC += (y[j] - estimation) .^ 2
end
return VC / n
end
# ╔═╡ d969e49e-0a0c-4bdf-aba2-835586c87e7f
begin
gr()
degres = 2:15
erreurs_apprentissage = zeros(length(degres))
erreurs_generalisation = zeros(length(degres))
erreurs_leave_one_out = zeros(length(degres))
for (i, d) = enumerate(degres)
# Calcul de l'erreur d'apprentissage en fonction de d
err_app = erreur_apprentissage(D_app, β_0, β_d, d)
erreurs_apprentissage[i] = err_app
# Calcul de l'erreur de généralisation en fonction de d
err_gen = erreur_generalisation(D_test, D_app, β_0, β_d, d)
erreurs_generalisation[i] = err_gen
# Calcul de la validation croisée Leave-one-out
err_loo = erreur_leave_one_out(D_app, β_0, β_d, d)
erreurs_leave_one_out[i] = err_loo
end
plot(degres, erreurs_apprentissage, linewidth=2, label="apprentissage")
plot!(degres, erreurs_generalisation, linewidth=2, label="généralisation")
plot!(degres, erreurs_leave_one_out, linewidth=2, label="leave-one-out")
xlabel!("d")
xticks!(degres)
ylabel!("erreur")
title!("σ=" * string(σ))
end
# ╔═╡ ae8a3f9b-12cf-4802-8ae5-31546d82516d
md"""
### Estimation de $d$ et $\sigma$
Le calcul de ces erreurs nous permet d'estimer le meilleur degré $d$ à sélectionner pour que notre modèle soit le plus exact:
"""
# ╔═╡ 7960408a-d922-43e2-9a61-4aaf0c9e6a39
begin
# Estimation du degré de la courbe par dérivée de l'apprentissage (ghetto)
local d_estime_app = argmax(diff(erreurs_apprentissage))
local σ_estime_app = std(erreurs_apprentissage)
# Estimation du degré de la courbe et de σ, par généralisation
local d_estime_gen = degres[argmin(erreurs_generalisation)]
local σ_estime_gen = std(erreurs_generalisation)
# Estimation du degré de la courbe et de σ, par leave-one-out
local d_estime_loo = degres[argmin(erreurs_leave_one_out)]
local σ_estime_loo = std(erreurs_leave_one_out)
Markdown.MD(
Markdown.Admonition(
"info", "Résultats", [Markdown.parse("""
∇²Apprentissage : d=$(d_estime_app), σ=$(round(σ_estime_app, digits=2)) (~broken)
Généralisation : d=$(d_estime_gen), σ=$(round(σ_estime_gen, digits=2))
Leave-one-out : d=$(d_estime_loo), σ=$(round(σ_estime_loo, digits=2))
""")]
)
)
end
# ╔═╡ b56c3502-4dd9-4446-acd9-dcb0c667803f
md"""
On peut par la même occasion essayer de calculer l'écart-type de nos données, et le comparer au véritable sigma=$(σ). Les estimations ne sont pas très précises.
"""
# ╔═╡ cde13c7c-fbc2-4a8d-9b82-7607d71bb4b1
begin
gr()
n = 1000
degre = 5
betas_estime = zeros(n, 4)
x_rng = LinRange(0, 1, 100)
for i = 1:n
y_rng = bezier_bruitee(β_0, β, β_d, x_rng, σ)
D_rng = [x_rng, y_rng]
betas_estime[i, :] = moindres_carres(D_rng, β_0, β_d, degre)
end
A = zeros(length(x_rng), degre - 1)
for i = 1:(degre-1)
A[:, i] = bernstein(i, degre, x_rng)
end
σ_norm = diag(σ^2 .* pinv(A' * A))
colors = distinguishable_colors(degre - 1)
plot()
for (i, b) = enumerate(β)
histogram!(betas_estime[:, i], bins=:scott, label=false, normed=true, opacity=0.25, fillcolor=colors[i])
plot!(Normal(b, σ_norm[i]), color=colors[i], label="β[" * string(i) * "]", linewidth=3)
end
xlabel!("β")
xlims!(105, 135)
ylims!(0, 1)
title!("σ=" * string(σ))
end
# ╔═╡ 76279a22-3afe-46e1-829f-88f5dddfd55c
md"""
### Vérifions que notre modèle soit non biaisé
On peut montrer que le vecteur de paramètres estimé en moindres carrés est distribué selon la loi normale suivante:
$$\beta \hookrightarrow \mathcal{N} ( \beta^*, \sigma^2 ( A^\intercal A )^{-1})$$
On trace alors sur la figure suivante, avec un tracé continu la distribution théorique des $\beta$, et via un histogramme la distribution que l'on obtient par estimation aux moindres carrés (sur un grand nombre de points, n=$(n))
"""
# ╔═╡ a8843a99-cd5d-47fa-810b-68b56e405af9
md"""
On observe bien que les tracés discrets et continus se superposent, on en déduit que notre estimation est bien non biaisée.
"""
# ╔═╡ c3f3a0c3-8523-43aa-8a8a-589f567399c9
begin
λ_slider = @bind λ Slider(0:0.01:0.1, default=0.05, show_value=true)
md"""
## Régression par les moindres carrés écrêtées
λ: $(λ_slider)
On observe que via les moindres carrés classiques, notre régression a souvent tendance à [overfitter](https://fr.wikipedia.org/wiki/Surapprentissage) les points d'apprentissage. Pour remédier à ce problème, nous pouvons introduire un hyperparamètre $\lambda$ qui nous permettra de pénaliser l'overfitting (par régularisation).
"""
end
# ╔═╡ f60fbae0-a6bd-4791-b38b-9e29ae801a07
begin
gr()
# Régression par les moindres carrés écrétés
β_estime_ecrete = moindres_carres_ecretes(D_test, β_0, β_d, 10, λ)
y_estime_ecrete = bezier(β_0, β_estime_ecrete, β_d, x_app)
# Tracé de la regression
plot(x_app, y_estime_ecrete, linewidth=2, label="estimation", legend=:bottomright)
plot!(x_exact, y_exact, label="exact", opacity=0.5)
scatter!(x_app, y_app, label="apprentissage", opacity=0.5, markershape=:x)
xlabel!("x")
xlims!(-0.01, 1.01)
xticks!(0:0.1:1)
ylabel!("y")
ylims!(114, 124)
title!("d=" * lpad(d, 2, "0") * ", σ=" * string(σ) * ", λ=" * rpad(λ, 4, "0"))
end
# ╔═╡ b95121b9-4b58-4086-bbd8-ecd2893060dd
md"""
On remarque alors que plus $\lambda$ est grand, plus la courbe estimée aura tendance à être simple, pour des $\lambda$ très élevés, la courbe tend vers une droite entre $\beta_0$ et $\beta_d$.
"""
# ╔═╡ 3ff18976-beb4-45ad-aa29-d511e5d3ad0a
md"""
### Calcul de l'erreur de l'estimation
Puisque nous sommes désormais capables de trouver le degré optimal à selectionner pour notre regression via l'etude précédente, nous pouvons désormais nous focaliser sur l'erreur de notre approximation en faisant varier $\lambda$.
"""
# ╔═╡ acbab47e-2ab5-443a-8d78-f597a4ca08ed
md"""
On estime alors le meilleur $\lambda$ à selectionner en minimisant l'erreur:
"""
# ╔═╡ c563818e-d117-47ce-abeb-5b048edad64e
function erreur_leave_one_out_bis(D_app, β_0, β_d, d, λ)
x, y = D_app
n = length(x)
VC = 0
for j = 1:n
D_loo = [
[x[1:j-1]; x[j+1:end]],
[y[1:j-1]; y[j+1:end]]
]
β = moindres_carres_ecretes(D_loo, β_0, β_d, d, λ)
estimation = bezier(β_0, β, β_d, x[j])
VC += (y[j] - estimation) .^ 2
end
return VC / n
end
# ╔═╡ 650ab9f4-581b-4190-8dd0-91c6d4c86b6b
function erreur_leave_one_out_ter(D_app, beta_0, beta_d, d, lambda)
x, y = D_app
A = zeros(length(x), d - 1)
for i = 1:(d-1)
A[:, i] = bernstein(i, d, x)
end
S = A * ((A' * A + lambda * I(d - 1)) \ A')
beta = moindres_carres_ecretes(D_app, beta_0, beta_d, d, lambda)
bidule = ((y - bezier(beta_0, beta, beta_d, x)) ./ (1 .- diag(S))) .^ 2
return mean(bidule)
end
# ╔═╡ 1b88eacd-754b-4b6b-a8e0-dd35ae37f248
begin
gr()
lambdas = 0.01:0.001:0.1
erreurs_leave_one_out_ter = zeros(length(lambdas))
for (i, l) = enumerate(lambdas)
err_loo = erreur_leave_one_out_ter(D_app, β_0, β_d, d, l)
erreurs_leave_one_out_ter[i] = err_loo
end
plot(lambdas, erreurs_leave_one_out_ter, linewidth=2, label="leave-one-out")
xlabel!("λ")
ylabel!("erreur")
title!("σ=" * string(σ))
end
# ╔═╡ 230c1126-6717-4a93-9013-5f0c15616904
begin
# Estimation de λ et de σ, par leave-one-out
local λ_estime_loo = lambdas[argmin(erreurs_leave_one_out_ter)]
Markdown.MD(
Markdown.Admonition(
"info", "Résultats", [Markdown.parse("""
Leave-one-out: λ=$(λ_estime_loo)
""")]
)
)
end
# ╔═╡ 5974ceb3-1e82-4183-9dca-35ccd6b9a9ba
md"""
## Courbes de Bézier couplées
Il peut nous être parfois amené à combiner plusieurs courbes de bézier en une seule (pour du tracé/dessin vectoriel par exemple). Voyons un exemple en couplant deux courbes de Bézier (modélisant deux bords du contour d'une flamme).
"""
# ╔═╡ c9e530b7-f5f6-4905-8c6c-69ea2440f57d
md"""
Pour coupler nos courbes, on couple simplement nos deux systèmes de la manière suivante:
Soit $x_g$, $x_d$ et $y$ les variables nous permettant de décrire respectivement le bord gauche et le bord droit de la flamme.
Posons
```math
X =
\begin{bmatrix}
x_g \\
x_d
\end{bmatrix}
, \quad
E =
\begin{bmatrix}
B_1^d(y_1) & \cdots & B_{d-1}^d(y_1) & 0 & \cdots & 0 & B_d^d(y_1) \\
\vdots & \ddots & \vdots & \vdots & & \vdots & \vdots \\
B_1^d(y_n) & \cdots & B_{d-1}^d(y_n) & 0 & \cdots & 0 & B_d^d(y_n) \\
0 & \cdots & 0 & B_1^d(y_1) & \cdots & B_{d-1}^d(y_1) & B_d^d(y_1) \\
\vdots & & \vdots & \vdots & \ddots & \vdots & \vdots \\
0 & \cdots & 0 & B_1^d(y_n) & \cdots & B_{d-1}^d(y_n) & B_d^d(y_n)
\end{bmatrix}
, \quad
F = X -
\begin{bmatrix}
\beta_0 B_0^d(y_1) \\
\vdots \\
\beta_0 B_0^d(y_n) \\
\gamma_0 B_0^d(y_1) \\
\vdots \\
\gamma_0 B_0^d(y_n) \\\end{bmatrix}
```
Il nous suffit alors de résoudre le système $E Y = F$ (où $Y$ est notre flamme).
"""
# ╔═╡ 67a5b4cc-00dd-4f8f-a408-27f1d8c60e7c
md"""
### Simulation de silhouettes
On modélisant les $\beta$ obtenus via les figures précédentes par une loi normale, nous sommes en mesure de _simuler_ de [nouvelles flammes](https://c.tenor.com/rdkHWmsaP5sAAAAC/inflatable-tube-man-air-dancer.gif). Cependant, on observe que les écarts-types trouvé sont plutôt grands. (Ici, les coefficients ont été atténués pour des raisons visuelles.)
"""
# ╔═╡ 8655b8fd-ef08-441f-9186-e2febce6e8f1
md"""
Une meilleure estimation serait de modéliser séparement tous les indices pairs et impairs de $\beta$ séparement, comme le laisse suggérer la figure suivante:
"""
# ╔═╡ 475aecd7-123f-4e4a-b004-90c2a8a951d8
md"""
On observe que les $\beta$ "oscillent", une estimation par loi normale semble donc peu adaptée, puisque cela génère de grands écart-types.
"""
# ╔═╡ fac7b990-552e-4353-8264-ba0840a95383
function moindres_carres_bis(d, y, bords_g, beta_0, bords_d, gamma_0)
x = [bords_g; bords_d]
F = x .- [beta_0 .* bernstein(0, d, y); gamma_0 .* bernstein(0, d, y)]
E_block = zeros(length(y), d)
for i = 1:d
E_block[:, i] = bernstein(i, d, y)
end
E = [
E_block[:, 1:d-1] zeros(length(y), d - 1) E_block[:, d]
zeros(length(y), d - 1) E_block
]
return E \ F
end
# ╔═╡ 27a1f0cc-a6bc-417e-9c58-6b63172f753f
begin
gr()
# chargement des données du TP à partir du fichier .mat
file = matopen("donnees.mat")
y = read(file, "y")
bords = read(file, "bords")
gamma_0 = read(file, "gamma_0")
beta_0 = read(file, "beta_0")
local d = Int(read(file, "n"))
close(file)
local flame = @animate for k in 1 : 10
X_estime = moindres_carres_bis(d, y, bords[:, 1, k], beta_0, bords[:, 2, k], gamma_0)'
beta_estime = [X_estime[1:d-1]; X_estime[2*d-1]]
gamma_estime = [X_estime[d:2*(d-1)]; X_estime[2*d-1]]
x_gauche = bezier(beta_0, beta_estime[1:end-1], beta_estime[end], y)
x_droite = bezier(gamma_0, gamma_estime[1:end-1], gamma_estime[end], y)
scatter(y, bords[:, 1, k], color=:crimson, markersize=2, opacity=0.25, label=false)
scatter!(y, bords[:, 2, k], color=:crimson, markersize=2, opacity=0.25, label=false)
plot!(y, x_gauche, color=:crimson, label=false)
plot!(y, x_droite, color=:crimson, label=false)
xlabel!("y")
xlims!(-0.01, 1.01)
ylabel!("x")
ylims!(55, 160)
title!("k=" * lpad(k, 2, "0"))
end
gif(flame, fps=10);
end
# ╔═╡ e16bb608-d97b-40eb-bf6b-4199ee4d1926
begin
gr()
local anim = @animate for k in 1 : 1 : 10
X_estime = moindres_carres_bis(d, y, bords[:, 1, k], beta_0, bords[:, 2, k], gamma_0)'
beta_estime = [X_estime[1:d-1]; X_estime[2*d-1]]
plot(beta_estime, label="betas")
xlabel!("beta")
ylabel!("estimation")
ylims!(20, 200)
title!("k=" * lpad(k, 2, "0"))
end
gif(anim, fps=5)
end
# ╔═╡ 4bcfb185-4499-422d-9439-6ca4e30c4846
function estimation_lois_n(x)
return mean(eachrow(x)), std(x, dims=1)[:]
end
# ╔═╡ 174dbb25-12ba-460d-a905-8223f8241964
function simulation(y, β_0, γ_0, moyennes, ecarts_types, d)
β = ecarts_types[1:d-1] / 5 .* randn(d - 1) .+ moyennes[1:d-1]
γ = ecarts_types[d:2*d-2] / 5 .* randn(d - 1) .+ moyennes[d:2*d-2]
γ_d = ecarts_types[2*d-1] / 5 * randn() + moyennes[2*d-1]
β_d = γ_d
x_gauche = bezier(β_0, β, β_d, y)
x_droite = bezier(γ_0, γ, γ_d, y)
return [x_gauche, x_droite]
end
# ╔═╡ 489676a6-b6cb-46dc-9898-feedcc2b61dd
begin
gr()
local d = 4
X = moindres_carres_bis(d, y, bords[:, 1, :], beta_0, bords[:, 2, :], gamma_0)'
moyennes, ecarts_types = estimation_lois_n(X)
# Simulation de silhouettes par tirages aléatoires
local flame = @animate for k in 1 : 1 : 10
x_g, x_d = simulation(y, beta_0, gamma_0, moyennes, ecarts_types, d)
plot(x_d, y, label=false, color=:crimson)
plot!(x_g, y, label=false, color=:crimson)
xlabel!("x")
xlims!(55, 160)
ylabel!("y")
ylims!(-0.01, 1.01)
end
gif(flame, fps=10)
end
# ╔═╡ 59be55be-5847-4fa1-a4f6-ef8e0658da13
md"""
# TP3 - Ellipses
Dans l'esprit des TP1 et TP2, on se concentre désormais sur l'estimation d'ellipses à partir d'un nuage de points.
"""
# ╔═╡ 2e002135-2a74-47bb-92a6-520d84e85bf3
md"""
## Cas n=1
"""
# ╔═╡ 764c0716-5dd2-4d3c-9489-1dd92d29d7c7
md"""
### Estimation par maximum de vraisemblance
Tout comme précédemment, la méthode la plus simple, mais non la plus efficace, est simplement d'essayer aléatoirement un grand nombre d'ellipses et de voir celle qui colle le mieux à notre nuage de points.
Mathématiquement on cherche à minimiser la vraisemblance:
```math
L_\text{p}(D_{app}) = \prod^{n_\text{app}}_{i=1} f_\text{P} (P_i)
```
avec:
```math
f_\text{P}(P_i) = \frac1{\sigma \sqrt{2\pi}} \exp\left\{{- \frac{r_\text{P}(P_i)^2}{2\sigma^2}}\right\}
```
En reformulant le problème, on minimise la somme:
```math
\min_\text{P} \left\{ \sum^{n_{app}}_{i=0} r_\text{P} (P_i)^2 \right\}
```
| | |
| :---------------------------------: | :--------------------------------: |
| $(Resource("https://fainsil.users.inpt.fr/content/TAV/TP3/exo1app.png")) | $(Resource("https://fainsil.users.inpt.fr/content/TAV/TP3/exo1mv.png")) |
"""
# ╔═╡ 274dd1f3-2a8c-4d4f-b8e2-a61e2f7bdef4
md"""
### Estimation par résolution dun système linéaire
Si l'on est sûr que notre nuage de point décrit une unique ellipse, on peut alors encore plus simplement résoudre le système linéaire:
```math
\alpha x^2 + \beta x y + \gamma y^2 + \delta x + \epsilon y + \phi = 0
```
On peut poser ce problème matriciellement:
```math
A X = O_{n_\text{app}}
```
avec:
```math
X = \begin{bmatrix}
\alpha \\
\beta \\
\gamma \\
\epsilon \\
\phi
\end{bmatrix}
, \quad
A =
\begin{bmatrix}
x_0^2 & x_0 y_0 & y_0^2 & x_0 & y_0 & 1 \\
\vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\
x_{n_\text{app}}^2 & x_{n_\text{app}} y_{n_\text{app}} & y_{n_\text{app}}^2 & x_{n_\text{app}} & y_{n_\text{app}} & 1 \\
1 & 0 & 1 & 0 & 0 & 0
\end{bmatrix}
, \quad
O_{n_\text{app}} =
\begin{bmatrix}
0 \\
\vdots \\
0 \\
1
\end{bmatrix}
```
| | |
| :---------------------------------: | :--------------------------------: |
| $(Resource("https://fainsil.users.inpt.fr/content/TAV/TP3/exo2app.png")) | $(Resource("https://fainsil.users.inpt.fr/content/TAV/TP3/exo2mc.png")) |
"""
# ╔═╡ e7ac36db-6496-4f0b-b961-1f180ca3622d
md"""
## Cas n > 1
"""
# ╔═╡ d97fd9ab-1422-4c39-85e6-d5306c690310
md"""
### Estimation par maximum de vraisemblance
Pour passer à l'estimation de plusieurs ellipses on change simplement nos formules:
```math
f(P) = \sum^{N_e}_{i=0}\frac{\pi_i}{\sigma_i \sqrt{2\pi}} \exp\left\{{- \frac{r_{\text{P}_i}(P_i)^2}{2\sigma_i^2}}\right\}
```
La maximisation de la log vraisemblance s'écrit alors:
```math
\max_\text{P} \left\{ \ln \prod^{n_\text{app}}_{i=1} f(\text{P}) \right\}
```
Pour améliorer nos résultats, on peut séparer les points en classes de telle manière que les points les plus proches d'une même ellipse soient dans la même classe.
En re-estimant nos ellipses, mais cette fois-ci classe par classes, on obtient un résultat satisfaisant.
"""
# ╔═╡ 6ce5a769-a05b-4a72-b2a6-46f61c530c3a
html"""
<table>
<tr>
<th>données</th>
<th>vraisemblance</th>
<th>séparation</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo3app.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo3mv.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo3mc_proba.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 1a31ebb3-b512-45b9-9f55-2a7b173ff2c7
md"""
### Estimation par lalgorithme EM
Une manière d'obtenir un résultat itérativement est d'utiliser un algorithme EM.
Celui-ci consiste au calcul des probabilités dappartenance aux classes des points dapprentissage:
```math
\mathcal{P}_k (P_i) = \frac
{ \displaystyle \frac{\pi_k}{\sigma_k} \exp\left\{{- \frac{r_{\text{P}_k}(P_i)^2}{2\sigma_k^2}}\right\} }
{ \displaystyle \sum^{N_e}_{i=0}\frac{\pi_i}{\sigma_i} \exp\left\{{- \frac{r_{\text{P}_i}(P_i)^2}{2\sigma_i^2}}\right\} }
```
À la mise à jour des proportions du mélange:
```math
\pi_k = \frac1{n_{app}} \sum^{n_{app}}_{i=1} \mathcal{P}_k (P_i)
```
Et à la résolution en moindres carrés pondérés des points de chaque classe.
"""
# ╔═╡ 05a64770-4015-438e-83ee-2d84d2277055
html"""
<style>
table {
table-layout: fixed ;
width: 100% ;
}
td {
width: 100% ;
}
</style>
<table>
<tr>
<th>données</th>
<th>vraisemblance</th>
<th>EM</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo4app.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo4mv.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo4.webp" />
</td>
</tr>
</table>
Voici deux autres exemples, avec cette fois-ci un plus grand nombre d'ellipses:
<table>
<th>données</th>
<th>EM</th>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo5app.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo5.webp" />
</td>
</tr>
<tr>
<td>
<img
src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo5app_explose.png"
/>
</td>
<td>
<img
src="https://fainsil.users.inpt.fr/content/TAV/TP3/exo5_explose.webp"
/>
</td>
</tr>
</table>
On observe que l'algorithme aura tendance à parfois exploser numériquement.
"""
# ╔═╡ 5a7ec049-1092-4096-b5d9-4d2f84c6be41
md"""
# TP4 - Segmentation
"""
# ╔═╡ 492cba29-1b5f-4f31-80e6-32dcab025f2f
md"""
Notre objectif dans ce TP est de procéder à la segmentation d'une image par classification.
Notre image (B&W) fil rouge sera celle-ci:
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/image_BW.png)
Cette image comporte 4 classes simples (gaussiennes, avec bruit) qu'il nous faudra retrouver.
"""
# ╔═╡ 830a7a78-0cb1-4ed1-906e-dd89bad6150f
md"""
## Approche supervisée
Si l'on dispose d'un expert, il est simple de lui demander d'échantillonner manuellement l'image pour que l'on puisse ainsi procéder derrière à une classification et à une segmentation.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/expert_BW.png)
Une première méthode naïve serait de procéder par maximimsation de la vraisemblance, mais cette méthode, en plus d'être stochastique, ne fournis pas des résultats très satisfaisants (~90%).
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo1mv.png)
Une amélioration consiste alors à utiliser le résultat précédent comme base à un recuit simulé.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo1.webp)
On remarque que cette approche améliore grandement notre estimation, il est possible d'obtenir de très bons résultats (>99.9%) si l'on tweak bien les paramètres de nos itérations.
"""
# ╔═╡ fb735670-d29b-43bd-a88b-ff79d489e0b0
md"""
## Approche non supervisé
Si on n'a pas d'expert, ou si on veut faire des économies, on peut essayer d'inférer les classes de notre image à partir de son histogramme.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo2hist4N_2B_0.99A.png)
En effet, on remarque facilement sur l'histogramme de notre image 4 pics (4 gaussiennes) qui correspondent à nos 4 classes.
Pour déterminer les paramètres de ces gaussiennes on procède par estimation à posteriori (on tire des combinaisons de gaussiennes au hasard et on prend celle qui correspond le mieux).
Voici l'image correspondante à cet histogramme:
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo2mv.png)
On obtient un histogramme satisfaisant, qui implique une bonne segmentation de notre image, mais comme précédemment, on peut soumettre ce résultat au recuit simulé pour améliorer notre segmentation.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_2B_0.99A.webp)
"""
# ╔═╡ 05ee0bd3-320d-4d8c-993e-f2e587d78dd8
md"""
## Avec de la couleur c'est mieux
Si l'on souhaite classifier une image en couleur et non en dégradé de gris, on applique la même logique que précédemment mais en dimension plus élevée (ici en dimension 3, puisque notre image colorée contient 3 canaux de couleur, le rouge, le vert et le bleu).
image à segmenter:
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/cellules.jpg)
selection de l'expert:
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo3expert.png)
maximum de vraisemblance:
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo3mv.png)
recuit simulé:
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_exo3.webp)
"""
# ╔═╡ c89378e2-424e-4a46-8c95-7fe97faafddd
md"""
## Influence des paramètres sur nos résultats
"""
# ╔═╡ 7cdf9477-d2a6-4846-9f4d-7f1df14f3c58
md"""
### Expert bourré
Si notre expert ne va pas très bien, s'il est bourré, ou juste nul, il se peut que son échantillonnage des classes soit mauvais.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/expert_BWbad.png)
Dans ce cas comme on peut s'en douter notre vraisemblance est horrible.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo1mvbad.png)
Le recuit peut tout de même améliorer cet échantillonnage, mais cette segmentation reste mauvaise.
![50%](https://fainsil.users.inpt.fr/content/TAV/TP4/exo1bad.webp)
"""
# ╔═╡ 49747114-1019-4478-984a-759887bd9b72
md"""
### Beta recuit simulé
Dans notre algorithme du recuit simulé, nous utilisons un paramètre $\beta$ représentant l'importance de notre régularisation lors d'une itération.
Ainsi pour différentes valeurs de beta:
"""
# ╔═╡ 9607de34-a8a6-4be3-b603-bf2a20e73fde
html"""
<table>
<tr>
<th>0.2</th>
<th>1</th>
<th>20</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_0.2B_0.99A.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_2B_0.99A.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_20B_0.99A.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ ca1d1182-f77b-4510-89be-e286818c41f3
md"""
On observe ainsi que plus $\beta$ est élevé, moins une itération pourra apporter de bruits. Cela permet de converger plus rapidement vers notre segmentation, mais celle-ci sera un peu moins précise.
"""
# ╔═╡ dc971adc-b6bc-4853-afde-a163e28ebbd3
md"""
### Alpha recuit simulé
Dans le recuit simulé $\alpha$ traduit quant à lui l'allure à laquelle la température décroît (selon une loi géométrique) :
"""
# ╔═╡ 9faecaf8-9a6a-41b4-a7e0-a9a65f039825
html"""
<table>
<tr>
<th>0.5</th>
<th>0.99</th>
<th>1.1</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_2B_0.5A.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_2B_0.99A.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_2B_1.1A.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ 1a381029-c50a-4631-8982-e9d90543ad44
md"""
On observe que plus la température diminue, moins notre estimation aura de chance de faire réapparaitre du bruit.
Ainsi un petit $\alpha$ aura tendance à geler rapidement notre figure. Un alpha proche de 1 permet globalement d'obtenir de meilleurs résultats, car une haute température corrige les petits défauts de l'estimation lors des dernières itérations. Un alpha supérieur à 1 part en couille.
"""
# ╔═╡ 57f39905-d77f-43f9-ab8a-c8941393498a
md"""
### N non supervisé
Lors d'une approche non supervisé il nous faut tout de même annoncer le nombre de classes de l'image, cette méthode fonctionne plutôt bien, mais des résultats intéressants apparaissent si l'on se trompe.
"""
# ╔═╡ 45b844b0-0b5e-433f-9590-70b001dceff2
html"""
<table>
<tr>
<th>2</th>
<th>4</th>
<th>8</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_2N_2B_0.99A.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_4N_2B_0.99A.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP4/recuit_MAP_8N_2B_0.99A.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ e328d40b-3ba4-4fc7-b52d-aafd62f589f2
md"""
On observe que plus l'on augmente le nombre de classes (dans notre cas), plus l'histogramme trouvé par MAP est précis, mais plus notre segmentation sera bruitée. L'estimation convergera tout de même (souvent) vers 4 classes, bien que nous en ayons spécifié plus.
"""
# ╔═╡ 709a5e59-545d-4b39-8b82-0c9fd2232d3b
md"""
# TP5 - Flamants roses
Notre objectif dans ce TP est de procéder à la détection d'objets dans une image, plus précisément au dénombrement de flamants roses dans une image (via des cercles/ellipses roses).
"""
# ╔═╡ eaedfd74-c98f-4f8f-b311-09b49839f16a
html"""
Voici à gauche des flamants roses pour référence et à droite l'image des flamants roses que nous souhaitons dénombrer:
<table>
<tr>
<td>
<img src="https://c.tenor.com/GQWonpcxJOkAAAAC/flamingo-place.gif" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP5/colonie.png" />
</td>
</tr>
</table>
Je vous laisse faire le jeu des sept différences parce que je n'en vois pas.
"""
# ╔═╡ 8d896695-50eb-4b66-a60c-10ea12cf2417
md"""
## Dénombrement naif
Une première méthode consiste à tirer à chaque itération un nouveau cercle tel que le niveau de gris moyen des pixels contenus dans l'ensemble des cercles de notre dénombrement soit plus élevé (puisque nos flamants roses sont blancs dans notre image en dégradé de gris).
Voici le résultat de 1000 itérations:
![](https://fainsil.users.inpt.fr/content/TAV/TP5/exercice0.webp)
On observe très clairement un problème, les flamants roses défient les lois de la physique et occupent le même espace.
"""
# ╔═╡ da89e6ff-5d19-48df-9106-140619914220
md"""
## Dénombrement moins con
Il est donc important de rajouter la contrainte empêchant deux flamants roses (deux cercles) d'être trop près. Nous pouvons donc écrire:
$\forall i \neq j, ||C_i - C_j|| \geq \sqrt2 R$
On obtient ainsi un résultat plus cohérent.
![](https://fainsil.users.inpt.fr/content/TAV/TP5/exercice1.webp)
"""
# ╔═╡ b133aaf9-9943-4ef7-a7ce-11daea4a3692
md"""
## Dénombrement automatique
Jusqu'à maintenant notre dénombrement/détection nécessitait l'entrée $N$ (plutôt inutile sachant que c'est ce que l'on cherche). Nous pouvons déterminer le véritable nombre de flamants en utilisant un algorithme de naissance et de mort combiné à un recuit simulé.
Après environ 400 itérations, on converge vers un résultat satisfaisant.
![](https://fainsil.users.inpt.fr/content/TAV/TP5/exercice2.webp)
On observe bien que l'énergie globale de notre recuit diminue au cours du temps, et que le nombre de flamants $N$ converge vers ~150.
"""
# ╔═╡ 797f62e6-c573-4edc-90eb-c258592e299a
md"""
## Dénombrement avec des ellipses
Comme vous l'avez peut-être remarqué, un flamant rose ressemble peu à un cercle parfait. C'est pourquoi il est plus judicieux de modéliser les flamants roses dans notre image par des ellipses lors de notre dénombrement.
Voici le résultat que l'on obtient:
![](https://fainsil.users.inpt.fr/content/TAV/TP5/exercice3.webp)
On observe globalement que les ellipses collent mieux aux taches blanches de l'image, cependant puisqu'une ellipse comporte bien plus de paramètres qu'un cercle, le nombre d'itérations pour obtenir un résultat satisfaisant est bien plus grand.
"""
# ╔═╡ c037620f-6644-40da-a045-2a68ae2464a0
md"""
# TP6 - Restauration dimages
Nous souhaitons dans cette nouvelle partie restaurer des images bruitées ou abimées.
"""
# ╔═╡ ee234a83-5f87-42c5-85d2-9a8a609a132c
md"""
## Débruitage par variation quadratique
Si l'on souhaite enlever le bruit (~poivre/sel) de cette image, une première approche consiste à chercher l'application $u$ qui minimise le problème suivant:
$$\text{argmin}\ E_{\text{Tikhonov}}(u) = \frac12 \iint_\Omega \left\{ \left[ u(x,y) - u_0(x,y) \right] ^2 + \lambda |\nabla u(x,y)|^2 \right\} \ dx \ dy$$
Résoudre ce problème revient à trouver une application qui transforme notre image en une image proche de celle original, mais dont le gradient serait inférieur. On obtient alors une image un peu floue.
"""
# ╔═╡ e90f4ee5-d0dc-4a43-a6ae-471c65f880fe
html"""
<table>
<tr>
<th>bruité</th>
<th>débruité</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo0_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo0_clean.png" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo0bis_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo0bis_clean.png" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo0amogus_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo0amogus_clean.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 3a5533f2-881a-4b99-96e4-4ce4bd640aec
md"""
## Débruitage par variation totale
Une méthode un peu plus avancée consite à résoudre le problème d'optimisation suivant:
$$\text{argmin}\ E_{\text{TV}}(u) = \iint_\Omega \left\{ \frac12 \left[ u(x,y) - u_0(x,y) \right] ^2 + \lambda\sqrt{|\nabla u(x,y)|^2 + \epsilon} \right\} \ dx \ dy$$
On remplace simplement la régularisation quadratique
$$\frac12 \iint_\Omega|\nabla u|^2$$
par une estimation de la régularisation totale
$$\iint_\Omega|\nabla u| \simeq \iint_\Omega \sqrt{|\nabla u(x,y)|^2 + \epsilon}$$
Sur nos images précédentes on obtient alors:
"""
# ╔═╡ fdfb04a5-3bf1-4a71-8433-1221e8fbcce4
html"""
<table>
<tr>
<th>bruité</th>
<th>débruitage</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo1_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo1_clean.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo1bis_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo1bis_clean.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo1amogus_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo1amogus_clean.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ a4826006-73e0-4b15-9813-0ab5cabf25ff
md"""
On observe globalement toujours un lissage sur nos images, mais les contours sont cette fois bien mieux conservés.
"""
# ╔═╡ 2e2847a7-d102-46dc-b6cf-3767354ec003
md"""
## Inpainting par variation totale
On peut résoudre le même type de problème pour restaurer une image comportant un défaut:
$$\text{argmin}\ E_{Inpainting}(u) = \frac12 \iint_{\Omega \backslash \text{D}} \left[ u(x,y) - u_0(x,y) \right] ^2 \ dx \ dy + \lambda \iint_\Omega \sqrt{|\nabla u(x,y)|^2 + \epsilon} \ dx \ dy$$
"""
# ╔═╡ ecdf5365-32b2-470e-bfa4-4ade78683841
html"""
<table>
<tr>
<th>bruité</th>
<th>débruitage</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2_clean.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2bis_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2bis_clean.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2ter_bruit.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2ter_clean.webp" />
</td>
</tr>
</table>
<table>
<tr>
<th>original</th>
<th>buité</th>
<th>débruité</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2catjam_original.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2catjam_bruit.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP6/exo2catjam_clean.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ 1daa22b9-335f-436d-bf4d-053637374978
md"""
## Inpainting par rapiéçage
TODO
"""
# ╔═╡ 28a84673-b849-4ccb-a488-9db4fe1818f6
md"""
# TP7 - Techniques de photomontage
L'objectif de ce TP est de permettre l'incrustation d'objets dans une scène le plus naturellement possible.
"""
# ╔═╡ 3a7e6fd6-9e54-41a0-90e3-a5d047e7e3de
md"""
## Photomontage par collage
"""
# ╔═╡ 4a66976a-9f51-4bfa-971b-2d922134f196
html"""
On souhaite obtenir ce type de résultats:
<table>
<tr>
<th>Image cible</th>
<th>Image source</th>
<th>Résultat</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/montagne.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/orque.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/exercice0.jpg" />
</td>
</tr>
</table>
Dans l'exemple ci-dessus, notre source a simplement été collée sur la cible, on remarque immédiatement que ce collage est très <i>brut</i>, une méthode pour rendre le résultat plus plaisant est alors de faire en sorte que les gradients de la cible et de la source correspondent mieux.
On obtient alors:
<table>
<tr>
<th>Image cible</th>
<th>Image source</th>
<th>Résultat</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/montagne.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/orque.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/exercice1.jpg" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/tsunami.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/rice_man.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/exercice1rice.jpg" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/raft.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/herisson.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/exercice1herisson.jpg" />
</td>
</tr>
</table>
"""
# ╔═╡ 32d4e51b-8599-4d93-905a-cb4a719710d3
md"""
## Décoloration partielle dune image
"""
# ╔═╡ 7db8d120-6c7e-4394-9336-9f66de7039be
html"""
Un résultat intéressant que l'on peut obtenir est la décoloration partielle d'une image, en effet si l'on vient coller (via la même technique que précédemment) un morceau de l'image sur la même image en niveau de gris, on obtient alors un effet artistique:
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/rose.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/exercice2.jpg" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/waldo.jpg" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP7/exercice2waldo.jpg" />
</td>
</tr>
</table>
"""
# ╔═╡ 6c2d038e-b1cf-49d1-8e7e-da9d883b8c35
md"""
## Autres techniques de photomontage
TODO
"""
# ╔═╡ 2f7ad872-4215-4d84-a30d-d3486979dd32
md"""
# TP8 - Décomposition dune image
"""
# ╔═╡ f77b33d9-57a1-4659-9a68-b89184838653
md"""
## Transformation de Fourier discrète 2D
La transformée de fourier d'une image 2D permet d'obtenir son spectre. Comme la transformée est inversible, la transformée inverse d'un spectre permet d'obtenir une image. Ainsi si l'on modifie le spectre d'une image, on observe des changements "fréquentiels" dans celle-ci:
"""
# ╔═╡ aab70bb0-5328-4044-a16a-4291c1583c9b
html"""
<table>
<tr>
<td>
Isolation des lignes horizontales
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo0_1.png" />
</td>
</tr>
<tr>
<td>
Isolation des lignes verticales
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo0_2.png" />
</td>
</tr>
<tr>
<td>
Isolation des lignes diagonales
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo0_3.png" />
</td>
</tr>
</table>
"""
# ╔═╡ c00837b6-cd15-479e-96cb-065aa4411283
md"""
## Décomposition structure + texture
Cette décomposition permet d'obtenir la structure correspond plutôt aux basses fréquences d'une image, et la texture correspond plutôt aux hautes fréquences de l'image.
"""
# ╔═╡ 5029374a-16c2-47aa-a057-84fcfc0e6c64
md"""
### Modèle filtre
Une première méthode pour obtenir une telle décomposition est d'appliquer un filtre passe bas et un filtre passe haut au spectre de notre image :
"""
# ╔═╡ 646d190f-b362-42a1-85ca-d7ba7c1c63ef
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo1.png" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo1chad.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 893b847a-b149-4059-ab12-2536bc86c1b6
md"""
On observe bien que la structure contient les couleurs de notre image et que la texture contient les contours de l'image. Cependant ce modèle n'est pas très précis.
Pour améliorer cette décomposition, au lieu de faire un filtrage passe-bas abrupt du spectre, on peut pondérer le spectre initial par les coefficients suivants:
$$\displaystyle \phi(f_x, f_y) = \frac{1}{1 + \displaystyle\frac{f_x^2 + f_y^2}{\eta}}, \quad \eta \approx 0.05$$
"""
# ╔═╡ d6987fe4-d170-4be0-9f71-8956a6eddcea
begin
plotly()
local x=range(-1, 1, step=0.01)
local y=range(-1, 1, step=0.01)
local eta = 0.05
local f(x,y) = 1 / ( 1 + (x^2 + y^2) / eta)
plot(x, y, f, st=:surface)
xlabel!("fx")
ylabel!("fy")
end
# ╔═╡ a6157e42-aa4b-4293-acf2-83dba61bd87a
md"""
On obtient alors un passe-bas bien plus lisse, et par la même occasion de bien meilleurs résultats:
"""
# ╔═╡ f0c2aaa2-dc18-42a1-be07-5fac922e82d9
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo1bis.png" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo1bischad.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 1ecdda8a-d979-47b1-8e31-1511bd711302
md"""
On observe cette fois-ci de bien meilleurs résultats.
"""
# ╔═╡ ae597ea1-5e3c-45c2-8a5a-808abf4b8a69
md"""
### Modèle ROF
Une seconde approche consiste à décomposer le spectre par une méthode variationnelle (variation totale). Tout comme dans le TP6, on cherche l'image structure telle que celle-ci soit proche de l'aimge originale, mais que ses gradients soient minimisés. La texture est trouvée par complémentarité.
Voici les résultats après 20 itérations :
"""
# ╔═╡ 9da8dadd-e786-4376-9d99-8b98ab44c24c
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo2pilier.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo2lena.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo2macro.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ c475163f-fc56-424f-aa63-4cce2b7b1a91
md"""
### Modèle TV-Hilbert
Cette dernière méthode (itérative) est basiquement une amélioration de la méthode ROF, puisqu'elle contraint cette fois-ci les spectres de u et ū à être égaux dans les basses fréquences.
Voici la progression de cette algorithme sur 1000 itérations:
"""
# ╔═╡ 6c6ff49d-569e-4998-add9-c23051713318
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo3pilier.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo3lena.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP8/exo3macro.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ 299b92f6-8f13-4a51-b831-22f05bcc312d
md"""
# TP9 - Tomographie
L'objectif de ce TP est d'effectuer la transformée inverse d'un sinogramme pour obtenir une image.
Un sinogramme est traditionnellement obtenu à l'issue d'un CT scan:
"""
# ╔═╡ 1eefc8b3-d3f4-4e6f-a93d-5f4ab6bab40c
html"""
<table>
<tr>
<td>
<video src="https://giant.gfycat.com/SpicyWelltodoCarpenterant.mp4" autoplay loop />
</td>
<td>
<video src="https://giant.gfycat.com/VagueCheapGoa.mp4" autoplay loop />
</td>
</tr>
</table>
"""
# ╔═╡ 39cea09d-5088-475b-bd09-19e6671752c8
md"""
## Résolution algébrique
Une première méthode consiste à récupérer l'information des pixels de l'image en résolvant un système linéaire.
On cherche l'image $f$, et l'on connaît les données du détecteur $p$ ainsi que les pixels touchés par chaque rayon X. Le problème se visualise comme ceci :
"""
# ╔═╡ 95a9918a-7223-4f69-98f0-c70425db2355
md"""
$(Resource("https://www.researchgate.net/profile/Omid-Ghasemalizadeh-2/publication/267810464/figure/fig1/AS:392051561648154@1470483784811/Area-integral-model-of-algebraic-reconstruction-technique-ART-in-the-fan-beam-geometry.png"))
"""
# ╔═╡ e82a9905-eb01-43b6-bf2d-050548f33df9
md"""
Si l'on note $W$ la matrice contenant la longueur du trajet d'un rayon X par rapport à chaque pixel de l'image, on peut construite le système:
$$W f = p$$
Comme $W$ est de grande taille on utilise [l'algorithme itératif de Kaczmarz](https://en.wikipedia.org/wiki/Kaczmarz_method) pour résoudre le système.
Voici donc les résultats d'une dizaine d'itérations sur plusieurs images:
"""
# ╔═╡ c8448dfc-1b07-43f0-9728-252a90bbba51
html"""
<table>
<tr>
<th>Image originale</th>
<th>Sinogramme</th>
<th>Image reconstituée</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/image.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/sinogramme_image.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/exo1.webp" />
</td>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/rick.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/sinogramme_rick.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/exo1rick.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ c7f093d9-5a8a-4c18-ac65-f1b03673a14d
md"""
## Résolution par rétroprojection
Une seconde méthode méthode plus rapide revient à "étaler" chaque échantillon du capteur (donc une tranche/colonne de notre sinogramme) sur la grille de pixel.
$(Resource("https://www.dspguide.com/graphics/F_25_16.gif"))
Les résultats que l'on obtient sont plutôt convaincant:
"""
# ╔═╡ 300fdf66-84d8-4ca0-b84e-af1deb3945ac
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/exo2.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP9/exo2_rick.png" />
</td>
</tr>
</table>
"""
# ╔═╡ e0f2cb2c-4b7b-4a24-b0a0-50db401d963e
md"""
## Résolution par utilisation du théorème du profil central
TODO
"""
# ╔═╡ f1dc867a-5683-4218-9723-25fb98d92875
md"""
# TP10 Compression audio
L'objectif de ce TP est de manipuler des fichiers audio via leurs spectrogrammes, notamment pour faire de la compression.
"""
# ╔═╡ 9797d171-bc06-4697-9ead-4a18b4327f49
md"""
## [Spectrogramme](https://en.wikipedia.org/wiki/Spectrogram)
Pour construire le spectrogramme de notre audio, nous allons effectuer la transformée de fourrier sur des morceaux successifs du signal (avec overlapping ou non).
Nous pouvons de même choisir une [fenêtre](https://en.wikipedia.org/wiki/Window_function), pour pondérer nos échantillons lors de la transformée de fourrier, celle-ci influe sur le [_spectral leakage_](https://en.wikipedia.org/wiki/Spectral_leakage).
Il existe bon nombre de fenêtres :
"""
# ╔═╡ f037c06f-db56-4c46-9fa1-88c52e6943b9
begin
local n = 100
plotly()
plot(DSP.rect(n), label="rectangle")
plot!(DSP.hanning(n), label="hanning")
plot!(DSP.hamming(n), label="hamming", visible="legendonly")
plot!(DSP.tukey(n, 0.5), label="tukey", visible="legendonly")
plot!(DSP.cosine(n), label="cosine", visible="legendonly")
plot!(DSP.lanczos(n), label="lanczos", visible="legendonly")
plot!(DSP.triang(n), label="triang", visible="legendonly")
plot!(DSP.bartlett(n), label="bartlett", visible="legendonly")
plot!(DSP.gaussian(n, 0.15), label="gaussian", visible="legendonly")
plot!(DSP.blackman(n), label="blackman", visible="legendonly")
plot!(DSP.kaiser(n, 10), label="kaiser", visible="legendonly")
end
# ╔═╡ e7c84285-20ef-44b6-82af-52ccc6e98163
md"""
Pour le calcul de nos spectrogrammes nous choisiront la fenêtre de Hanning.
Voici donc les spectrogrammes de plusieurs audios:
"""
# ╔═╡ dca00dac-cccd-448a-a4a2-2a38b981d2e7
html"""
<table>
<tr>
<th>Audio</th>
<th>Spectrogramme</th>
</tr>
<tr>
<td>
<figcaption>007</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/007.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_TFCT.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Beethoven</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_TFCT.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Grapelli</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_TFCT.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Mourousi</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_TFCT.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 7439ea28-5e00-4a5a-ad0c-6b23771df9ea
md"""
## Compression acoustique
Une des premières problématiques de l'audio numérique est la compression (bien que moins prévalente aujourd'hui).
"""
# ╔═╡ ae215d41-addb-4694-867c-4720cd6835a1
md"""
### Tronquage
Une première méthode, très simple, est de tronquer le spectrogramme de notre audio au-delà d'une certaine fréquence.
"""
# ╔═╡ 494bedcf-1e3a-44d6-a51f-8c29cfb50830
html"""
<table>
<tr>
<th>Audio</th>
<th>Spectrogramme</th>
</tr>
<tr>
<td>
<figcaption>007</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_restitution1_tronc.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_TFCTT.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Beethoven</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_restitution1_tronc.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_TFCTT.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Grapelli</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_restitution1_tronc.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_TFCTT.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Mourousi</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_restitution1_tronc.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_TFCTT.png" />
</td>
</tr>
</table>
"""
# ╔═╡ d12f3d01-ed74-4204-82e6-19f43e559699
md"""
Puisque nos audio comportent peu d'informations dans les hautes fréquences, cela les impacte peu. De même, les amplitudes des coefficients de Fourrier dans les hautes fréquences étant faibles, leur disparition est difficilement remarquable.
"""
# ╔═╡ f769a263-d8df-4b40-9de7-3d6517095557
md"""
### MP3
Pour compresser plus fortement notre signal nous pouvons reprendre une des idées sur célèbre format de compression [MP3](https://en.wikipedia.org/wiki/MP3). Cette idée consiste à garder sur chaque tranche temporelle les $n$ coeffcients de Fourrier les plus élevés.
Pour $n=100$ on observe alors une compression très forte (~10% de la taille initiale), et pourtant l'audio original est toujours distinguable. Pour des $n$ plus élevés, il devient possible d'obtenir une [transparence](https://en.wikipedia.org/wiki/Transparency_(data_compression)) de l'audio, tout en réduisant la taille du fichier.
"""
# ╔═╡ 247bf9c5-4f0a-4ed2-a604-1e9ef8b64b01
html"""
<table>
<tr>
<th>Audio</th>
<th>Spectrogramme</th>
</tr>
<tr>
<td>
<figcaption>007</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_restitution2.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_mp3.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Beethoven</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_restitution2.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_mp3.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Grapelli</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_restitution2.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_mp3.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Mourousi</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_restitution2.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_mp3.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 9c95a7a3-bd09-4a69-a5b2-ec39f4e3d868
md"""
## Stéganographie
TODO
"""
# ╔═╡ d33bd7b5-904b-4fbf-964e-5f32a34d26a6
md"""
## Débruitage
Une dernière application du spectrogramme est le débruitage. Voyons ici une méthode plutôt simple appelée le [_spectral noise gating_](https://wiki.audacityteam.org/wiki/How_Audacity_Noise_Reduction_Works) notamment implémentée dans le fameux logiciel [Audacity](https://en.wikipedia.org/wiki/Audacity_(audio_editor)).
Cette technique nécessite un extrait audio avec uniquement le bruit (isolé) que l'on souhaite supprimer. Nous pouvons effectuer sur cet extrait une estimation d'une loi normale. À partir de cette estimation, nous définissons un seuil, qui nous permettra par la suite d'atténuer sur notre véritable audio le bruit.
"""
# ╔═╡ 109586dd-7ff7-4d09-981c-173775a0debb
html"""
<table>
<tr>
<th>Audio bruité</th>
<th>Audio débruité</th>
</tr>
<tr>
<td>
<figcaption>007</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_bruit.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_bruit.png" />
</td>
<td>
<figcaption>&nbsp;</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_denoised.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/007_denoised.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Beethoven</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_bruit.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_bruit.png" />
</td>
<td>
<figcaption>&nbsp;</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_denoised.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Beethoven_denoised.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Grapelli</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_bruit.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_bruit.png" />
</td>
<td>
<figcaption>&nbsp;</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_denoised.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Grapelli_denoised.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Mourousi</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_bruit.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_bruit.png" />
</td>
<td>
<figcaption>&nbsp;</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_denoised.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP10/Mourousi_denoised.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 2afd14dd-78e5-4205-93df-c5020ab3253f
md"""
# TP11 - Shazam
L'objectif de ce TP est de trouver des points remarquables dans des enregistrements sonores, pour permettre leur identification.
"""
# ╔═╡ 13f60f6b-3bac-4ba7-9ae6-233fac4bbfdd
md"""
## Calcul des pics spectraux
Une méthode assez simple consiste alors à trouver les maximums locaux du spectrogramme de l'audio.
"""
# ╔═╡ bd53b23f-9aaf-4fce-af04-0b8592d646be
html"""
<table>
<tr>
<td>
<figcaption>Aimee Mann-Wise Up</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo1_2.png" />
</td>
<td>
<figcaption>Altın Gün-Süpürgesi Yoncadan</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo1_4.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Gultrah Sound System-Elli tchelou</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo1_42.png" />
</td>
<td>
<figcaption>Nina Simone-My Baby Just Cares For Me</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo1_69.png" />
</td>
</tr>
</table>
"""
# ╔═╡ f6625979-61d8-4e17-b4fa-0c673c53edea
md"""
Ces maximas permettent de plutôt bien de caractériser les audios. Cependant on peut faire mieux.
"""
# ╔═╡ b7254213-a06b-45c7-a1cb-e799a0cc4844
md"""
## Appariement des pics spectraux
Une amélioration consiste à relier les les pics proches de manière à former des couples de maximas.
"""
# ╔═╡ 4b54f396-a3a6-4fde-bf1c-11b8c4579c21
html"""
<table>
<tr>
<td>
<figcaption>Aimee Mann-Wise Up</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo2_2.png" />
</td>
<td>
<figcaption>Altın Gün-Süpürgesi Yoncadan</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo2_4.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Gultrah Sound System-Elli tchelou</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo2_42.png" />
</td>
<td>
<figcaption>Nina Simone-My Baby Just Cares For Me</figcaption>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP11/exo2_69.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 2ae06afc-1cb2-485a-8b05-e9c76b7aa4e4
md"""
Cette méthode est bien meilleure puisqu'elle permet de rejeter bien plus rapidement les audios qui partagent des maximas avec le son recherché mais qui ne correspondent pas parfaitement.
"""
# ╔═╡ f4f00c5d-7bad-4261-a683-ef374ccde1d8
md"""
### Indexation des paires de pics spectraux
Pour consistuer la base de données avec laquelle nous allons reconnaitre des extraits audio. Nous pouvons créer une hasmap entre les paires de maximas et les sons correspondants.
Pour simplifier la représentation des couples, nous allons les encoder dans un entier non signé de 32 bits:
"""
# ╔═╡ 5399561a-f4d1-465f-88da-9452746625b8
md"""
| (ti, tj, fi, fj) | (ti, tj, fj - fi) | uint32 |
| :------------------: | :---------------: | :--------: |
| (1, 2, 3, 4) | (1, 2, 1) | 0x00010001 |
| (10, 20, 30, 40) | (10, 20, 10) | 0x0913000a |
| (100, 200, 300, 400) | (100, 200, 100) | 0x63c70064 |
| ⋮ | ⋮ | ⋮ |
"""
# ╔═╡ 6256ef07-6188-41fe-bddc-c006cba557bd
md"""
## Reconnaissance musicale
Nous pouvons désormais tester notre algorithme en lui fournissant des courts extraits sonores, dont la musique complète est dans notre base de données.
"""
# ╔═╡ 14f43c7e-fdb1-4bc5-8820-dcc90ebff3b7
md"""
### Reconnaissance simplifié
En calculant les paires de maximas dans le spectrogramme de l'extrait et en trouvant la musique dont les paires correspondent le plus dans notre base de données, on peut alors faire une prédiction.
Cet algorithme reconnait bien les 4 musiques vues plus haut:
- 2 - Aimee Mann-Wise Up
- 4 - Altın Gün-Süpürgesi Yoncadan
- 42 - Gultrah Sound System-Elli tchelou
- 69 - Nina Simone-My Baby Just Cares For Me
De manière générale, sur l'ensemble des musiques de notre base de données, cet algorithme effectue 90% du temps une bonne prédiction.
"""
# ╔═╡ 49c73149-e31a-41f0-8857-7d4a035e098f
md"""
### Reconnaissance avancée
Une méthode pour améliorer nos résultats est de prendre en compte la cohérence entre les instants dapparition des paires de pics de lextrait et ceux du morceau présent dans la base de données.
Pour réaliser cela on peut simplement stocker une approximation du début de l'extrait audio dans l'audio intégrale. En prenant le temps de plus commun ($t_0$) et en filtrant tous nos résultats tels que: $t_0 \leq t \leq t_0 + t_{echantillon}$. On obtient un algorithme d'une précision presque parfaite.
"""
# ╔═╡ 9b4befdd-8ed9-4269-9a7e-0e8afc10eaa9
md"""
# TP12 - Séparation de sources
L'objectif de ce TP est de retrouver les "pistes audio d'un mélange", à partir de l'audio final.
"""
# ╔═╡ abf219da-d9bf-410f-9c3f-14ddbd5fbb34
md"""
## Décomposition harmonique/percussive
"""
# ╔═╡ 8cebf3fb-1db5-42b7-9214-b53e8607bff2
md"""
- Un son harmonique varie peu au cours du temps, son sonagramme fait apparaître des lignes horizontales.
- Un son percussif est bref mais très riche en fréquences, son sonagramme fait apparaître des lignes verticales.
"""
# ╔═╡ 435410b9-d7c5-4aba-989c-58ffbbd93c6e
html"""
<table>
<tr>
<th>Harmoniques (violon)</th>
<th>Percussions (batterie)</th>
</tr>
<tr>
<td>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/violon.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_sono_h.png" />
</td>
<td>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/batterie.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_sono_p.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 54bd596e-4d22-4894-b2e0-63f424f3a540
md"""
On cherche alors à séparer dans un audio les harmoniques et les percussions, soit les traits verticaux et horizontaux sur le spectrogramme.
"""
# ╔═╡ 232c1de0-5ec4-4c64-904c-4bc9acd098df
html"""
<table>
<tr>
<th>Audio</th>
<th>Spectrogramme</th>
</tr>
<tr>
<td>
<figcaption>violon + batterie</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_combined.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_sono_mélange.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 2d5c6ac0-4eed-4f2e-8600-42e605a47f22
md"""
En appliquant un filtre médian "horizontal" de taille (n, 1) sur notre spectrogramme, somme capable de créer un masque représentant les lignes horizontales du spectrogramme. De même avec un filtre médian "vertical" de taille (n, 1), on extrait les lignes verticales du spectrogramme:
"""
# ╔═╡ ca95bbed-d34a-4d15-980a-d3aeaf2bbada
html"""
<table>
<tr>
<th>Masque harmoniques</th>
<th>Masque percussions</th>
</tr>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_mask_h.png" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_mask_p.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 0a850a9e-6c57-4d8f-9d60-07113b004967
md"""
Si l'on applique ainsi ces masques à notre spectrogramme, on obtient alors un résultat correct:
"""
# ╔═╡ a5e5700a-0c01-47b5-ae00-0bdc86cacc4e
html"""
<table>
<tr>
<th>Harmoniques</th>
<th>Percussions</th>
</tr>
<tr>
<td>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_HPSS_harmonique.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_sono_filter_h.png" />
</td>
<td>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_HPSS_percussive.wav" controls></audio>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo1_sono_filter_p.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 7a39222f-6155-41c6-a1fe-66ecc42203b8
md"""
## Décomposition NMF
La factorisation en matrices non négatives (NMF, pour Non-negative Matrix Factorization) consiste à approximer une matrice $S$ à coefficients non négatifs (notre spectrogramme ici), par le produit de deux matrices $D$ et $A$ à coefficients non négatifs de rang $R$. On souhaite avec cette technique décompose un audio en $R$ piste distinctes.
Numériquement cette décomposition revient à appliquer itérativement les opérations suivantes:
$$A^{(k+1)} = A^{(k)} \odot (D^\top S) \oslash (D^\top D A^{(k)})$$
$$D^{(k+1)} = D^{(k)} \odot (S A^\top) \oslash (D^{(k)} A A^\top)$$
avec
$S \in M_{n, m}(R_+)$,
$D^{(0)} \in M_{n, R}(R_+)$,
$A^{(0)} \in M_{R, m}(R_+)$.
Voici le résultat d'une trentaine d'itération pour $R = 4$:
"""
# ╔═╡ 740501ba-2e31-4ff4-968e-1dfb33cbbe78
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_activ_4.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_dico_4.webp" />
</td>
</tr>
<tr>
<td>
<label>Audio final reconstitué</label>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_final_4.wav" controls></audio>
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_sono_4.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ 2a93b54d-4d97-4850-83df-47f0902fc72f
md"""
Nous pouvons de même extraire chacune des composantes, les écouter et intuiter leur utilité:
"""
# ╔═╡ 57c14c36-e5c6-4049-ba2d-79e5825533b1
html"""
<table>
<tr>
<th>Audio</th>
<th>Spectrogramme</th>
</tr>
<tr>
<td>
<figcaption>Composante n°1</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_composante_1_4.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_mask_1_4.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Composante n°2</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_composante_2_4.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_mask_2_4.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Composante n°3</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_composante_3_4.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_mask_4_4.png" />
</td>
</tr>
<tr>
<td>
<figcaption>Composante n°4</figcaption>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_composante_4_4.wav" controls />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_mask_4_4.png" />
</td>
</tr>
</table>
"""
# ╔═╡ 635832fe-1b8c-40c0-9b94-968f449c89e6
md"""
### Variation de R
Il est intéressant de faire varier $R$ pour observer son effet sur le résultat final.
"""
# ╔═╡ 7bb25bbc-f4b4-41d6-bd4b-17df602d0fd5
md"""
#### R = 1
Pour un rang faible, on remarque que chaque "note" aura tendance à contenir plusieurs véritable notes.
"""
# ╔═╡ e6038ed1-95d1-4241-99d4-5789b0715981
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_activ_1.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_dico_1.webp" />
</td>
</tr>
<tr>
<td>
<label>Audio final reconstitué</label>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_final_1.wav" controls></audio>
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_sono_1.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ dd6e50f2-d3d9-4810-a064-8404f9189f7a
md"""
#### R = 25
Pour un rang élevé, on remarque que chaque "note" aura tendance à n'être que des morceaux de notes.
"""
# ╔═╡ d92d7f39-7e4b-40b8-93a3-118c2ea03db5
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_activ_25.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_dico_25.webp" />
</td>
</tr>
<tr>
<td>
<label>Audio final reconstitué</label>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_final_25.wav" controls></audio>
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo2_sono_25.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ be37333b-4350-4405-9f2f-80b3a5f566a7
md"""
On conclut alors qu'il faut choisir minutieusement $R$.
"""
# ╔═╡ 1c38a45b-d50f-48f6-831e-d3b4b2fe9641
md"""
## Décomposition par dictionnaire
"""
# ╔═╡ 0909842d-2a28-4c11-92dd-e95acd070c41
md"""
Pour obtenir de meilleurs résultats nous pouvons initialiser les matrices $D$ et $A$ à partir de notes existantes. De cette manière nous devrions converger vers des minimas plus proches, et les itérations serviront uniquement à affiner les notes initiales pour que celles-ci collent mieux à notre audio.
"""
# ╔═╡ 602994bf-c91d-4f39-8286-405d4bcede38
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3_activ_12.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3_dico_12.webp" />
</td>
</tr>
<tr>
<td>
<label>Audio final reconstitué</label>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3_final.wav" controls></audio>
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3_sono_12.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ 80b2e36a-5464-454a-a5cd-6980e89aa5b7
md"""
Un cas d'application de cette initialisation est de permettre la séparation de sources. En effet si nous initialisons nos matrices à partir de notes de piano et de violons, en coupant en deux nos matrices A et D finales nous sommes maintenant capable de séparer l'audio du violon et du piano.
"""
# ╔═╡ af94b4a6-94bc-4a8e-956a-488c050a7b20
html"""
<table>
<tr>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3bis_activ_24.webp" />
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3bis_dico_24.webp" />
</td>
</tr>
<tr>
<td>
<label>Audio final du piano reconstitué</label>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3bis_final_piano.wav" controls></audio>
<label>Audio final du violon reconstitué</label>
<audio src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3bis_final_violon.wav" controls></audio>
</td>
<td>
<img src="https://fainsil.users.inpt.fr/content/TAV/TP12/exo3bis_sono_24.webp" />
</td>
</tr>
</table>
"""
# ╔═╡ 7f3da391-00db-4d83-a617-044503b92acf
md"""
## Classification des notes du dictionnaire
TODO
"""
# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MAT = "23992714-dd62-5051-b70f-ba57cb901cac"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
[compat]
DSP = "~0.7.5"
Distributions = "~0.25.48"
MAT = "~0.10.3"
Plots = "~1.28.1"
PlutoUI = "~0.7.34"
StatsPlots = "~0.14.33"
"""
# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised
julia_version = "1.8.2"
manifest_format = "2.0"
project_hash = "09ba23f9feefa3ab0528920701e07c04901f9ef3"
[[deps.AbstractFFTs]]
deps = ["ChainRulesCore", "LinearAlgebra"]
git-tree-sha1 = "6f1d9bc1c08f9f4a8fa92e3ea3cb50153a1b40d4"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.1.0"
[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"
[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "af92965fb30777147966f58acb05da51c5616b5f"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.3"
[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"
[[deps.Arpack]]
deps = ["Arpack_jll", "Libdl", "LinearAlgebra", "Logging"]
git-tree-sha1 = "91ca22c4b8437da89b030f08d71db55a379ce958"
uuid = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97"
version = "0.5.3"
[[deps.Arpack_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS_jll", "Pkg"]
git-tree-sha1 = "5ba6c757e8feccf03a1554dfaf3e26b3cfc7fd5e"
uuid = "68821587-b530-5797-8361-c406ea357684"
version = "3.5.1+1"
[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
[[deps.AxisAlgorithms]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
git-tree-sha1 = "66771c8d21c8ff5e3a93379480a2307ac36863f7"
uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
version = "1.0.1"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[deps.BufferedStreams]]
deps = ["Compat", "Test"]
git-tree-sha1 = "5d55b9486590fdda5905c275bb21ce1f0754020f"
uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
version = "1.0.0"
[[deps.Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"
[[deps.Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "4b859a208b2397a7a623a03449e4636bdb17bcf2"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+1"
[[deps.Calculus]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
version = "0.5.1"
[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "c9a6160317d1abe9c44b3beb367fd448117679ca"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.13.0"
[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.2"
[[deps.Clustering]]
deps = ["Distances", "LinearAlgebra", "NearestNeighbors", "Printf", "SparseArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "75479b7df4167267d75294d14b58244695beb2ac"
uuid = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5"
version = "0.14.2"
[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"
[[deps.ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "12fc73e5e0af68ad3137b886e3f7c1eacfca2640"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.17.1"
[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"
[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"
[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.0"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "0.5.2+0"
[[deps.Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"
[[deps.DSP]]
deps = ["Compat", "FFTW", "IterTools", "LinearAlgebra", "Polynomials", "Random", "Reexport", "SpecialFunctions", "Statistics"]
git-tree-sha1 = "3e03979d16275ed5d9078d50327332c546e24e68"
uuid = "717857b8-e6f2-59f4-9121-6e50c889abd2"
version = "0.7.5"
[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"
[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"
[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"
[[deps.DataValues]]
deps = ["DataValueInterfaces", "Dates"]
git-tree-sha1 = "d88a19299eba280a6d062e135a43f00323ae70bf"
uuid = "e7dc6d0d-1eca-5fa6-8ad6-5aecde8b7ea5"
version = "0.4.13"
[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
[[deps.DensityInterface]]
deps = ["InverseFunctions", "Test"]
git-tree-sha1 = "80c3e8639e3353e5d2912fb3a1916b8455e2494b"
uuid = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
version = "0.4.0"
[[deps.Distances]]
deps = ["LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "3258d0659f812acde79e8a74b11f17ac06d0ca04"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.7"
[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
[[deps.Distributions]]
deps = ["ChainRulesCore", "DensityInterface", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns", "Test"]
git-tree-sha1 = "38012bf3553d01255e83928eec9c998e19adfddf"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.48"
[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"
[[deps.DualNumbers]]
deps = ["Calculus", "NaNMath", "SpecialFunctions"]
git-tree-sha1 = "84f04fe68a3176a583b864e492578b9466d87f1e"
uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
version = "0.6.6"
[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"
[[deps.Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ae13fcbc7ab8f16b0856729b050ef0c446aa3492"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.4.4+0"
[[deps.FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"
[[deps.FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "d8a578692e3077ac998b50c0217dfd67f21d1e5f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.4.0+0"
[[deps.FFTW]]
deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
git-tree-sha1 = "505876577b5481e50d089c1c68899dfb6faebc62"
uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
version = "1.4.6"
[[deps.FFTW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c6033cc3892d0ef5bb9cd29b7f2f0331ea5184ea"
uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
version = "3.3.10+0"
[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "deed294cde3de20ae0b2e0355a6c4e1c6a5ceffc"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.8"
[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"
[[deps.Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.93+0"
[[deps.Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"
[[deps.FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "87eb71354d8ec1a96d4a7636bd57a7347dde3ef9"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.4+0"
[[deps.FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"
[[deps.GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "51d2dfe8e590fbd74e7a842cf6d13d8a2f45dc01"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.6+0"
[[deps.GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "RelocatableFolders", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "9f836fb62492f4b0f0d3b06f55983f2704ed0883"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.64.0"
[[deps.GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "a6c850d77ad5118ad3be4bd188919ce97fffac47"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.64.0+0"
[[deps.GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "83ea630384a13fc4f002b77690bc0afeb4255ac9"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.2"
[[deps.Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"
[[deps.Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "a32d672ac2c967f3deb8a81d828afc739c838a06"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+2"
[[deps.Graphite2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
version = "1.3.14+0"
[[deps.Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"
[[deps.HDF5]]
deps = ["Compat", "HDF5_jll", "Libdl", "Mmap", "Random", "Requires"]
git-tree-sha1 = "ed6c28c220375a214d07fba0e3d3382d8edd779e"
uuid = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
version = "0.16.2"
[[deps.HDF5_jll]]
deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "OpenSSL_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "bab67c0d1c4662d2c4be8c6007751b0b6111de5c"
uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
version = "1.12.1+0"
[[deps.HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.17"
[[deps.HarfBuzz_jll]]
deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3"
uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
version = "2.8.1+1"
[[deps.HypergeometricFunctions]]
deps = ["DualNumbers", "LinearAlgebra", "SpecialFunctions", "Test"]
git-tree-sha1 = "65e4589030ef3c44d3b90bdc5aac462b4bb05567"
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
version = "0.3.8"
[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"
[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"
[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"
[[deps.IniFile]]
git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.1"
[[deps.IntelOpenMP_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d979e54b71da82f3a65b62553da4fc3d18c9004c"
uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
version = "2018.0.3+2"
[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[deps.Interpolations]]
deps = ["AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
git-tree-sha1 = "b15fc0a95c564ca2e0a7ae12c1f095ca848ceb31"
uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
version = "0.13.5"
[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"
[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"
[[deps.IterTools]]
git-tree-sha1 = "fa6287a4469f5e048d763df38279ee729fbd44e5"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.4.0"
[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"
[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.1"
[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.3"
[[deps.JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b53380851c6e6664204efb2e62cd24fa5c47e4ba"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.2+0"
[[deps.KernelDensity]]
deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
git-tree-sha1 = "591e8dc09ad18386189610acafb970032c519707"
uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
version = "0.6.3"
[[deps.LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"
[[deps.LERC_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434"
uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
version = "3.0.0+1"
[[deps.LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"
[[deps.LaTeXStrings]]
git-tree-sha1 = "f2355693d6778a178ade15952b7ac47a4ff97996"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.3.0"
[[deps.Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "6f14549f7760d84b2db7a9b10b88cd3cc3025730"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.14"
[[deps.LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.3"
[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "7.84.0+0"
[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.10.2+0"
[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[deps.Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+1"
[[deps.Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"
[[deps.Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"
[[deps.Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"
[[deps.Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c7cb1f5d892775ba13767a87c7ada0b980ea0a71"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+2"
[[deps.Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"
[[deps.Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "c9551dd26e31ab17b86cbd00c2ede019c08758eb"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+1"
[[deps.Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"
[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "e5718a00af0ab9756305a0392832c8952c7426c1"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.6"
[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[deps.MAT]]
deps = ["BufferedStreams", "CodecZlib", "HDF5", "SparseArrays"]
git-tree-sha1 = "971be550166fe3f604d28715302b58a3f7293160"
uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
version = "0.10.3"
[[deps.MKL_jll]]
deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "Pkg"]
git-tree-sha1 = "e595b205efd49508358f7dc670a940c790204629"
uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
version = "2022.0.0+0"
[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"
[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[deps.MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"
[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.0+0"
[[deps.Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"
[[deps.Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"
[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2022.2.1"
[[deps.MultivariateStats]]
deps = ["Arpack", "LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI", "StatsBase"]
git-tree-sha1 = "7008a3412d823e29d370ddc77411d593bd8a3d03"
uuid = "6f286f6a-111f-5878-ab1e-185364afe411"
version = "0.9.1"
[[deps.MutableArithmetics]]
deps = ["LinearAlgebra", "SparseArrays", "Test"]
git-tree-sha1 = "ba8c0f8732a24facba709388c74ba99dcbfdda1e"
uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
version = "1.0.0"
[[deps.NaNMath]]
git-tree-sha1 = "b086b7ea07f8e38cf122f5016af580881ac914fe"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.7"
[[deps.NearestNeighbors]]
deps = ["Distances", "StaticArrays"]
git-tree-sha1 = "16baacfdc8758bc374882566c9187e785e85c2f0"
uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
version = "0.4.9"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[deps.Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"
[[deps.OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.8"
[[deps.Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+1"
[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.20+0"
[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
version = "0.8.1+0"
[[deps.OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "648107615c15d4e09f7eca16307bc821c1f718d8"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.13+0"
[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"
[[deps.Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"
[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"
[[deps.PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"
[[deps.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "7e2166042d1698b6072352c74cfd1fca2a968253"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.6"
[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "13468f237353112a01b2d6b32f3d0f80219944aa"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.2"
[[deps.Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.8.0"
[[deps.PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"
[[deps.PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "6f1b25e8ea06279b5689263cc538f51331d7ca17"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.1.3"
[[deps.Plots]]
deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs", "UnicodeFun", "Unzip"]
git-tree-sha1 = "d05baca9ec540de3d8b12ef660c7353aae9f9477"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.28.1"
[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "8979e9802b4ac3d58c503a20f2824ad67f9074dd"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.34"
[[deps.Polynomials]]
deps = ["LinearAlgebra", "MutableArithmetics", "RecipesBase"]
git-tree-sha1 = "0107e2f7f90cc7f756fee8a304987c574bbd7583"
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
version = "3.0.0"
[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "de893592a221142f3db370f48290e3a2ef39998f"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.4"
[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[deps.Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "0c03844e2231e12fda4d0086fd7cbe4098ee8dc5"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+2"
[[deps.QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "78aadffb3efd2155af139781b8a8df1ef279ea39"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.4.2"
[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[deps.Ratios]]
deps = ["Requires"]
git-tree-sha1 = "dc84268fe0e3335a62e315a3a7cf2afa7178a734"
uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
version = "0.4.3"
[[deps.RecipesBase]]
git-tree-sha1 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.2.1"
[[deps.RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "995a812c6f7edea7527bb570f0ac39d0fb15663c"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.5.1"
[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"
[[deps.RelocatableFolders]]
deps = ["SHA", "Scratch"]
git-tree-sha1 = "cdbd3b1338c72ce29d9584fdbe9e9b70eeb5adca"
uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
version = "0.1.3"
[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"
[[deps.Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "bf3188feca147ce108c76ad82c2792c57abe7b1f"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.7.0"
[[deps.Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+0"
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"
[[deps.SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "6a2f7d70512d205ca8c7ee31bfa9f142fe74310c"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.12"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
[[deps.Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"
[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"
[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "5ba658aeecaaf96923dce0da9e703bd1fe7666f9"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.1.4"
[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "74fb527333e72ada2dd9ef77d98e4991fb185f04"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.4.1"
[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
[[deps.StatsAPI]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "c3d8ba7f3fa0625b062b82853a7d5229cb728b6b"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.2.1"
[[deps.StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "8977b17906b0a1cc74ab2e3a05faa16cf08a8291"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.16"
[[deps.StatsFuns]]
deps = ["ChainRulesCore", "HypergeometricFunctions", "InverseFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "25405d7016a47cf2bd6cd91e66f4de437fd54a07"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "0.9.16"
[[deps.StatsPlots]]
deps = ["AbstractFFTs", "Clustering", "DataStructures", "DataValues", "Distributions", "Interpolations", "KernelDensity", "LinearAlgebra", "MultivariateStats", "Observables", "Plots", "RecipesBase", "RecipesPipeline", "Reexport", "StatsBase", "TableOperations", "Tables", "Widgets"]
git-tree-sha1 = "4d9c69d65f1b270ad092de0abe13e859b8c55cad"
uuid = "f3b207a7-027a-5e70-b257-86293d7955fd"
version = "0.14.33"
[[deps.StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "57617b34fa34f91d536eb265df67c2d4519b8b98"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.5"
[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.0"
[[deps.TableOperations]]
deps = ["SentinelArrays", "Tables", "Test"]
git-tree-sha1 = "e383c87cf2a1dc41fa30c093b2a19877c83e1bc1"
uuid = "ab02a1b2-a7df-11e8-156e-fb1833f50b87"
version = "1.2.0"
[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"
[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "bb1064c9a84c52e277f1096cf41434b675cd368b"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.1"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.1"
[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"
[[deps.URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"
[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
[[deps.UnicodeFun]]
deps = ["REPL"]
git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
version = "0.4.1"
[[deps.Unzip]]
git-tree-sha1 = "34db80951901073501137bdbc3d5a8e7bbd06670"
uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d"
version = "0.1.2"
[[deps.Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"
[[deps.Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4528479aa01ee1b3b4cd0e6faef0e04cf16466da"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.25.0+0"
[[deps.Widgets]]
deps = ["Colors", "Dates", "Observables", "OrderedCollections"]
git-tree-sha1 = "505c31f585405fc375d99d02588f6ceaba791241"
uuid = "cc8bc4a8-27d6-5769-a93b-9d913e69aa62"
version = "0.6.5"
[[deps.WoodburyMatrices]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "de67fa59e33ad156a590055375a30b23c40299d3"
uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
version = "0.5.5"
[[deps.XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"
[[deps.XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"
[[deps.Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"
[[deps.Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"
[[deps.Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"
[[deps.Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"
[[deps.Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"
[[deps.Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"
[[deps.Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"
[[deps.Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"
[[deps.Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"
[[deps.Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"
[[deps.Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"
[[deps.Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"
[[deps.Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"
[[deps.Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"
[[deps.Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"
[[deps.Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"
[[deps.Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"
[[deps.Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"
[[deps.Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"
[[deps.Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"
[[deps.Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"
[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.12+3"
[[deps.Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e45044cd873ded54b6a5bac0eb5c971392cf1927"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.2+0"
[[deps.libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.15.1+0"
[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.1.1+0"
[[deps.libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "2.0.2+0"
[[deps.libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"
[[deps.libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "b910cb81ef3fe6e78bf6acee440bda86fd6ae00c"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+1"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.48.0+0"
[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "17.4.0+0"
[[deps.x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2021.5.5+0"
[[deps.x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.5.0+0"
[[deps.xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""
# ╔═╡ Cell order:
# ╠═bbe20904-d70a-438d-b46a-889ddfb8bab3
# ╟─e08d7569-68d5-4da6-b8fa-085ed6a312f6
# ╠═63ae9b00-344d-4726-8d21-a0bdc75ee8be
# ╟─f7cdde7d-884d-4116-9d40-0b2f68e71e6a
# ╟─8cd04c88-4ea4-4594-b0ab-a145096b3122
# ╟─3a57871a-8ccf-11ec-3c6c-d525f4e8b5c2
# ╟─513cf0e8-b7db-447c-b04a-a2e2fa17fb32
# ╟─97fba57b-8d2a-49c9-a18a-58eb2fe651eb
# ╟─78784cae-f749-4a0f-89cc-46dfa4994d3a
# ╠═f2f61669-6cbd-460d-9a48-454634f86579
# ╠═cf014e6b-9e74-4abf-9532-540f4bb6f43f
# ╟─287bac5f-8932-4d8f-a114-39f4e071ba26
# ╟─e1b0bacc-a173-4084-bb5f-3fbd63527c23
# ╟─1ae5bc6d-16c4-40cb-846e-3b0e177fd0cf
# ╟─b3634cc1-fb5f-4b7b-bfa3-08d1fb787c5f
# ╟─73ed5f0e-4298-4308-bcb8-7c1245ebf0f5
# ╟─d969e49e-0a0c-4bdf-aba2-835586c87e7f
# ╟─c0615490-3d43-4c42-9ac6-c4fdd59aae7f
# ╟─cc938f64-41f9-478b-a136-ae0cc55b5f48
# ╟─aec0ff6e-ac6a-4104-83c1-f70e254602b9
# ╟─dbffcc35-ba67-4468-b1b5-1c38a0fd971e
# ╟─ae8a3f9b-12cf-4802-8ae5-31546d82516d
# ╠═7960408a-d922-43e2-9a61-4aaf0c9e6a39
# ╟─b56c3502-4dd9-4446-acd9-dcb0c667803f
# ╟─76279a22-3afe-46e1-829f-88f5dddfd55c
# ╟─cde13c7c-fbc2-4a8d-9b82-7607d71bb4b1
# ╟─a8843a99-cd5d-47fa-810b-68b56e405af9
# ╠═c3f3a0c3-8523-43aa-8a8a-589f567399c9
# ╟─f60fbae0-a6bd-4791-b38b-9e29ae801a07
# ╟─b95121b9-4b58-4086-bbd8-ecd2893060dd
# ╟─3ff18976-beb4-45ad-aa29-d511e5d3ad0a
# ╟─1b88eacd-754b-4b6b-a8e0-dd35ae37f248
# ╟─acbab47e-2ab5-443a-8d78-f597a4ca08ed
# ╟─230c1126-6717-4a93-9013-5f0c15616904
# ╟─c563818e-d117-47ce-abeb-5b048edad64e
# ╟─650ab9f4-581b-4190-8dd0-91c6d4c86b6b
# ╟─5974ceb3-1e82-4183-9dca-35ccd6b9a9ba
# ╟─27a1f0cc-a6bc-417e-9c58-6b63172f753f
# ╟─c9e530b7-f5f6-4905-8c6c-69ea2440f57d
# ╟─67a5b4cc-00dd-4f8f-a408-27f1d8c60e7c
# ╟─489676a6-b6cb-46dc-9898-feedcc2b61dd
# ╟─8655b8fd-ef08-441f-9186-e2febce6e8f1
# ╟─e16bb608-d97b-40eb-bf6b-4199ee4d1926
# ╟─475aecd7-123f-4e4a-b004-90c2a8a951d8
# ╟─fac7b990-552e-4353-8264-ba0840a95383
# ╟─4bcfb185-4499-422d-9439-6ca4e30c4846
# ╟─174dbb25-12ba-460d-a905-8223f8241964
# ╟─59be55be-5847-4fa1-a4f6-ef8e0658da13
# ╟─2e002135-2a74-47bb-92a6-520d84e85bf3
# ╟─764c0716-5dd2-4d3c-9489-1dd92d29d7c7
# ╟─274dd1f3-2a8c-4d4f-b8e2-a61e2f7bdef4
# ╟─e7ac36db-6496-4f0b-b961-1f180ca3622d
# ╟─d97fd9ab-1422-4c39-85e6-d5306c690310
# ╟─6ce5a769-a05b-4a72-b2a6-46f61c530c3a
# ╟─1a31ebb3-b512-45b9-9f55-2a7b173ff2c7
# ╟─05a64770-4015-438e-83ee-2d84d2277055
# ╟─5a7ec049-1092-4096-b5d9-4d2f84c6be41
# ╟─492cba29-1b5f-4f31-80e6-32dcab025f2f
# ╟─830a7a78-0cb1-4ed1-906e-dd89bad6150f
# ╟─fb735670-d29b-43bd-a88b-ff79d489e0b0
# ╟─05ee0bd3-320d-4d8c-993e-f2e587d78dd8
# ╟─c89378e2-424e-4a46-8c95-7fe97faafddd
# ╟─7cdf9477-d2a6-4846-9f4d-7f1df14f3c58
# ╟─49747114-1019-4478-984a-759887bd9b72
# ╟─9607de34-a8a6-4be3-b603-bf2a20e73fde
# ╟─ca1d1182-f77b-4510-89be-e286818c41f3
# ╟─dc971adc-b6bc-4853-afde-a163e28ebbd3
# ╟─9faecaf8-9a6a-41b4-a7e0-a9a65f039825
# ╟─1a381029-c50a-4631-8982-e9d90543ad44
# ╟─57f39905-d77f-43f9-ab8a-c8941393498a
# ╟─45b844b0-0b5e-433f-9590-70b001dceff2
# ╟─e328d40b-3ba4-4fc7-b52d-aafd62f589f2
# ╟─709a5e59-545d-4b39-8b82-0c9fd2232d3b
# ╟─eaedfd74-c98f-4f8f-b311-09b49839f16a
# ╟─8d896695-50eb-4b66-a60c-10ea12cf2417
# ╟─da89e6ff-5d19-48df-9106-140619914220
# ╟─b133aaf9-9943-4ef7-a7ce-11daea4a3692
# ╟─797f62e6-c573-4edc-90eb-c258592e299a
# ╟─c037620f-6644-40da-a045-2a68ae2464a0
# ╟─ee234a83-5f87-42c5-85d2-9a8a609a132c
# ╟─e90f4ee5-d0dc-4a43-a6ae-471c65f880fe
# ╟─3a5533f2-881a-4b99-96e4-4ce4bd640aec
# ╟─fdfb04a5-3bf1-4a71-8433-1221e8fbcce4
# ╟─a4826006-73e0-4b15-9813-0ab5cabf25ff
# ╟─2e2847a7-d102-46dc-b6cf-3767354ec003
# ╟─ecdf5365-32b2-470e-bfa4-4ade78683841
# ╟─1daa22b9-335f-436d-bf4d-053637374978
# ╟─28a84673-b849-4ccb-a488-9db4fe1818f6
# ╟─3a7e6fd6-9e54-41a0-90e3-a5d047e7e3de
# ╟─4a66976a-9f51-4bfa-971b-2d922134f196
# ╟─32d4e51b-8599-4d93-905a-cb4a719710d3
# ╟─7db8d120-6c7e-4394-9336-9f66de7039be
# ╟─6c2d038e-b1cf-49d1-8e7e-da9d883b8c35
# ╟─2f7ad872-4215-4d84-a30d-d3486979dd32
# ╟─f77b33d9-57a1-4659-9a68-b89184838653
# ╟─aab70bb0-5328-4044-a16a-4291c1583c9b
# ╟─c00837b6-cd15-479e-96cb-065aa4411283
# ╟─5029374a-16c2-47aa-a057-84fcfc0e6c64
# ╟─646d190f-b362-42a1-85ca-d7ba7c1c63ef
# ╟─893b847a-b149-4059-ab12-2536bc86c1b6
# ╟─d6987fe4-d170-4be0-9f71-8956a6eddcea
# ╟─a6157e42-aa4b-4293-acf2-83dba61bd87a
# ╟─f0c2aaa2-dc18-42a1-be07-5fac922e82d9
# ╟─1ecdda8a-d979-47b1-8e31-1511bd711302
# ╟─ae597ea1-5e3c-45c2-8a5a-808abf4b8a69
# ╟─9da8dadd-e786-4376-9d99-8b98ab44c24c
# ╟─c475163f-fc56-424f-aa63-4cce2b7b1a91
# ╟─6c6ff49d-569e-4998-add9-c23051713318
# ╟─299b92f6-8f13-4a51-b831-22f05bcc312d
# ╟─1eefc8b3-d3f4-4e6f-a93d-5f4ab6bab40c
# ╟─39cea09d-5088-475b-bd09-19e6671752c8
# ╟─95a9918a-7223-4f69-98f0-c70425db2355
# ╟─e82a9905-eb01-43b6-bf2d-050548f33df9
# ╟─c8448dfc-1b07-43f0-9728-252a90bbba51
# ╟─c7f093d9-5a8a-4c18-ac65-f1b03673a14d
# ╟─300fdf66-84d8-4ca0-b84e-af1deb3945ac
# ╟─e0f2cb2c-4b7b-4a24-b0a0-50db401d963e
# ╟─f1dc867a-5683-4218-9723-25fb98d92875
# ╟─9797d171-bc06-4697-9ead-4a18b4327f49
# ╟─f037c06f-db56-4c46-9fa1-88c52e6943b9
# ╟─e7c84285-20ef-44b6-82af-52ccc6e98163
# ╟─dca00dac-cccd-448a-a4a2-2a38b981d2e7
# ╟─7439ea28-5e00-4a5a-ad0c-6b23771df9ea
# ╟─ae215d41-addb-4694-867c-4720cd6835a1
# ╟─494bedcf-1e3a-44d6-a51f-8c29cfb50830
# ╟─d12f3d01-ed74-4204-82e6-19f43e559699
# ╟─f769a263-d8df-4b40-9de7-3d6517095557
# ╟─247bf9c5-4f0a-4ed2-a604-1e9ef8b64b01
# ╟─9c95a7a3-bd09-4a69-a5b2-ec39f4e3d868
# ╟─d33bd7b5-904b-4fbf-964e-5f32a34d26a6
# ╟─109586dd-7ff7-4d09-981c-173775a0debb
# ╟─2afd14dd-78e5-4205-93df-c5020ab3253f
# ╟─13f60f6b-3bac-4ba7-9ae6-233fac4bbfdd
# ╟─bd53b23f-9aaf-4fce-af04-0b8592d646be
# ╟─f6625979-61d8-4e17-b4fa-0c673c53edea
# ╟─b7254213-a06b-45c7-a1cb-e799a0cc4844
# ╟─4b54f396-a3a6-4fde-bf1c-11b8c4579c21
# ╟─2ae06afc-1cb2-485a-8b05-e9c76b7aa4e4
# ╟─f4f00c5d-7bad-4261-a683-ef374ccde1d8
# ╟─5399561a-f4d1-465f-88da-9452746625b8
# ╟─6256ef07-6188-41fe-bddc-c006cba557bd
# ╟─14f43c7e-fdb1-4bc5-8820-dcc90ebff3b7
# ╟─49c73149-e31a-41f0-8857-7d4a035e098f
# ╟─9b4befdd-8ed9-4269-9a7e-0e8afc10eaa9
# ╟─abf219da-d9bf-410f-9c3f-14ddbd5fbb34
# ╟─8cebf3fb-1db5-42b7-9214-b53e8607bff2
# ╟─435410b9-d7c5-4aba-989c-58ffbbd93c6e
# ╟─54bd596e-4d22-4894-b2e0-63f424f3a540
# ╟─232c1de0-5ec4-4c64-904c-4bc9acd098df
# ╟─2d5c6ac0-4eed-4f2e-8600-42e605a47f22
# ╟─ca95bbed-d34a-4d15-980a-d3aeaf2bbada
# ╟─0a850a9e-6c57-4d8f-9d60-07113b004967
# ╟─a5e5700a-0c01-47b5-ae00-0bdc86cacc4e
# ╟─7a39222f-6155-41c6-a1fe-66ecc42203b8
# ╟─740501ba-2e31-4ff4-968e-1dfb33cbbe78
# ╟─2a93b54d-4d97-4850-83df-47f0902fc72f
# ╟─57c14c36-e5c6-4049-ba2d-79e5825533b1
# ╟─635832fe-1b8c-40c0-9b94-968f449c89e6
# ╟─7bb25bbc-f4b4-41d6-bd4b-17df602d0fd5
# ╟─e6038ed1-95d1-4241-99d4-5789b0715981
# ╟─dd6e50f2-d3d9-4810-a064-8404f9189f7a
# ╟─d92d7f39-7e4b-40b8-93a3-118c2ea03db5
# ╟─be37333b-4350-4405-9f2f-80b3a5f566a7
# ╟─1c38a45b-d50f-48f6-831e-d3b4b2fe9641
# ╟─0909842d-2a28-4c11-92dd-e95acd070c41
# ╟─602994bf-c91d-4f39-8286-405d4bcede38
# ╟─80b2e36a-5464-454a-a5cd-6980e89aa5b7
# ╟─af94b4a6-94bc-4a8e-956a-488c050a7b20
# ╟─7f3da391-00db-4d83-a617-044503b92acf
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002