Jdi na navigaci předmětu

07: Vizualizace a manipulace s grafikou

Tento notebook je výukovým materiálem v předmětu BI-JUL.21 vyučovaném v zimním semestru akademického roku 2026/2027 Tomášem Kalvodou. Tvorba těchto materiálů byla podpořena NVS FIT.

Hlavní stránkou předmětu, kde jsou i další notebooky a zajímavé informace, je jeho Course Pages stránka.

versioninfo()

1. Vizualizace

Nejen v Julia existuje celá řada nástrojů/knihoven pro vytváření různých grafů, diagramů a vizualizací. V Python ekosystému je asi nejpoúžívanější Matplotlib, který je k dispozici i v Julia (viz PyPlot.jl níže).

Všechny tyto nástroje se snaží dosáhnout podobných cílů a nabízejí různě kvalitní implementace. Dost často bývá otázkou vkusu, který z nástrojů použít. V tomto notebooku ukážeme tři možnosti:

Paleta možností je ale širší. Zmiňme alespoň následující (pro zájemce k prozkoumání):

  • Plots: nadstavba nad několika grafickými balíčky.
  • Gadfly: atraktivní a interaktivní výstup.
  • Winston: malý a rychlý, terminologií vychází s MATLABu.
  • UnicodePlots: zajímavý nástroj vytvářející grafy pomocí unicode symbolů a fungující i v terminálu.

Možností je opravdu mnoho a tento notebook je spíše experimentální.

Při používání tohoto notebooku doporučuji vždy mezi sekcemi (různými balíčky) restartovat jádro. Moduly mají podobné zaměření a mohlo by dojít ke kolizím pojmenování metod.


1.1 GR.jl

Balíček GR.jl poskytuje Julia rozhranní k GR frameworku. GR byl vyvinut skupinou Scientific IT-Systems z Peter Grünberg Institutu na Forschungszentrum Jülich.

  • Mezi jeho přednosti patří rychlost.
  • Mezi nevýhody bych zařadil nekvalitní dokumentaci a nekonzistenci API. (Chyba může být ale na straně Julia balíčku...)

Tento balíček pravděpodobně nemáte nainstalovaný, takže nejprve tento musíme nainstalovat (případně viz pokyny k instalaci systémových závislostí):

(@v1.11) pkg> add GR

Zde v notebooku projdeme jen ty nejužitečnější části balíčku. Také tento balíček použijeme jako jakéhosi průvodci po možnostech, u ostatních se totiž dost opakují. Zvídavého čtenáře odkazujeme na dokumentaci s ukázkami dalších typů grafů.

using GR

Tvorbu jednotlivých grafů můžeme oddělit zavoláním figure, případně subplot. K dispozici také je savefig.


plot a oplot

Pomocí plot nepřekvapivě vykreslíme 2D graf funkce. Poziční argumenty mohou mít následující význam:

  • stejně dlouhé vektory x a y reprezentující souřadnice bodů,
  • vektor x a funkce pro výpočet yy-nové hodnoty,
  • pouze vektor y, nezávisle proměnná pak odpovídá indexům.

Dále plot přijímá několik keyword argumentů, nejzajímavější asi jsou:

  • title: titulek grafu (alternativně metoda title),
  • xlabel: titulek grafu (alternativně metoda xlabel),
  • ylabel: titulek grafu (alternativně metoda ylabel),
  • xlim, ylim: rozsah jednotlivých os (alternativně metody xlim a ylim),

a metoda

  • legend: popisky jednotlivých grafů.

Následuje několik jednoduchých ukázek:

xs = LinRange(-10, 10, 100)
ys = sin.(xs)

figure()
plot(xs, ys, title="\$y=\\sin(x)\$", xlabel="\$x\$", ylabel="\$y\$", ylim=(-1.2, 1.2))

Poznámka: Všimněte si, že vykreslení prvního grafu je (nejen zde, takřka vždy) výrazně pomalejší, než těch následujících. To je způsobeno probíhající JIT kompilací. Tento efekt je v Julia světě označován zkratkou TTFP (Time To First Plot). Tomuto efektu se lze v některých situacích částečně vyhnout pomocí PackageCompiler.jl.


Pokud chceme vykreslit více dat do jednoho obrázku, použijeme k tomu oplot, nastavení popisků atp. se bohužel bere z posledního volání oplus (lze to obejít nastavením těchto parametrů pomocí metod, ne pomocí keyword hodnot).

figure()
legend("\$\\sin\$", "\$\\cos\$")
plot(xs, ys, title="\$y=\\sin(x)\$", xlabel="\$x\$", ylabel="\$y\$", ylim=(-2.2, 1.2))
oplot(xs, cos, title="Trigonometrické funkce", xlabel="\$x\$", ylabel="\$y\$", ylim=(-1.2, 1.2))

Styl čáry lze kontrolovat pomocí klíčů:

  • setlinetype: druh čáry (viz GR.LINETYPE_...),
  • setlinewidth: šířka čáry,
  • setlinecolorind: barva čáry (???).
figure()

setlinetype(GR.LINETYPE_DOUBLE_DOT)
plot(LinRange(-1, 1, 50), exp, linewidth=5)
figure()

plot(LinRange(-1, 1, 3), exp, linewidth=10)

Zde jsem se vzdal a proto:


Cvičení: ???

  1. Pokuste* se změnit barvu čáry například na červenou.

* tj. úspěch není nutný.

figure()

setlinecolorind(GR.COLOR_BROWN) # ???

plot(LinRange(-1, 1, 50), exp)
figure()

setlinecolorind(GR.COLOR_BROWN) # ???

plot(LinRange(-1, 1, 50), exp)
Invalid keyword: linestyle

Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:44
 [2] create_context(dict::Dict{Symbol, Int64})
   @ GR.jlgr ~/.julia/packages/GR/f4wDQ/src/jlgr.jl:899
 [3] create_context
   @ ~/.julia/packages/GR/f4wDQ/src/jlgr.jl:892 [inlined]
 [4] plot(::LinRange{Float64, Int64}, ::Vararg{Union{…}}; kv::@Kwargs{linestyle::Int64})
   @ GR.jlgr ~/.julia/packages/GR/f4wDQ/src/jlgr.jl:1919
 [5] kwcall(::@NamedTuple{linestyle::Int64}, ::typeof(plot), ::LinRange{Float64, Int64}, ::Function)
   @ GR.jlgr ~/.julia/packages/GR/f4wDQ/src/jlgr.jl:1918
 [6] top-level scope
   @ In[49]:5
 [7] eval(m::Module, e::Any)
   @ Core ./boot.jl:489
Some type information was truncated. Use `show(err)` to see complete types.
# colored line drawing example
using LaTeXStrings

f(x) = x*exp(-2x*im)
x = 0:0.01:π
w = f.(x)

plot(real.(w), imag.(w), x, linewidth=5, title=L"f(x) = xe^{-2xi} \forall x \in [0,\pi]", xlabel=L"\Re e\{f(x)\}", ylabel=L"\Im e\{f(x)\}")
methods(plot)
# 3 methods for generic function plot from GR.jlgr:
figure()

xs = -1:0.1:1

plot(xs, exp.(xs), "g-", linewidth=10)
x = collect(0:0.1:10)
p = plot(x, sin.(x))

setcolorrep(100, 0.8, 0.2, 0.2)  # define a custom RGB color at index 100
setlinecolorind(100)             # or: setlinecolorind(inqcolorfromrgb(0.2, 0.6, 0.8))
setlinewidth(2)
polyline(x, cos.(x))             # drawn in the plot's coordinates
updatews()

p

scatter

Scatter plot vykresluje pouze jednotlivé body a neinterpoluje (lineárně) mezi nimi:

figure()

title("\$a_n = (-1)^n\$")
legend("\$a_n\$")
xlabel("\$n\$")
ylabel("\$a_n\$")

plot([(-1)^n for n=0:9])
?legend
figure()

title("\$a_n = (-1)^n\$")
legend("\$a_n\$")
xlabel("\$n\$")
ylabel("\$a_n\$")

x = Float64[j for j=0:9]
y = Float64[(-1)^n for n=0:9]

scatter(x, y, xlim=(-0.2, 9.2), ylim=(-1.2, 1.2))
figure()

title("\$a_n = (-1)^n\$")
legend("\$a_n\$")
xlabel("\$n\$")
ylabel("\$a_n\$")

x = Float64[j for j=0:9]
y = Float64[(-1)^n for n=0:9]
s = 4*ones(10)
c = rand(0:255, 10)

scatter(x, y, s, c, xlim=(-0.2, 9.2), ylim=(-1.2, 1.2))
x
10-element Vector{Float64}:
 0.0
 1.0
 2.0
 3.0
 4.0
 5.0
 6.0
 7.0
 8.0
 9.0
y
10-element Vector{Float64}:
  1.0
 -1.0
  1.0
 -1.0
  1.0
 -1.0
  1.0
 -1.0
  1.0
 -1.0
s
10-element Vector{Float64}:
 4.0
 4.0
 4.0
 4.0
 4.0
 4.0
 4.0
 4.0
 4.0
 4.0
c
10-element Vector{Int64}:
 161
 214
 243
  33
 145
 220
 177
  50
 111
 177
figure()
x = [z/10 for z=0:10]
y = LinRange(0, 1, 11)
s = LinRange(1, 3, 11)
c = LinRange(0, 255, 11)
scatter(x, y, s, c)
x
11-element Vector{Float64}:
 0.0
 0.1
 0.2
 0.3
 0.4
 0.5
 0.6
 0.7
 0.8
 0.9
 1.0
y
11-element LinRange{Float64, Int64}:
 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
s
11-element LinRange{Float64, Int64}:
 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8, 3.0
c
11-element LinRange{Float64, Int64}:
 0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0

Můžeme snadno i změnit barevnou škálu.

figure()

ts = LinRange(0, 4pi, 40)
x  = [t * sin(t) for t in ts]
y  = [t * cos(t) for t in ts]
s  = ts
c  = LinRange(0, 255, 40)
scatter(x, y, s, c, colormap=GR.COLORMAP_INFERNO)

plot3

plot3 je 3D analog čárového grafu. Přijímá tedy tři vektory x, y a z, jejichž složky popořadě tvoří jednotlivé body křivky.

ts = LinRange(0, 20, 400)
xs = [ sin(t) for t in ts ]
ys = [ cos(t) for t in ts ]
zs = ts

figure()
plot3(xs, ys, zs, xlabel="x", ylabel="y", zlabel="z", title="Šroubovice")

histogram a hexbin, Cvičení

Histogram bere vektor x hodnot a jako keyword argument nbins udávající počet přihrádek. Budeme ho ilustrovat na náhodném generátoru bodů na kružnici (vzpomeňte dřívější diskuzi).

n = 50_000
xs = rand(n) .- 0.5
ys = rand(n) .- 0.5;
figure()
scatter(xs, ys, xlim=(-1.2,1.2), ylim=(-1.2,1.2))
for j=1:length(xs)
    r = sqrt(xs[j]^2 + ys[j]^2)
    xs[j] /= r
    ys[j] /= r
end
figure()
scatter(xs, ys)
φs = [ atan(ys[j], xs[j]) for j=1:length(xs) ];
figure()
histogram(φs, nbins=50, xlabel="\$\\varphi\$", ylabel="hits")
savefig("test.pdf")
savefig("test.png")
savefig("test.svg")

Takovýto generátor evidentně preferuje směry odpovídající osám kvadrantů.

Dvourozměrný ekvivalent histogramu (tj. pro dvousložková data) je metoda hexbin, která funguje analogicky, jen rovinu rozděluje na šestiúhelníky.

xs = rand(10^6)
ys = randn(10^6)

figure()
hexbin(xs, ys)
xs = rand(10^6)
ys = randn(10^6)

figure()
hexbin(xs, ys, colormap=GR.COLORMAP_INFERNO, xlabel="A", ylabel="B")

contour a contourf

Contour plot je vizualizace funkce dvou proměnných, která v jejím definičním oboru znázorňuje křivky, kde je daná funkce konstantní (tzv. kontury). Varianta s f v názvu vybarvuje plochy mezi konturami (fill).

Metody očekávají buď trojici vektorů x, y, z, kde x a y definují souřadnice mřížky a z obsahuje funkční hodnoty na této mřížce (tj. z je matice). Místo z můžeme předat funkci dvou proměnných.

figure()
contour(LinRange(-1, 1, 50), LinRange(-2, 2, 50), (x, y) -> 3x^2 + y^2,
    colormap=GR.COLORMAP_AUTUMN, xlabel="\$x\$", ylabel="\$y\$")

Pozor, jak se GR chová k pořadí os v buňce níže!

figure()

xs = LinRange(-10, 10, 150)
ys = LinRange(-15, 15, 100)
zs = [ sin(x)*cos(y) for y in ys, x in xs ]

contourf(xs, ys, zs)
zs
100×150 Matrix{Float64}:
 -0.413286   -0.324264   -0.229408   …   0.229408    0.324264    0.413286
 -0.288886   -0.226659   -0.160355       0.160355    0.226659    0.288886
 -0.13816    -0.1084     -0.0766901      0.0766901   0.1084      0.13816
  0.0251563   0.0197376   0.0139638     -0.0139638  -0.0197376  -0.0251563
  0.18618     0.146077    0.103345      -0.103345   -0.146077   -0.18618
  0.330237    0.259104    0.183309   …  -0.183309   -0.259104   -0.330237
  0.444201    0.34852     0.246569      -0.246569   -0.34852    -0.444201
  0.517687    0.406177    0.287359      -0.287359   -0.406177   -0.517687
  0.543997    0.42682     0.301964      -0.301964   -0.42682    -0.543997
  0.520735    0.408568    0.289051      -0.289051   -0.408568   -0.520735
  0.450019    0.353085    0.249798   …  -0.249798   -0.353085   -0.450019
  0.338295    0.265426    0.187782      -0.187782   -0.265426   -0.338295
  0.195743    0.15358     0.108654      -0.108654   -0.15358    -0.195743
  ⋮                                  ⋱                          
  0.338295    0.265426    0.187782      -0.187782   -0.265426   -0.338295
  0.450019    0.353085    0.249798      -0.249798   -0.353085   -0.450019
  0.520735    0.408568    0.289051   …  -0.289051   -0.408568   -0.520735
  0.543997    0.42682     0.301964      -0.301964   -0.42682    -0.543997
  0.517687    0.406177    0.287359      -0.287359   -0.406177   -0.517687
  0.444201    0.34852     0.246569      -0.246569   -0.34852    -0.444201
  0.330237    0.259104    0.183309      -0.183309   -0.259104   -0.330237
  0.18618     0.146077    0.103345   …  -0.103345   -0.146077   -0.18618
  0.0251563   0.0197376   0.0139638     -0.0139638  -0.0197376  -0.0251563
 -0.13816    -0.1084     -0.0766901      0.0766901   0.1084      0.13816
 -0.288886   -0.226659   -0.160355       0.160355    0.226659    0.288886
 -0.413286   -0.324264   -0.229408       0.229408    0.324264    0.413286
figure()

xs = LinRange(-10, 10, 150)
ys = LinRange(-15, 15, 100)
zs = [ exp(-(x - y)^2) for y in ys, x in xs ]

contourf(xs, ys, zs)

heatmap

heatmap funguje v podstatě stejně jako contour, jen se nesnaží nalézt křivky konstantní hodnoty funkce (což je netriviální! Odpovídající algoritmus marching cubes algorithm (zde squares -- 2D varianta) byl ještě do relativně nedávná doby pod patentem.)

figure()

xs = LinRange(-10, 10, 200)
ys = LinRange(-10, 10, 200)
zs = [ sin(x)*cos(y/3) for x in xs, y in ys ]

heatmap(zs, colormap=GR.COLORMAP_FLAME)
figure()

# Create example data
x = LinRange(-2, 2, 40)
y = LinRange(0, pi, 20)
z = sin.(x') .+ cos.(y)
# Draw the heatmap
heatmap(z)

Z neznámého důvodu od jisté doby heatmap nefunguje v GR... :-/

?heatmap
search: heatmap polarheatmap floatmax

Draw a heatmap.

This function uses the current colormap to display a two-dimensional array as a heatmap. The array is drawn with its first value in the bottom left corner, so in some cases it may be neccessary to flip the columns (see the example below).

By default the function will use the column and row indices for the x- and y-axes, respectively, so setting the axis limits is recommended. Also note that the values in the array must lie within the current z-axis limits so it may be neccessary to adjust these limits or clip the range of array values.

:param data: the heatmap data

Usage examples:

.. code-block:: julia

julia> # Create example data
julia> x = LinRange(-2, 2, 40)
julia> y = LinRange(0, pi, 20)
julia> z = sin.(x') .+ cos.(y)
julia> # Draw the heatmap
julia> heatmap(z)

imshow

Tato metoda je velmi užitečná. Umožňuje nám graficky znázornit matici.

h = [ i + j - 1 for i=1:10, j=1:10 ]

figure()
imshow(h, colormap=GR.COLORMAP_INFERNO)
figure()

xs = LinRange(-10, 10, 200)
ys = LinRange(-10, 10, 200)
zs = [ sin(x)*cos(y/3) for x in xs, y in ys ]

imshow(zs, colormap=GR.COLORMAP_WINTER)
figure()

xs = LinRange(-10, 10, 200)
ys = LinRange(-10, 10, 200)
zs = [ sin(x)*cos(y) for x in xs, y in ys ]

imshow(zs, colormap=GR.COLORMAP_FLAME)

Cvičení: Julia v Julia

Uvažme zobrazení f:C→Cf: \mathbb{C} \to \mathbb{C} zadané předpisem fc(z)=z2+cf_c(z) = z^2 + c, pro pevně zadanou konstantu c∈Cc \in \mathbb{C}. Zkoumáme pak množinu bodů z∈Cz\in\mathbb{C}, pro které je posloupnost (fcn(z))n=1∞(f^n_c(z))_{n=1}^\infty omezená (mocnina zde nyní označuje opakované skládání zobrazení).

Přibližně se lze k této množině blížit takto: zvolme dostatečně velké R>0R > 0 a maximální počet iterací M∈NM \in \mathbb{N} a pro zz z nějaké zvolené množiny (typicky mřížka) naměřme, kolik iterací musíme provést, než se dostaneme dále než RR od počátku.

Proveďte tento výpočet pro c=−0.8+0.156ic = -0.8 + 0.156i a c=(φ−2)+(φ−1)ic = (\varphi - 2) + (\varphi - 1)i.

function julia(c::Complex{Float64}, R::Float64, reals, imags; imax::Int64=1_000)
    data = fill(imax, length(reals), length(imags))
    f(u) = u^2 + c
    
    for j = axes(reals, 1), k = axes(imags, 1)
        z = reals[j] + 1im * imags[k]
        
        for n = 1:imax
            z = f(z)
            if abs(z) > R
                data[j, k] = n
                break
            end
        end
    end
    
    return reverse(transpose(data), dims=1)
end
julia (generic function with 1 method)
res = LinRange(-2, 2, 10)
ims = LinRange(-1, 1, 10)
data1 = julia(-0.8 + 0.156im, 4.0, res, ims)
10×10 Matrix{Int64}:
 1  2   2    3    3    3    2   2  2  1
 2  2   3    3    5    4    3   2  2  1
 2  2   4    5  116   11    4   3  2  2
 2  3   9    8   65  108    7   4  3  2
 2  4  60   19   21  131  125   7  3  2
 2  3   7  125  131   21   19  60  4  2
 2  3   4    7  108   65    8   9  3  2
 2  2   3    4   11  116    5   4  2  2
 1  2   2    3    4    5    3   3  2  2
 1  2   2    2    3    3    3   2  2  1
res = LinRange(-2, 2, 20)
ims = LinRange(-1, 1, 10)
data1 = julia(-0.8 + 0.156im, 40.0, res, ims)
10×20 Matrix{Int64}:
 3  3  3   3   4    4    4    4   4  …   4    4    4    4   3   3  3  3  3
 3  3  3   4   4    4    5    5   6      5    5    4    4   4   4  3  3  3
 3  3  4   4   5    6    6    7  14      8    6    5    5   4   4  4  3  3
 3  4  4   5   8  181   11  188  44     33    8    8   14   5   4  4  3  3
 3  4  5  24  54   43   20   54  21     15   88  415   94   8   6  4  4  3
 3  4  4   6   8   94  415   88  15  …  21   54   20   43  54  24  5  4  3
 3  3  4   4   5   14    8    8  33     44  188   11  181   8   5  4  4  3
 3  3  4   4   4    5    5    6   8     14    7    6    6   5   4  4  3  3
 3  3  3   4   4    4    4    5   5      6    5    5    4   4   4  3  3  3
 3  3  3   3   3    4    4    4   4      4    4    4    4   4   3  3  3  3
imshow(data1, colormap=GR.COLORMAP_BLUESCALE)
imshow([0 1 0; 0 0 1])
res = LinRange(-1.5, 1.5, 600)
ims = LinRange(-1, 1, 400)
data2 = julia(-0.5125 + 0.5213im, 4.0, res, ims, imax=1_000);
imshow(data2, colormap=GR.COLORMAP_BLUESCALE)
res = LinRange(-2, 2, 600)
ims = LinRange(-1, 1, 400)
data1 = julia(-0.8 + 0.156im, 40.0, res, ims);
imshow(data1, colormap=GR.COLORMAP_INFERNO)
res = LinRange(-0.3, -0.1, 800)
ims = LinRange(-0.35, -0.15, 800)
data2 = julia(-0.8 + 0.156im, 4.0, res, ims);
imshow(data2, colormap=GR.COLORMAP_INFERNO)

Nyní obrázky generované pomocí PyPlot (viz níže).

using PyPlot
res = LinRange(-2, 2, 600)
ims = LinRange(-1, 1, 400)
data2 = julia(-0.8 + 0.156im, 4.0, res, ims);

fig, ax = plt.subplots()
ax.imshow(data2, extent=[res[1], res[end], ims[1], ims[end]])
res = LinRange(0, 2, 600)
ims = LinRange(0, 1, 400)
data2 = julia(-0.8 + 0.156im, 4.0, res, ims);

fig, ax = plt.subplots()
ax.imshow(data2, extent=[res[1], res[end], ims[1], ims[end]])
res = LinRange(-2, 2, 1000)
ims = LinRange(-1, 1, 1000)
c = (MathConstants.golden - 2) + (MathConstants.golden - 1)*1im
data3 = julia(c, 5.0, res, ims);
fig, ax = plt.subplots()
ax.imshow(data3, extent=[res[1], res[end], ims[1], ims[end]], cmap="Reds")
res = LinRange(0.25, 0.75, 1000)
ims = LinRange(-0.75, -0.25, 1000)
c = (MathConstants.golden - 2) + (MathConstants.golden - 1)*1im
data4 = julia(c, 5.0, res, ims);
fig, ax = plt.subplots()
ax.imshow(data4, extent=[res[1], res[end], ims[1], ims[end]], cmap="Reds")

1.2 PyPlot.jl

PyPlot.jl je v podstatě Julia rozhraní k Matplotlib Pyhon knihovně, přesněji k funkcím v matplotlib.pyplot Python modulu.

Pokud znáte tuto knihovnu, pak by pro vás tato varianta měla být asi nejpříjemnější na používání. Pro ostatní mohou být užitečné tyto taháky.

  • Výhody: lepší dokumentace, obrovská základna uživatelů, ozkoušený a robustní software.
  • Nevýhody: pomalejší, závislost na Pythonu.

Budete potřebovat mít nainstalovánu tuto knihovnu ($ pip install matplotlib) a poté nainstalovat Julia balíček PyPlot.jl:

(@v1.11) pkg> add PyPlot

Pro porovnání ukažme jen základní typ grafu a pak zkusme vytvořit komplikovanější diagram v cvičení.

# Doporučuji restartovat jádro!
using PyPlot

plot

Logika této metody je velmi podobná jako analogické metody v GR.jl výše.

xs = LinRange(0, 10, 200)
ys = [ sin(x) - sin(2x)/2 + sin(3x)/3 - sin(4x)/4 for x in xs ]

f = figure()
plot(xs, ys, color="red", linewidth=4.0, linestyle="--")
grid()
title("Můj úžasný graf")
xlabel("\$x\$")
ylabel("\$y\$");

Výsledné obrázky lze opět snadno uložit.

f.savefig("matplotlib_fig.png")
f.savefig("matplotlib_fig.pdf")

K dispozici také je několik přednastavených stylů, například:

plt.style.available
28-element Vector{String}:
 "Solarize_Light2"
 "bmh"
 "classic"
 "dark_background"
 "fast"
 "fivethirtyeight"
 "ggplot"
 "grayscale"
 "petroff10"
 "petroff6"
 "petroff8"
 "seaborn-v0_8"
 "seaborn-v0_8-bright"
 ⋮
 "seaborn-v0_8-darkgrid"
 "seaborn-v0_8-deep"
 "seaborn-v0_8-muted"
 "seaborn-v0_8-notebook"
 "seaborn-v0_8-paper"
 "seaborn-v0_8-pastel"
 "seaborn-v0_8-poster"
 "seaborn-v0_8-talk"
 "seaborn-v0_8-ticks"
 "seaborn-v0_8-white"
 "seaborn-v0_8-whitegrid"
 "tableau-colorblind10"
f = figure()
plt.style.use("seaborn-v0_8")
plot(xs, ys, color="red", linewidth=4.0, linestyle="--")
title("Můj úžasný graf")
xlabel(L"$x$")
ylabel(L"$y$");

Cvičení: Řešení ODE

V předchozí lekci jsme si ukázali, jak vyřešit obyčejnou diferenciální rovnici y′=Ayy' = \mathbb{A} y, y(0)=y0∈Cny(0) = y_0 \in \mathbb{C}^n, pomocí maticové exponenciály. Řešením byla vektorová funkce y(t)=exp⁡(tA)y0y(t) = \exp(t\mathbb{A}) y_0.

Konkrétně jsme zvolili

A=(0−110).\mathbb{A} = \begin{pmatrix} 0 & -1 \\ 1 & 0 \end{pmatrix}.

Pokuste se vizualizovat tato řešení (v rovině) pro pár počátečních podmínek.

Poznámka: Viz předchozí notebook.

A = Float64[0 -1; 1 0]

function solution(matrix::Matrix{Float64}, initial_condition::Vector{Float64}, timestamps)
    data = zeros(length(timestamps),2)
    y    = initial_condition

    for j in axes(data, 1)
        data[j, :] = y
        y = exp(ts[j] * matrix) * initial_condition
    end

    return data
end
solution (generic function with 1 method)
ts = LinRange(0, 3, 100)
s1 = solution(A, [1.0, 0.0], ts)
s2 = solution(A, [-0.5, 0.0], ts)
s3 = solution(A, [1.0, 1.0], ts);
f = figure()

scatter(s1[:,1], s1[:,2], color="red")
scatter(s2[:,1], s2[:,2], color="green")
scatter(s3[:,1], s3[:,2], color="blue")
grid()
title("Trajektorie")
xlabel("\$y_1\$")
ylabel("\$y_2\$");
ts = LinRange(0, 7, 350)
s1 = solution(A, [1.0, 0.0], ts)
s2 = solution(A, [-0.5, 0.0], ts)
s3 = solution(A, [1.0, 1.0], ts);
fig, ax = plt.subplots()
ax.set_aspect("equal")

scatter(s1[:,1], s1[:,2], color="red")
scatter(s2[:,1], s2[:,2], color="green")
scatter(s3[:,1], s3[:,2], color="blue")
grid()
title("Trajektorie")
xlabel("\$y_1\$")
ylabel("\$y_2\$");

1.3 PGFPlotsX.jl

Tento balíček využívá pro generování grafů LaTeX balíček PGFPlots.

  • Výhody: zřejmě nejkvalitnější výstup, vhodný i do publikací.
  • Nevýhody: ideálně vyžaduje familiaritu s PGFPlots, náročnější instalace.

Musíte mít lokálně nainstalovaný LaTeX s balíčkem PGFPlots. Poté jen přidáme Julia balíček

(@v1.11) pkg> add PGFPlotsX
using PGFPlotsX

Takřka nutností pro používání tohoto balíčku je prostudování si PGFPlots manuálu, který je velmi podrobný a názorný.

xs = LinRange(0, 20, 100)
ys = [ cos(x)/sqrt(x+1) for x in xs ];

Makro @pgf nám umožňuje parsovat výrazy, které by nebyly validní Julia kód, například parametry grafu:

options = @pgf {
    title => "Tlumená oscilace",
    xlabel => "\$x\$",
    ylabel => "\$y\$",
    grid => "major"
}
[title={Tlumená oscilace}, xlabel={$x$}, ylabel={$y$}, grid={major}] 

Grafické objekty pak vytvoříme pomocí konstruktorů, jejichž jména kopírují názvy LaTeX maker a prostředí.

g = Axis(options, Plot(Table(xs, ys)))

Můžeme si i prohlédnout LaTeX kód, který se vygeneroval:

print_tex(g)
\begin{axis}[title={Tlumená oscilace}, xlabel={$x$}, ylabel={$y$}, grid={major}]
    \addplot
        table[row sep={\\}]
        {
            \\
            0.0  1.0  \\
            0.20202020202020204  0.893554245654044  \\
            0.4040404040404041  0.775983556965637  \\
            0.6060606060606061  0.6485406037552491  \\
            0.8080808080808082  0.5138048905565358  \\
            1.0101010101010102  0.3750758675276676  \\
            1.2121212121212122  0.23601775972659012  \\
            1.4141414141414141  0.10041183197927928  \\
            1.6161616161616164  -0.028037702725686314  \\
            1.8181818181818183  -0.14586492596796072  \\
            2.0202020202020203  -0.24997819449287356  \\
            2.2222222222222223  -0.3377729061720941  \\
            2.4242424242424243  -0.4072208430616047  \\
            2.6262626262626263  -0.4569351605487472  \\
            2.8282828282828283  -0.4862101377254743  \\
            3.0303030303030303  -0.4950352538653798  \\
            3.2323232323232327  -0.484083816106636  \\
            3.4343434343434343  -0.45467712002295513  \\
            3.6363636363636367  -0.4087259023159157  \\
            3.8383838383838382  -0.348651588805861  \\
            4.040404040404041  -0.2772905093754423  \\
            4.242424242424242  -0.19778481180957286  \\
            4.444444444444445  -0.11346423350720694  \\
            4.646464646464646  -0.027723165846137755  \\
            4.848484848484849  0.056102440473789546  \\
            5.050505050505051  0.13485383368998885  \\
            5.252525252525253  0.2056596042718416  \\
            5.454545454545454  0.2660333065780028  \\
            5.656565656565657  0.31395516431249626  \\
            5.858585858585858  0.3479350974447313  \\
            6.0606060606060606  0.3670550360768516  \\
            6.262626262626263  0.37098928216349664  \\
            6.464646464646465  0.3600025126700308  \\
            6.666666666666666  0.334925856167946  \\
            6.8686868686868685  0.2971122875082646  \\
            7.070707070707071  0.2483733418065222  \\
            7.272727272727273  0.19089982160318078  \\
            7.474747474747475  0.1271697352276353  \\
            7.6767676767676765  0.05984713990471977  \\
            7.878787878787879  -0.00832414513650889  \\
            8.080808080808081  -0.07462785190881611  \\
            8.282828282828284  -0.13647951821882634  \\
            8.484848484848484  -0.19152377168661003  \\
            8.686868686868687  -0.23772105099838264  \\
            8.88888888888889  -0.27342062443246246  \\
            9.09090909090909  -0.29741733199031645  \\
            9.292929292929292  -0.30899013494359545  \\
            9.494949494949495  -0.30792127917779194  \\
            9.696969696969697  -0.2944956380370906  \\
            9.8989898989899  -0.2694805669827386  \\
            10.101010101010102  -0.2340873468053269  \\
            10.303030303030303  -0.1899159860547257  \\
            10.505050505050505  -0.13888577066265204  \\
            10.707070707070708  -0.08315446652729264  \\
            10.909090909090908  -0.02502948025897692  \\
            11.11111111111111  0.033125449750166375  \\
            11.313131313131313  0.0889843341231118  \\
            11.515151515151516  0.14035010633054038  \\
            11.717171717171716  0.18523937201735224  \\
            11.919191919191919  0.22195751208390194  \\
            12.121212121212121  0.24916133517051656  \\
            12.323232323232322  0.265906996654124  \\
            12.525252525252526  0.27168150542183467  \\
            12.727272727272727  0.26641680216167013  \\
            12.92929292929293  0.25048608730278416  \\
            13.131313131313131  0.22468277566227216  \\
            13.333333333333332  0.1901831310210957  \\
            13.535353535353536  0.1484942611195437  \\
            13.737373737373737  0.10138970795629311  \\
            13.939393939393941  0.050835328894516835  \\
            14.141414141414142  -0.0010914861119250925  \\
            14.343434343434343  -0.05228598940028595  \\
            14.545454545454547  -0.10070027530470042  \\
            14.747474747474747  -0.14442417523563253  \\
            14.94949494949495  -0.1817596870160649  \\
            15.151515151515152  -0.21128606825729157  \\
            15.353535353535353  -0.23191312435899492  \\
            15.555555555555555  -0.24292071556420858  \\
            15.757575757575758  -0.24398307403676198  \\
            15.95959595959596  -0.23517713813765204  \\
            16.161616161616163  -0.2169747521993337  \\
            16.363636363636363  -0.19021922062382077  \\
            16.565656565656568  -0.1560873198104007  \\
            16.767676767676768  -0.11603843624934314  \\
            16.96969696969697  -0.07175299227959078  \\
            17.17171717171717  -0.02506272370473082  \\
            17.373737373737374  0.022124329366016028  \\
            17.575757575757574  0.06790107619516698  \\
            17.77777777777778  0.11043777281991562  \\
            17.97979797979798  0.14805452738856317  \\
            18.18181818181818  0.1792870624657542  \\
            18.383838383838384  0.20294317957898042  \\
            18.585858585858585  0.21814776617432916  \\
            18.78787878787879  0.2243746621565776  \\
            18.98989898989899  0.22146424265126738  \\
            19.19191919191919  0.20962615358154776  \\
            19.393939393939394  0.1894272337188925  \\
            19.595959595959595  0.1617652471816049  \\
            19.7979797979798  0.12782961052335254  \\
            20.0  0.08905080657207223  \\
        }
        ;
\end{axis}

PGFPlots nabízí bohaté možnosti změny stylu a prezentace:

@pgf Axis(options, Plot({"red", "line width" => "2pt" }, Table(xs, ys)))

PGFPlots zvládá i 3D grafy (v LaTeXu!!!).

@pgf Axis({"colorbar"},
    Plot3({"surf", "samples" => "15", "domain" => "0:1", "y domain" => "-1:1"}, Expression("x^2 - y^2"))
)

1.4 Makie.jl

Do čtvrtice ještě jedna poměrně zajímavá možnost. Pro podrobnější prozkoumání doporučuji vaší pozornosti galerii.

  • Výhody: poměrně velmi dobrá dokumentace se spoustou příkladů.
  • Nevýhody: vleče se při prvním startu.

Balíček nabízí několik backendů: CairoMakie.jl (2D), GLMakie.jl a WGLMakie.jl. Instalace je opět standardní a nebudu ji zde opakovat.

using CairoMakie
[ Info: Precompiling CairoMakie [13f3f980-e62b-5c42-98c6-ff1f3baf88f0] (caches not reused: 1 for dependency source file not found)
Precompiling packages...
   0.9 s  ✓ Animations
   0.9 s  ✓ FastInterpolations → FastInterpolationsColorVectorSpaceExt
   1.0 s  ✓ StructArrays
   0.8 s  ✓ StructArrays → LinearAlgebraExt
   0.8 s  ✓ StructArrays → AdaptExt
   1.0 s  ✓ Accessors → StructArraysExt
   1.2 s  ✓ StructArrays → SparseArraysExt
   1.3 s  ✓ StructArrays → StaticArraysExt
   2.8 s  ✓ ShaderAbstractions
   3.0 s  ✓ FreeTypeAbstraction
   6.0 s  ✓ MathTeXEngine
 229.1 s  ✓ Makie
   6.3 s  ✓ IntervalArithmetic → IntervalArithmeticMakieExt
   7.3 s  ✓ IntervalSets → IntervalSetsMakieExt
  57.2 s  ✓ CairoMakie
  15 dependencies successfully precompiled in 304 seconds. 282 already precompiled.
xs = LinRange(0, 20, 200)
ys = sin.(xs) .* xs;
fig, ax, plt = lines(xs, ys)
lines!(xs, cos.(xs))

ax.xlabel = "x"
ax.ylabel = "y"
ax.title = "Pokusný graf"

current_figure()

Cvičení: Mandelbrotův fraktál

function mandelbrot(R::Float64, reals, imags; imax::Int64=50)
    data = fill(imax, length(reals), length(imags))
    f(u, c) = u^2 + c 
    
    for j = axes(reals, 1), k = axes(imags, 1)
        c = reals[j] + 1im * imags[k]
        z = zero(c)
        
        for n = 1:imax
            z = f(z, c)
            if abs(z) > R
                data[j, k] = n
                break
            end
        end
    end
    
    return data
end
mandelbrot (generic function with 1 method)
res = LinRange(-2, 1, 400)
ims = LinRange(-1, 1, 200)
data1 = mandelbrot(10.0, res, ims);
heatmap(res, ims, data1, colormap = :deep)
res = LinRange(-1.51, -1.49, 600)
ims = LinRange(-0.01, 0.01, 400)
data1 = mandelbrot(15.0, res, ims, imax=50);
heatmap(res, ims, data1, colormap = :inferno)
res = LinRange(-1.505, -1.500, 600)
ims = LinRange(-0.002, 0.002, 400)
data1 = mandelbrot(50.0, res, ims, imax=200);
heatmap(res, ims, data1, colormap = :viridis)

3D grafy

Makie poskytuje dva backendy schopné vykreslovat 3D grafiku, GLMakie.jl a WGLMakie.jl.

using GLMakie

xs = LinRange(0, 10, 100)
ys = LinRange(0, 15, 100)
zs = [cos(x) * sin(2*y) + x + y for x in xs, y in ys]

surface(xs, ys, zs, axis=(type=Axis3,))

Cvičení: Vizualizace PRNG založeného na logistickém zobrazení

function my_prng(n, seed)
    data = zeros(n)
    data[1] = seed
    
    for j = 2:n
        data[j] = 4data[j-1]*(1 - data[j-1])
    end
    
    return data
end
data = my_prng(10^6, 0.7500000001);

Jak tato posloupnost vypadá?

using CairoMakie
fig, ax, plt = scatter(1:1000, data[1:1000])

ax.xlabel = "n"

current_figure()
hist(data, bins=50, normalization = :pdf)
f(x) = acos(1-2x)/pi
udata = f.(data);
hist(udata, bins=50, normalization = :pdf)

1.5 Interact.jl

Tento balíček umožňuje vytvářet interaktivní vizualizace. Pokyny k instalaci jsou tentokrát malinko komplikovanější, viz Getting Started.

using Interact, CairoMakie
xs = LinRange(-5, 20, 200);
@manipulate for a=0:0.01:1, ω=0:0.01:2
    ys  = [ a * sin(ω * x) for x in xs  ]
    vbox(lines(xs, ys))
end

1.6 UnicodePlots.jl

Samostatnou kapitolou a disciplínou je vytváření grafů pomocí unicode znaků (lze i v terminálu, vyzkoušejte!).

using UnicodePlots
lineplot(LinRange(0, 10, 100), sin.(LinRange(0, 10, 100)), title="Sinus")
      ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Sinus⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 
      ┌────────────────────────────────────────┐ 
    1 │⠀⠀⠀⠀⡰⠉⠉⠑⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠉⠑⡄⠀⠀⠀⠀⠀⠀│ 
      │⠀⠀⠀⡰⠁⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀│ 
      │⠀⠀⢠⠃⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠸⡀⠀⠀⠀⠀│ 
      │⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠀⠀⠀⠀⢧⠀⠀⠀⠀│ 
      │⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠁⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀⠀⠀│ 
      │⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀│ 
      │⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀│ 
      │⠧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢵⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢵⠤⠤│ 
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀│ 
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀│ 
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇│ 
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⢰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘│ 
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
   -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⣀⣀⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
      └────────────────────────────────────────┘ 
      ⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10⠀ 
heatmap(randn(100, 100))
       ┌──────────────────────────────┐  4  
   100 │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ ┌──┐
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
       │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│
     1 │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ └──┘
       └──────────────────────────────┘ -4  
        1                          100      
using Images, FileIO

img_Gauss = load("../homeworks/B211/files/gauss_portret.jpg")
img_Gauss = map(x -> Float64(x.r), img_Gauss)

heatmap(reverse(img_Gauss, dims=1))

2. Images.jl, Colors.jl, ImageMagick.jl, ImageMetadata.jl...

Tyto balíčky se specializují přímo na práci s obrázky, jejich vytváření a jejich editaci. Nespadají tedy přímo do kategorie předchozích balíčků. Využijete je třeba při rychlém generování obrázků z matic nebo při strojovém před/zpracování obrázků (viz ImageFeatures.jl, ImageTransformation.jl nebo ImageSegmentation.jl, ImageDraw.jl).

Instalace je opět standardní (]add Images, ]add Colors).

using Images, Colors
[ Info: Precompiling PolynomialsMakieExt [1531507d-725e-5582-9e59-18df98086d4b]
Precompiling packages...
   5.6 s  ✓ Polynomials → PolynomialsMakieExt
  1 dependency successfully precompiled in 9 seconds. 295 already precompiled.
[ Info: Precompiling ArrayInterfaceFillArraysExt [103188a2-059f-504d-80a8-05c2106ecd92]
Precompiling packages...
   0.6 s  ✓ ArrayInterface → ArrayInterfaceFillArraysExt
  1 dependency successfully precompiled in 1 seconds. 3 already precompiled.

2.1 Vytvoření obrázku

img_data = rand(400, 600)
img_data[1:50, 1:50] .= 0
img_data
400×600 Matrix{Float64}:
 0.0        0.0        0.0        …  0.681026   0.920322    0.596755
 0.0        0.0        0.0           0.336384   0.975388    0.0189425
 0.0        0.0        0.0           0.928234   0.00987076  0.101844
 0.0        0.0        0.0           0.515735   0.5151      0.281218
 0.0        0.0        0.0           0.0056696  0.161269    0.231896
 0.0        0.0        0.0        …  0.0410987  0.576529    0.585257
 0.0        0.0        0.0           0.593428   0.99256     0.429586
 0.0        0.0        0.0           0.329358   0.541753    0.564124
 0.0        0.0        0.0           0.0918989  0.248027    0.61908
 0.0        0.0        0.0           0.977239   0.337007    0.689221
 0.0        0.0        0.0        …  0.552717   0.340214    0.426312
 0.0        0.0        0.0           0.0928862  0.808408    0.341378
 0.0        0.0        0.0           0.338943   0.801685    0.391441
 ⋮                                ⋱                         
 0.907803   0.465305   0.202659      0.376308   0.213809    0.654264
 0.210653   0.0798368  0.681277      0.396067   0.474638    0.982776
 0.308087   0.370473   0.0320016  …  0.665892   0.322288    0.941678
 0.284839   0.222254   0.296509      0.212968   0.716135    0.612288
 0.0233161  0.792131   0.932767      0.189227   0.263264    0.479845
 0.816231   0.402788   0.0937667     0.549838   0.0855301   0.677286
 0.987072   0.530093   0.0695206     0.474522   0.475297    0.210332
 0.452932   0.481609   0.615754   …  0.524793   0.362966    0.199156
 0.893552   0.489221   0.617432      0.747552   0.804627    0.360774
 0.439967   0.764987   0.3217        0.224662   0.377398    0.131746
 0.343369   0.704933   0.698127      0.349691   0.831072    0.288844
 0.4028     0.536984   0.31806       0.644772   0.225268    0.37677
Gray(0.7)
typeof(Gray(0.9))
Gray{Float64}
img = Gray.(img_data)
display(MIME("text/plain"), img)
400×600 Array{Gray{Float64},2} with eltype Gray{Float64}:
 Gray{Float64}(0.0)        …  Gray{Float64}(0.596755)
 Gray{Float64}(0.0)           Gray{Float64}(0.0189425)
 Gray{Float64}(0.0)           Gray{Float64}(0.101844)
 Gray{Float64}(0.0)           Gray{Float64}(0.281218)
 Gray{Float64}(0.0)           Gray{Float64}(0.231896)
 Gray{Float64}(0.0)        …  Gray{Float64}(0.585257)
 Gray{Float64}(0.0)           Gray{Float64}(0.429586)
 Gray{Float64}(0.0)           Gray{Float64}(0.564124)
 Gray{Float64}(0.0)           Gray{Float64}(0.61908)
 Gray{Float64}(0.0)           Gray{Float64}(0.689221)
 Gray{Float64}(0.0)        …  Gray{Float64}(0.426312)
 Gray{Float64}(0.0)           Gray{Float64}(0.341378)
 Gray{Float64}(0.0)           Gray{Float64}(0.391441)
 ⋮                         ⋱  
 Gray{Float64}(0.907803)      Gray{Float64}(0.654264)
 Gray{Float64}(0.210653)      Gray{Float64}(0.982776)
 Gray{Float64}(0.308087)   …  Gray{Float64}(0.941678)
 Gray{Float64}(0.284839)      Gray{Float64}(0.612288)
 Gray{Float64}(0.0233161)     Gray{Float64}(0.479845)
 Gray{Float64}(0.816231)      Gray{Float64}(0.677286)
 Gray{Float64}(0.987072)      Gray{Float64}(0.210332)
 Gray{Float64}(0.452932)   …  Gray{Float64}(0.199156)
 Gray{Float64}(0.893552)      Gray{Float64}(0.360774)
 Gray{Float64}(0.439967)      Gray{Float64}(0.131746)
 Gray{Float64}(0.343369)      Gray{Float64}(0.288844)
 Gray{Float64}(0.4028)        Gray{Float64}(0.37677)
dump(img)
Array{Gray{Float64}}((400, 600))
  1: Gray{Float64}
    val: Float64 0.0
  2: Gray{Float64}
    val: Float64 0.0
  3: Gray{Float64}
    val: Float64 0.0
  4: Gray{Float64}
    val: Float64 0.0
  5: Gray{Float64}
    val: Float64 0.0
  ...
  239996: Gray{Float64}
    val: Float64 0.19915637518526674
  239997: Gray{Float64}
    val: Float64 0.3607737992612461
  239998: Gray{Float64}
    val: Float64 0.13174588925607555
  239999: Gray{Float64}
    val: Float64 0.2888438071782746
  240000: Gray{Float64}
    val: Float64 0.3767696115162892

Obrázky můžeme i snadno ukládat v různých formátech.

using FileIO
save("test.jpg", img)
200343
RGB(0.1, 0.2, 0.52)
HSV(180,0.5,1)
map(x -> HSV(360 * x, 1, 1), img_data)

Nahrávání obrázků

using FileIO, ImageMagick, ImageIO
img_Gauss = load("../homeworks/B211/files/gauss_portret.jpg")
typeof(img_Gauss)
Matrix{RGB{N0f8}} (alias for Array{RGB{Normed{UInt8, 8}}, 2})
N0f8
N0f8 (alias for Normed{UInt8, 8})
dump(img_Gauss)
Array{RGB{N0f8}}((480, 422))
  1: RGB{N0f8}
    r: N0f8
      i: UInt8 0xe3
    g: N0f8
      i: UInt8 0xeb
    b: N0f8
      i: UInt8 0xed
  2: RGB{N0f8}
    r: N0f8
      i: UInt8 0x99
    g: N0f8
      i: UInt8 0xa1
    b: N0f8
      i: UInt8 0xa3
  3: RGB{N0f8}
    r: N0f8
      i: UInt8 0x37
    g: N0f8
      i: UInt8 0x3f
    b: N0f8
      i: UInt8 0x41
  4: RGB{N0f8}
    r: N0f8
      i: UInt8 0x57
    g: N0f8
      i: UInt8 0x5f
    b: N0f8
      i: UInt8 0x61
  5: RGB{N0f8}
    r: N0f8
      i: UInt8 0xa1
    g: N0f8
      i: UInt8 0xa9
    b: N0f8
      i: UInt8 0xab
  ...
  202556: RGB{N0f8}
    r: N0f8
      i: UInt8 0x3f
    g: N0f8
      i: UInt8 0x4a
    b: N0f8
      i: UInt8 0x4c
  202557: RGB{N0f8}
    r: N0f8
      i: UInt8 0x3b
    g: N0f8
      i: UInt8 0x46
    b: N0f8
      i: UInt8 0x48
  202558: RGB{N0f8}
    r: N0f8
      i: UInt8 0x35
    g: N0f8
      i: UInt8 0x40
    b: N0f8
      i: UInt8 0x42
  202559: RGB{N0f8}
    r: N0f8
      i: UInt8 0x42
    g: N0f8
      i: UInt8 0x4d
    b: N0f8
      i: UInt8 0x4f
  202560: RGB{N0f8}
    r: N0f8
      i: UInt8 0x2e
    g: N0f8
      i: UInt8 0x39
    b: N0f8
      i: UInt8 0x3b

Všimněte si speciálního datového typu:

?N0f8
search: N0f8 N8f8 n0f8 N56f8 N0f16 N4f28 N60f4 N30f2 N0f64 N10f6 N6f58 N0f32

Normed{T <: Unsigned, f} <: FixedPoint{T, f}

Normed{T,f} maps Unsigned integers from 0 to 2^f-1 to the range [0.0, 1.0]. For example, Normed{UInt8,8} maps 0x00 to 0.0 and 0xff to 1.0.

There are the typealiases for Normed in the NXfY notation, where Y is the number of fractional bits (i.e. f), and X+Y equals the number of underlying bits used. For example, N0f8 is aliased to Normed{UInt8,8} and N4f12 is aliased to Normed{UInt16,12}.

Jeden pixel je barevně reprezentován trojicí čísel (RGB).

p = img_Gauss[1, 1]
typeof(p)
RGB{N0f8}
p.r
0.89N0f8
p.g
0.922N0f8
p.b
0.929N0f8
img_Gauss[10, 10]

Například můžeme extrahovat červený kanál a převést ho do datového typu, v kterém se lépe počítá:

imgR = map(x -> Float64(x.r), img_Gauss)
480×422 Matrix{Float64}:
 0.890196  0.501961   0.682353  0.854902  …  0.635294  0.647059  0.541176
 0.6       0.478431   0.639216  0.584314     0.462745  0.827451  0.788235
 0.215686  0.705882   0.615686  0.431373     0.454902  0.65098   0.513725
 0.341176  0.482353   0.435294  0.576471     0.356863  0.564706  0.4
 0.631373  0.545098   0.596078  0.403922     0.411765  0.603922  0.356863
 0.215686  0.462745   0.937255  0.65098   …  0.611765  0.584314  0.411765
 0.337255  0.109804   0.627451  0.843137     0.541176  0.615686  0.576471
 0.705882  0.427451   0.431373  0.67451      0.294118  0.701961  0.65098
 0.337255  0.439216   0.423529  0.407843     0.368627  0.639216  0.627451
 0.282353  0.278431   0.501961  0.313725     0.721569  0.478431  0.701961
 0.6       0.533333   0.513725  0.447059  …  0.768627  0.611765  0.639216
 0.682353  0.752941   0.619608  0.619608     0.721569  0.615686  0.470588
 0.427451  0.568627   0.713725  0.419608     0.556863  0.678431  0.509804
 ⋮                                        ⋱            ⋮         
 0.305882  0.0588235  0.270588  0.537255     0.239216  0.34902   0.423529
 0.356863  0.207843   0.184314  0.337255     0.380392  0.415686  0.423529
 0.27451   0.254902   0.294118  0.235294  …  0.407843  0.360784  0.403922
 0.368627  0.12549    0.356863  0.427451     0.45098   0.341176  0.4
 0.52549   0.396078   0.309804  0.258824     0.576471  0.266667  0.34902
 0.376471  0.337255   0.454902  0.372549     0.4       0.372549  0.368627
 0.129412  0.235294   0.580392  0.509804     0.443137  0.45098   0.352941
 0.329412  0.101961   0.239216  0.337255  …  0.341176  0.254902  0.247059
 0.505882  0.14902    0.192157  0.372549     0.556863  0.313725  0.231373
 0.376471  0.203922   0.219608  0.337255     0.462745  0.14902   0.207843
 0.219608  0.509804   0.623529  0.415686     0.239216  0.113725  0.258824
 0.211765  0.568627   0.607843  0.25098      0.235294  0.211765  0.180392
Gray.(imgR)
Gray.([imgR[1:180, :]; imgR[225:end, :]])
Gray.(imgR')

3. Cairo.jl a Luxor.jl

Cairo.jl představuje rozhranní k 2D grafické knihovně Cairo. S pomocí této knihovny jsme schopni deklarativním způsobem vytvářet zcela vlastní obrázky, ovšem operuje na poměrně nízké úrovni. Uživatelsky příjemnější je balíček Luxor.jl. Tento balíček nám umožňuje vytvářet rasterovou (PNG) i vektorovou (SVG) grafiku. Jeho výhodou je i poměrně podrobná dokumentace s tutoriály.

using Luxor
@svg begin
    text("Hello world")
end
@svg begin
    text("Toto je můj první obrázek!", Point(0, 0), angle = pi/4, halign = :center)
    rect(-50, -50, 100, 100, :stroke)
    circle(Point(0, 0), 50, :stroke)
end
@svg begin
    circle(Point(-50, 0), 50, action=:fill)
    circle(Point(50, 0), 60, action=:fill)
    rulers()
end

Pozor, souřadný systém je orientován v grafice obvyklým způsobem "hlavou dolů".

Vedle výše použitých primitiv rect a circ máme k dispozici spoustu dalších nástrojů.

@svg begin
    background("blue")
    sethue("red")
    move(Point(100, 0))
    arc(Point(0, 0), 100, 0, pi/4)
    arc(Point(0, 0), 200, pi/4, pi/2)
    strokepath()
end
@svg begin
    background("black")
    sethue("white")
    for j = 0:15
        line(100 * (-1)^j, -200 + j * 20)
    end
    strokepath()
end

move a line pracují v souřadnicích relativních k počátku v bodě (0,0)(0,0). K dispozici máme i rmove a rline, které pracují relativně k aktuální poloze.

@svg begin
    background("black")
    sethue("white")

    circle(Point(-100, 0), 5, action = :fill)
    circle(Point(100, 0), 5, action = :fill)
    
    move(Point(-100, 0))
    for j = 0:8
        rline(Point(50*cos(j*2pi/8), 50*sin(j*2pi/8)))
    end
    closepath()
    
    move(Point(100, 0))
    for j = 0:8
        rline(Point(50*cos(j*2pi/8), 50*sin(j*2pi/8)))
    end
    closepath()
    
    strokepath()
end

Makra se hodí k rychlému prozkoumávání a experimentování v Notebooku nebo editoru (VS Code umí obrázky správně zobrazovat).

Co když chceme výsledný obrázek exportovat?

function on_circle(j::Integer, n::Integer, R::Real)
    return Point(R*cos((j-1)*2pi/n), R*sin((j-1)*2pi/n))
end

function my_colorful_ngon(n::Integer, filename::AbstractString; R::Real = 200)
    n < 3 && error("At least 3 vertices are needed!")
    R <= 0 && error("R has to be positive!")

    Drawing(500, 500, filename)
    origin()

    for j = 1:n
        setcolor(rand(3)...)
        poly([ Point(0,0), on_circle(j-1, n, R), on_circle(j, n, R) ], :fill)
    end

    finish()
    preview()
end
my_colorful_ngon (generic function with 1 method)
my_colorful_ngon(3, "3.svg")
my_colorful_ngon(8, "8.svg")

Řešení některých příkladů

K Julia:

function julia(c::Complex{Float64}, R::Float64, reals, imags; imax::Int64=1000)
    img   = zeros(length(reals), length(imags))
    fc(z) = z^2 + c
    
    for j in axes(reals, 1), k in axes(imags, 1)
        n = 0
        z = reals[j] + 1im*imags[k]
        
        while n <= imax && abs(z) < R
            z = fc(z)
            n += 1
        end
        
        img[j, k] = n
    end
    
    return img
end